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 }