github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/controller/showcontroller.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/gnuflag"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/api/base"
    15  	"github.com/juju/juju/api/controller"
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/environs/bootstrap"
    19  	"github.com/juju/juju/jujuclient"
    20  	"github.com/juju/juju/permission"
    21  	"github.com/juju/juju/status"
    22  )
    23  
    24  var usageShowControllerSummary = `
    25  Shows detailed information of a controller.`[1:]
    26  
    27  var usageShowControllerDetails = `
    28  Shows extended information about a controller(s) as well as related models
    29  and user login details.
    30  
    31  Examples:
    32      juju show-controller
    33      juju show-controller aws google
    34      
    35  See also: 
    36      controllers`[1:]
    37  
    38  type showControllerCommand struct {
    39  	modelcmd.JujuCommandBase
    40  
    41  	out   cmd.Output
    42  	store jujuclient.ClientStore
    43  	api   func(controllerName string) ControllerAccessAPI
    44  
    45  	controllerNames []string
    46  	showPasswords   bool
    47  }
    48  
    49  // NewShowControllerCommand returns a command to show details of the desired controllers.
    50  func NewShowControllerCommand() cmd.Command {
    51  	cmd := &showControllerCommand{
    52  		store: jujuclient.NewFileClientStore(),
    53  	}
    54  	return modelcmd.WrapBase(cmd)
    55  }
    56  
    57  // Init implements Command.Init.
    58  func (c *showControllerCommand) Init(args []string) (err error) {
    59  	c.controllerNames = args
    60  	return nil
    61  }
    62  
    63  // Info implements Command.Info
    64  func (c *showControllerCommand) Info() *cmd.Info {
    65  	return &cmd.Info{
    66  		Name:    "show-controller",
    67  		Args:    "[<controller name> ...]",
    68  		Purpose: usageShowControllerSummary,
    69  		Doc:     usageShowControllerDetails,
    70  	}
    71  }
    72  
    73  // SetFlags implements Command.SetFlags.
    74  func (c *showControllerCommand) SetFlags(f *gnuflag.FlagSet) {
    75  	c.JujuCommandBase.SetFlags(f)
    76  	f.BoolVar(&c.showPasswords, "show-password", false, "Show password for logged in user")
    77  	c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{
    78  		"yaml": cmd.FormatYaml,
    79  		"json": cmd.FormatJson,
    80  	})
    81  }
    82  
    83  // ControllerAccessAPI defines a subset of the api/controller/Client API.
    84  type ControllerAccessAPI interface {
    85  	GetControllerAccess(user string) (permission.Access, error)
    86  	ModelConfig() (map[string]interface{}, error)
    87  	ModelStatus(models ...names.ModelTag) ([]base.ModelStatus, error)
    88  	AllModels() ([]base.UserModel, error)
    89  	Close() error
    90  }
    91  
    92  func (c *showControllerCommand) getAPI(controllerName string) (ControllerAccessAPI, error) {
    93  	if c.api != nil {
    94  		return c.api(controllerName), nil
    95  	}
    96  	api, err := c.NewAPIRoot(c.store, controllerName, "")
    97  	if err != nil {
    98  		return nil, errors.Annotate(err, "opening API connection")
    99  	}
   100  	return controller.NewClient(api), nil
   101  }
   102  
   103  // Run implements Command.Run
   104  func (c *showControllerCommand) Run(ctx *cmd.Context) error {
   105  	controllerNames := c.controllerNames
   106  	if len(controllerNames) == 0 {
   107  		currentController, err := c.store.CurrentController()
   108  		if errors.IsNotFound(err) {
   109  			return errors.New("there is no active controller")
   110  		} else if err != nil {
   111  			return errors.Trace(err)
   112  		}
   113  		controllerNames = []string{currentController}
   114  	}
   115  	controllers := make(map[string]ShowControllerDetails)
   116  	for _, controllerName := range controllerNames {
   117  		one, err := c.store.ControllerByName(controllerName)
   118  		if err != nil {
   119  			return err
   120  		}
   121  		var access string
   122  		client, err := c.getAPI(controllerName)
   123  		if err != nil {
   124  			return err
   125  		}
   126  		defer client.Close()
   127  		accountDetails, err := c.store.AccountDetails(controllerName)
   128  		if err != nil {
   129  			fmt.Fprintln(ctx.Stderr, err)
   130  			access = "(error)"
   131  		} else {
   132  			access = c.userAccess(client, ctx, accountDetails.User)
   133  			one.AgentVersion = c.agentVersion(client, ctx)
   134  		}
   135  
   136  		var details ShowControllerDetails
   137  		var modelStatus []base.ModelStatus
   138  		allModels, err := client.AllModels()
   139  		if err != nil {
   140  			details.Errors = append(details.Errors, err.Error())
   141  			continue
   142  		}
   143  		modelTags := make([]names.ModelTag, len(allModels))
   144  		for i, m := range allModels {
   145  			modelTags[i] = names.NewModelTag(m.UUID)
   146  		}
   147  		modelStatus, err = client.ModelStatus(modelTags...)
   148  		if err != nil {
   149  			details.Errors = append(details.Errors, err.Error())
   150  			continue
   151  		}
   152  		c.convertControllerForShow(&details, controllerName, one, access, allModels, modelStatus)
   153  		controllers[controllerName] = details
   154  	}
   155  	return c.out.Write(ctx, controllers)
   156  }
   157  
   158  func (c *showControllerCommand) userAccess(client ControllerAccessAPI, ctx *cmd.Context, user string) string {
   159  	var access string
   160  	userAccess, err := client.GetControllerAccess(user)
   161  	if err == nil {
   162  		access = string(userAccess)
   163  	} else {
   164  		code := params.ErrCode(err)
   165  		if code != "" {
   166  			access = fmt.Sprintf("(%s)", code)
   167  		} else {
   168  			fmt.Fprintln(ctx.Stderr, err)
   169  			access = "(error)"
   170  		}
   171  	}
   172  	return access
   173  }
   174  
   175  func (c *showControllerCommand) agentVersion(client ControllerAccessAPI, ctx *cmd.Context) string {
   176  	var ver string
   177  	mc, err := client.ModelConfig()
   178  	if err != nil {
   179  		code := params.ErrCode(err)
   180  		if code != "" {
   181  			ver = fmt.Sprintf("(%s)", code)
   182  		} else {
   183  			fmt.Fprintln(ctx.Stderr, err)
   184  			ver = "(error)"
   185  		}
   186  		return ver
   187  	}
   188  	return mc["agent-version"].(string)
   189  }
   190  
   191  type ShowControllerDetails struct {
   192  	// Details contains the same details that client store caches for this controller.
   193  	Details ControllerDetails `yaml:"details,omitempty" json:"details,omitempty"`
   194  
   195  	// Machines is a collection of all machines forming the controller cluster.
   196  	Machines map[string]MachineDetails `yaml:"controller-machines,omitempty" json:"controller-machines,omitempty"`
   197  
   198  	// Models is a collection of all models for this controller.
   199  	Models map[string]ModelDetails `yaml:"models,omitempty" json:"models,omitempty"`
   200  
   201  	// CurrentModel is the name of the current model for this controller
   202  	CurrentModel string `yaml:"current-model,omitempty" json:"current-model,omitempty"`
   203  
   204  	// Account is the account details for the user logged into this controller.
   205  	Account *AccountDetails `yaml:"account,omitempty" json:"account,omitempty"`
   206  
   207  	// Errors is a collection of errors related to accessing this controller details.
   208  	Errors []string `yaml:"errors,omitempty" json:"errors,omitempty"`
   209  }
   210  
   211  // ControllerDetails holds details of a controller to show.
   212  type ControllerDetails struct {
   213  	// ControllerUUID is the unique ID for the controller.
   214  	ControllerUUID string `yaml:"uuid" json:"uuid"`
   215  
   216  	// APIEndpoints is the collection of API endpoints running in this controller.
   217  	APIEndpoints []string `yaml:"api-endpoints,flow" json:"api-endpoints"`
   218  
   219  	// CACert is a security certificate for this controller.
   220  	CACert string `yaml:"ca-cert" json:"ca-cert"`
   221  
   222  	// Cloud is the name of the cloud that this controller runs in.
   223  	Cloud string `yaml:"cloud" json:"cloud"`
   224  
   225  	// CloudRegion is the name of the cloud region that this controller runs in.
   226  	CloudRegion string `yaml:"region,omitempty" json:"region,omitempty"`
   227  
   228  	// AgentVersion is the version of the agent running on this controller.
   229  	// AgentVersion need not always exist so we omitempty here. This struct is
   230  	// used in both list-controller and show-controller. show-controller
   231  	// displays the agent version where list-controller does not.
   232  	AgentVersion string `yaml:"agent-version,omitempty" json:"agent-version,omitempty"`
   233  }
   234  
   235  // ModelDetails holds details of a model to show.
   236  type MachineDetails struct {
   237  	// ID holds the id of the machine.
   238  	ID string `yaml:"id,omitempty" json:"id,omitempty"`
   239  
   240  	// InstanceID holds the cloud instance id of the machine.
   241  	InstanceID string `yaml:"instance-id,omitempty" json:"instance-id,omitempty"`
   242  
   243  	// HAStatus holds information informing of the HA status of the machine.
   244  	HAStatus string `yaml:"ha-status,omitempty" json:"ha-status,omitempty"`
   245  }
   246  
   247  // ModelDetails holds details of a model to show.
   248  type ModelDetails struct {
   249  	// ModelUUID holds the details of a model.
   250  	ModelUUID string `yaml:"uuid" json:"uuid"`
   251  
   252  	// MachineCount holds the number of machines in the model.
   253  	MachineCount *int `yaml:"machine-count,omitempty" json:"machine-count,omitempty"`
   254  
   255  	// CoreCount holds the number of cores across the machines in the model.
   256  	CoreCount *int `yaml:"core-count,omitempty" json:"core-count,omitempty"`
   257  }
   258  
   259  // AccountDetails holds details of an account to show.
   260  type AccountDetails struct {
   261  	// User is the username for the account.
   262  	User string `yaml:"user" json:"user"`
   263  
   264  	// Access is the level of access the user has on the controller.
   265  	Access string `yaml:"access,omitempty" json:"access,omitempty"`
   266  
   267  	// Password is the password for the account.
   268  	Password string `yaml:"password,omitempty" json:"password,omitempty"`
   269  }
   270  
   271  func (c *showControllerCommand) convertControllerForShow(
   272  	controller *ShowControllerDetails,
   273  	controllerName string,
   274  	details *jujuclient.ControllerDetails,
   275  	access string,
   276  	allModels []base.UserModel,
   277  	modelStatus []base.ModelStatus,
   278  ) {
   279  
   280  	controller.Details = ControllerDetails{
   281  		ControllerUUID: details.ControllerUUID,
   282  		APIEndpoints:   details.APIEndpoints,
   283  		CACert:         details.CACert,
   284  		Cloud:          details.Cloud,
   285  		CloudRegion:    details.CloudRegion,
   286  		AgentVersion:   details.AgentVersion,
   287  	}
   288  	c.convertModelsForShow(controllerName, controller, allModels, modelStatus)
   289  	c.convertAccountsForShow(controllerName, controller, access)
   290  	var controllerModelUUID string
   291  	for _, m := range allModels {
   292  		if m.Name == bootstrap.ControllerModelName {
   293  			controllerModelUUID = m.UUID
   294  			break
   295  		}
   296  	}
   297  	if controllerModelUUID != "" {
   298  		var controllerModel base.ModelStatus
   299  		found := false
   300  		for _, m := range modelStatus {
   301  			if m.UUID == controllerModelUUID {
   302  				controllerModel = m
   303  				found = true
   304  				break
   305  			}
   306  		}
   307  		if found {
   308  			c.convertMachinesForShow(controllerName, controller, controllerModel)
   309  		}
   310  	}
   311  }
   312  
   313  func (c *showControllerCommand) convertAccountsForShow(controllerName string, controller *ShowControllerDetails, access string) {
   314  	storeDetails, err := c.store.AccountDetails(controllerName)
   315  	if err != nil && !errors.IsNotFound(err) {
   316  		controller.Errors = append(controller.Errors, err.Error())
   317  	}
   318  	if storeDetails == nil {
   319  		return
   320  	}
   321  	details := &AccountDetails{
   322  		User:   storeDetails.User,
   323  		Access: access,
   324  	}
   325  	if c.showPasswords {
   326  		details.Password = storeDetails.Password
   327  	}
   328  	controller.Account = details
   329  }
   330  
   331  func (c *showControllerCommand) convertModelsForShow(
   332  	controllerName string,
   333  	controller *ShowControllerDetails,
   334  	models []base.UserModel,
   335  	modelStatus []base.ModelStatus,
   336  ) {
   337  	controller.Models = make(map[string]ModelDetails)
   338  	for i, model := range models {
   339  		modelDetails := ModelDetails{ModelUUID: model.UUID}
   340  		if modelStatus[i].TotalMachineCount > 0 {
   341  			modelDetails.MachineCount = new(int)
   342  			*modelDetails.MachineCount = modelStatus[i].TotalMachineCount
   343  		}
   344  		if modelStatus[i].CoreCount > 0 {
   345  			modelDetails.CoreCount = new(int)
   346  			*modelDetails.CoreCount = modelStatus[i].CoreCount
   347  		}
   348  		controller.Models[model.Name] = modelDetails
   349  	}
   350  	var err error
   351  	controller.CurrentModel, err = c.store.CurrentModel(controllerName)
   352  	if err != nil && !errors.IsNotFound(err) {
   353  		controller.Errors = append(controller.Errors, err.Error())
   354  	}
   355  }
   356  
   357  func (c *showControllerCommand) convertMachinesForShow(
   358  	controllerName string,
   359  	controller *ShowControllerDetails,
   360  	controllerModel base.ModelStatus,
   361  ) {
   362  	controller.Machines = make(map[string]MachineDetails)
   363  	numControllers := 0
   364  	for _, m := range controllerModel.Machines {
   365  		if !m.WantsVote {
   366  			continue
   367  		}
   368  		numControllers++
   369  	}
   370  	for _, m := range controllerModel.Machines {
   371  		if !m.WantsVote {
   372  			// Skip non controller machines.
   373  			continue
   374  		}
   375  		instId := m.InstanceId
   376  		if instId == "" {
   377  			instId = "(unprovisioned)"
   378  		}
   379  		details := MachineDetails{InstanceID: instId}
   380  		if numControllers > 1 {
   381  			details.HAStatus = haStatus(m.HasVote, m.WantsVote, m.Status)
   382  		}
   383  		controller.Machines[m.Id] = details
   384  	}
   385  }
   386  
   387  func haStatus(hasVote bool, wantsVote bool, statusStr string) string {
   388  	if statusStr == string(status.Down) {
   389  		return "down, lost connection"
   390  	}
   391  	if !wantsVote {
   392  		return ""
   393  	}
   394  	if hasVote {
   395  		return "ha-enabled"
   396  	}
   397  	return "ha-pending"
   398  }