github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/controller/listmodels.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package controller 5 6 import ( 7 "fmt" 8 "io" 9 "time" 10 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/gnuflag" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/api/base" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/cmd/juju/common" 19 "github.com/juju/juju/cmd/modelcmd" 20 "github.com/juju/juju/cmd/output" 21 "github.com/juju/juju/jujuclient" 22 ) 23 24 // NewListModelsCommand returns a command to list models. 25 func NewListModelsCommand() cmd.Command { 26 return modelcmd.WrapController(&modelsCommand{}) 27 } 28 29 // modelsCommand returns the list of all the models the 30 // current user can access on the current controller. 31 type modelsCommand struct { 32 modelcmd.ControllerCommandBase 33 out cmd.Output 34 all bool 35 loggedInUser string 36 user string 37 listUUID bool 38 exactTime bool 39 modelAPI ModelManagerAPI 40 sysAPI ModelsSysAPI 41 } 42 43 var listModelsDoc = ` 44 The models listed here are either models you have created yourself, or 45 models which have been shared with you. Default values for user and 46 controller are, respectively, the current user and the current controller. 47 The active model is denoted by an asterisk. 48 49 Examples: 50 51 juju models 52 juju models --user bob 53 54 See also: 55 add-model 56 share-model 57 unshare-model 58 ` 59 60 // ModelManagerAPI defines the methods on the model manager API that 61 // the models command calls. 62 type ModelManagerAPI interface { 63 Close() error 64 ListModels(user string) ([]base.UserModel, error) 65 ModelInfo([]names.ModelTag) ([]params.ModelInfoResult, error) 66 } 67 68 // ModelsSysAPI defines the methods on the controller manager API that the 69 // list models command calls. 70 type ModelsSysAPI interface { 71 Close() error 72 AllModels() ([]base.UserModel, error) 73 } 74 75 // Info implements Command.Info 76 func (c *modelsCommand) Info() *cmd.Info { 77 return &cmd.Info{ 78 Name: "models", 79 Purpose: "Lists models a user can access on a controller.", 80 Doc: listModelsDoc, 81 Aliases: []string{"list-models"}, 82 } 83 } 84 85 func (c *modelsCommand) getModelManagerAPI() (ModelManagerAPI, error) { 86 if c.modelAPI != nil { 87 return c.modelAPI, nil 88 } 89 return c.NewModelManagerAPIClient() 90 } 91 92 func (c *modelsCommand) getSysAPI() (ModelsSysAPI, error) { 93 if c.sysAPI != nil { 94 return c.sysAPI, nil 95 } 96 return c.NewControllerAPIClient() 97 } 98 99 // SetFlags implements Command.SetFlags. 100 func (c *modelsCommand) SetFlags(f *gnuflag.FlagSet) { 101 c.ControllerCommandBase.SetFlags(f) 102 f.StringVar(&c.user, "user", "", "The user to list models for (administrative users only)") 103 f.BoolVar(&c.all, "all", false, "Lists all models, regardless of user accessibility (administrative users only)") 104 f.BoolVar(&c.listUUID, "uuid", false, "Display UUID for models") 105 f.BoolVar(&c.exactTime, "exact-time", false, "Use full timestamps") 106 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 107 "yaml": cmd.FormatYaml, 108 "json": cmd.FormatJson, 109 "tabular": c.formatTabular, 110 }) 111 } 112 113 // ModelSet contains the set of models known to the client, 114 // and UUID of the current model. 115 type ModelSet struct { 116 Models []common.ModelInfo `yaml:"models" json:"models"` 117 118 // CurrentModel is the name of the current model, qualified for the 119 // user for which we're listing models. i.e. for the user admin@local, 120 // and the model admin@local/foo, this field will contain "foo"; for 121 // bob@local and the same model, the field will contain "admin/foo". 122 CurrentModel string `yaml:"current-model,omitempty" json:"current-model,omitempty"` 123 124 // CurrentModelQualified is the fully qualified name for the current 125 // model, i.e. having the format $owner/$model. 126 CurrentModelQualified string `yaml:"-" json:"-"` 127 } 128 129 // Run implements Command.Run 130 func (c *modelsCommand) Run(ctx *cmd.Context) error { 131 accountDetails, err := c.ClientStore().AccountDetails(c.ControllerName()) 132 if err != nil { 133 return err 134 } 135 c.loggedInUser = accountDetails.User 136 137 // First get a list of the models. 138 var models []base.UserModel 139 if c.all { 140 models, err = c.getAllModels() 141 } else { 142 if c.user == "" { 143 c.user = accountDetails.User 144 } 145 models, err = c.getUserModels() 146 } 147 if err != nil { 148 return errors.Annotate(err, "cannot list models") 149 } 150 151 // And now get the full details of the models. 152 paramsModelInfo, err := c.getModelInfo(models) 153 if err != nil { 154 return errors.Annotate(err, "cannot get model details") 155 } 156 157 // TODO(perrito666) 2016-05-02 lp:1558657 158 now := time.Now() 159 modelInfo := make([]common.ModelInfo, 0, len(models)) 160 for _, info := range paramsModelInfo { 161 model, err := common.ModelInfoFromParams(info, now) 162 if err != nil { 163 return errors.Trace(err) 164 } 165 model.ControllerName = c.ControllerName() 166 modelInfo = append(modelInfo, model) 167 } 168 169 modelSet := ModelSet{Models: modelInfo} 170 current, err := c.ClientStore().CurrentModel(c.ControllerName()) 171 if err != nil && !errors.IsNotFound(err) { 172 return err 173 } 174 modelSet.CurrentModelQualified = current 175 modelSet.CurrentModel = current 176 if c.user != "" { 177 userForListing := names.NewUserTag(c.user) 178 unqualifiedModelName, owner, err := jujuclient.SplitModelName(current) 179 if err == nil { 180 modelSet.CurrentModel = common.OwnerQualifiedModelName( 181 unqualifiedModelName, owner, userForListing, 182 ) 183 } 184 } 185 186 if err := c.out.Write(ctx, modelSet); err != nil { 187 return err 188 } 189 if len(models) == 0 && c.out.Name() == "tabular" { 190 // When the output is tabular, we inform the user when there 191 // are no models available, and tell them how to go about 192 // creating or granting access to them. 193 fmt.Fprintf(ctx.Stderr, "\n%s\n\n", errNoModels.Error()) 194 } 195 return nil 196 } 197 198 func (c *modelsCommand) getModelInfo(userModels []base.UserModel) ([]params.ModelInfo, error) { 199 client, err := c.getModelManagerAPI() 200 if err != nil { 201 return nil, errors.Trace(err) 202 } 203 defer client.Close() 204 205 tags := make([]names.ModelTag, len(userModels)) 206 for i, m := range userModels { 207 tags[i] = names.NewModelTag(m.UUID) 208 } 209 results, err := client.ModelInfo(tags) 210 if err != nil { 211 return nil, errors.Trace(err) 212 } 213 214 info := make([]params.ModelInfo, len(tags)) 215 for i, result := range results { 216 if result.Error != nil { 217 if params.IsCodeUnauthorized(result.Error) { 218 // If we get this, then the model was removed 219 // between the initial listing and the call 220 // to query its details. 221 continue 222 } 223 return nil, errors.Annotatef( 224 result.Error, "getting model %s (%q) info", 225 userModels[i].UUID, userModels[i].Name, 226 ) 227 } 228 info[i] = *result.Result 229 } 230 return info, nil 231 } 232 233 func (c *modelsCommand) getAllModels() ([]base.UserModel, error) { 234 client, err := c.getSysAPI() 235 if err != nil { 236 return nil, errors.Trace(err) 237 } 238 defer client.Close() 239 return client.AllModels() 240 } 241 242 func (c *modelsCommand) getUserModels() ([]base.UserModel, error) { 243 client, err := c.getModelManagerAPI() 244 if err != nil { 245 return nil, errors.Trace(err) 246 } 247 defer client.Close() 248 return client.ListModels(c.user) 249 } 250 251 // formatTabular takes an interface{} to adhere to the cmd.Formatter interface 252 func (c *modelsCommand) formatTabular(writer io.Writer, value interface{}) error { 253 modelSet, ok := value.(ModelSet) 254 if !ok { 255 return errors.Errorf("expected value of type %T, got %T", modelSet, value) 256 } 257 258 // We need the tag of the user for which we're listing models, 259 // and for the logged-in user. We use these below when formatting 260 // the model display names. 261 loggedInUser := names.NewUserTag(c.loggedInUser) 262 userForLastConn := loggedInUser 263 var userForListing names.UserTag 264 if c.user != "" { 265 userForListing = names.NewUserTag(c.user) 266 userForLastConn = userForListing 267 } 268 269 tw := output.TabWriter(writer) 270 w := output.Wrapper{tw} 271 w.Println("CONTROLLER: " + c.ControllerName()) 272 w.Println() 273 w.Print("MODEL") 274 if c.listUUID { 275 w.Print("UUID") 276 } 277 // Only owners, or users with write access or above get to see machines and cores. 278 haveMachineInfo := false 279 for _, m := range modelSet.Models { 280 if haveMachineInfo = len(m.Machines) > 0; haveMachineInfo { 281 break 282 } 283 } 284 if haveMachineInfo { 285 w.Println("OWNER", "STATUS", "MACHINES", "CORES", "ACCESS", "LAST CONNECTION") 286 offset := 0 287 if c.listUUID { 288 offset++ 289 } 290 tw.SetColumnAlignRight(3 + offset) 291 tw.SetColumnAlignRight(4 + offset) 292 } else { 293 w.Println("OWNER", "STATUS", "ACCESS", "LAST CONNECTION") 294 } 295 for _, model := range modelSet.Models { 296 owner := names.NewUserTag(model.Owner) 297 name := common.OwnerQualifiedModelName(model.Name, owner, userForListing) 298 if jujuclient.JoinOwnerModelName(owner, model.Name) == modelSet.CurrentModelQualified { 299 name += "*" 300 w.PrintColor(output.CurrentHighlight, name) 301 } else { 302 w.Print(name) 303 } 304 if c.listUUID { 305 w.Print(model.UUID) 306 } 307 lastConnection := model.Users[userForLastConn.Canonical()].LastConnection 308 if lastConnection == "" { 309 lastConnection = "never connected" 310 } 311 userForAccess := loggedInUser 312 if c.user != "" { 313 userForAccess = names.NewUserTag(c.user) 314 } 315 access := model.Users[userForAccess.Canonical()].Access 316 w.Print(model.Owner, model.Status.Current) 317 if haveMachineInfo { 318 machineInfo := fmt.Sprintf("%d", len(model.Machines)) 319 cores := uint64(0) 320 for _, m := range model.Machines { 321 cores += m.Cores 322 } 323 coresInfo := "-" 324 if cores > 0 { 325 coresInfo = fmt.Sprintf("%d", cores) 326 } 327 w.Print(machineInfo, coresInfo) 328 } 329 w.Println(access, lastConnection) 330 } 331 tw.Flush() 332 return nil 333 }