github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/modelcmd/modelcommand.go (about) 1 // Copyright 2013-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelcmd 5 6 import ( 7 "fmt" 8 "os" 9 "strings" 10 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/gnuflag" 14 "github.com/juju/loggo" 15 16 "github.com/juju/juju/api" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/juju/osenv" 19 "github.com/juju/juju/jujuclient" 20 ) 21 22 var logger = loggo.GetLogger("juju.cmd.modelcmd") 23 24 // ErrNoModelSpecified is returned by commands that operate on 25 // an environment if there is no current model, no model 26 // has been explicitly specified, and there is no default model. 27 var ErrNoModelSpecified = errors.New(`No model in focus. 28 29 Please use "juju models" to see models available to you. 30 You can set current model by running "juju switch" 31 or specify any other model on the command line using the "-m" flag. 32 `) 33 34 // GetCurrentModel returns the name of the current Juju model. 35 // 36 // If $JUJU_MODEL is set, use that. Otherwise, get the current 37 // controller from controllers.yaml, and then identify the current 38 // model for that controller in models.yaml. If there is no current 39 // controller, then an empty string is returned. It is not an error 40 // to have no current model. 41 // 42 // If there is a current controller, but no current model for that 43 // controller, then GetCurrentModel will return the string 44 // "<controller>:". If there is a current model as well, it will 45 // return "<controller>:<model>". Only when $JUJU_MODEL is set, 46 // will the result possibly be unqualified. 47 func GetCurrentModel(store jujuclient.ClientStore) (string, error) { 48 if model := os.Getenv(osenv.JujuModelEnvKey); model != "" { 49 return model, nil 50 } 51 52 currentController, err := store.CurrentController() 53 if errors.IsNotFound(err) { 54 return "", nil 55 } else if err != nil { 56 return "", errors.Trace(err) 57 } 58 59 currentModel, err := store.CurrentModel(currentController) 60 if errors.IsNotFound(err) { 61 return currentController + ":", nil 62 } else if err != nil { 63 return "", errors.Trace(err) 64 } 65 return JoinModelName(currentController, currentModel), nil 66 } 67 68 // ModelCommand extends cmd.Command with a SetModelName method. 69 type ModelCommand interface { 70 CommandBase 71 72 // SetClientStore is called prior to the wrapped command's Init method 73 // with the default controller store. It may also be called to override the 74 // default controller store for testing. 75 SetClientStore(jujuclient.ClientStore) 76 77 // ClientStore returns the controller store that the command is 78 // associated with. 79 ClientStore() jujuclient.ClientStore 80 81 // SetModelName sets the model name for this command. Setting the model 82 // name will also set the related controller name. The model name can 83 // be qualified with a controller name (controller:model), or 84 // unqualified, in which case it will be assumed to be within the 85 // current controller. 86 // 87 // SetModelName is called prior to the wrapped command's Init method 88 // with the active model name. The model name is guaranteed 89 // to be non-empty at entry of Init. 90 SetModelName(modelName string) error 91 92 // ModelName returns the name of the model. 93 ModelName() string 94 95 // ControllerName returns the name of the controller that contains 96 // the model returned by ModelName(). 97 ControllerName() string 98 99 // SetAPIOpener allows the replacement of the default API opener, 100 // which ends up calling NewAPIRoot 101 SetAPIOpener(opener APIOpener) 102 } 103 104 // ModelCommandBase is a convenience type for embedding in commands 105 // that wish to implement ModelCommand. 106 type ModelCommandBase struct { 107 JujuCommandBase 108 109 // store is the client controller store that contains information 110 // about controllers, models, etc. 111 store jujuclient.ClientStore 112 113 modelName string 114 controllerName string 115 116 // opener is the strategy used to open the API connection. 117 opener APIOpener 118 } 119 120 // SetClientStore implements the ModelCommand interface. 121 func (c *ModelCommandBase) SetClientStore(store jujuclient.ClientStore) { 122 c.store = store 123 } 124 125 // ClientStore implements the ModelCommand interface. 126 func (c *ModelCommandBase) ClientStore() jujuclient.ClientStore { 127 return c.store 128 } 129 130 // SetModelName implements the ModelCommand interface. 131 func (c *ModelCommandBase) SetModelName(modelName string) error { 132 controllerName, modelName := SplitModelName(modelName) 133 if controllerName == "" { 134 currentController, err := c.store.CurrentController() 135 if errors.IsNotFound(err) { 136 return errors.Errorf("no current controller, and none specified") 137 } else if err != nil { 138 return errors.Trace(err) 139 } 140 controllerName = currentController 141 } else { 142 var err error 143 if _, err = c.store.ControllerByName(controllerName); err != nil { 144 return errors.Trace(err) 145 } 146 } 147 c.controllerName = controllerName 148 c.modelName = modelName 149 return nil 150 } 151 152 // ModelName implements the ModelCommand interface. 153 func (c *ModelCommandBase) ModelName() string { 154 return c.modelName 155 } 156 157 // ControllerName implements the ModelCommand interface. 158 func (c *ModelCommandBase) ControllerName() string { 159 return c.controllerName 160 } 161 162 // SetAPIOpener specifies the strategy used by the command to open 163 // the API connection. 164 func (c *ModelCommandBase) SetAPIOpener(opener APIOpener) { 165 c.opener = opener 166 } 167 168 func (c *ModelCommandBase) NewAPIClient() (*api.Client, error) { 169 root, err := c.NewAPIRoot() 170 if err != nil { 171 return nil, errors.Trace(err) 172 } 173 return root.Client(), nil 174 } 175 176 // NewAPIRoot returns a new connection to the API server for the environment 177 // directed to the model specified on the command line. 178 func (c *ModelCommandBase) NewAPIRoot() (api.Connection, error) { 179 // This is work in progress as we remove the ModelName from downstream code. 180 // We want to be able to specify the environment in a number of ways, one of 181 // which is the connection name on the client machine. 182 if c.modelName == "" { 183 return nil, errors.Trace(ErrNoModelSpecified) 184 } 185 _, err := c.store.ModelByName(c.controllerName, c.modelName) 186 if err != nil { 187 if !errors.IsNotFound(err) { 188 return nil, errors.Trace(err) 189 } 190 // The model isn't known locally, so query the models 191 // available in the controller, and cache them locally. 192 if err := c.RefreshModels(c.store, c.controllerName); err != nil { 193 return nil, errors.Annotate(err, "refreshing models") 194 } 195 } 196 return c.newAPIRoot(c.modelName) 197 } 198 199 // NewControllerAPIRoot returns a new connection to the API server for the environment 200 // directed to the controller specified on the command line. 201 // This is for the use of model-centered commands that still want 202 // to talk to controller-only APIs. 203 func (c *ModelCommandBase) NewControllerAPIRoot() (api.Connection, error) { 204 return c.newAPIRoot("") 205 } 206 207 // newAPIRoot is the internal implementation of NewAPIRoot and NewControllerAPIRoot; 208 // if modelName is empty, it makes a controller-only connection. 209 func (c *ModelCommandBase) newAPIRoot(modelName string) (api.Connection, error) { 210 if c.controllerName == "" { 211 controllers, err := c.store.AllControllers() 212 if err != nil { 213 return nil, errors.Trace(err) 214 } 215 if len(controllers) == 0 { 216 return nil, errors.Trace(ErrNoControllersDefined) 217 } 218 return nil, errors.Trace(ErrNoCurrentController) 219 } 220 opener := c.opener 221 if opener == nil { 222 opener = OpenFunc(c.JujuCommandBase.NewAPIRoot) 223 } 224 return opener.Open(c.store, c.controllerName, modelName) 225 } 226 227 // ConnectionName returns the name of the connection if there is one. 228 // It is possible that the name of the connection is empty if the 229 // connection information is supplied through command line arguments 230 // or environment variables. 231 func (c *ModelCommandBase) ConnectionName() string { 232 return c.modelName 233 } 234 235 // WrapOption specifies an option to the Wrap function. 236 type WrapOption func(*modelCommandWrapper) 237 238 // Options for the Wrap function. 239 var ( 240 // WrapSkipModelFlags specifies that the -m and --model flags 241 // should not be defined. 242 WrapSkipModelFlags WrapOption = wrapSkipModelFlags 243 244 // WrapSkipDefaultModel specifies that no default model should 245 // be used. 246 WrapSkipDefaultModel WrapOption = wrapSkipDefaultModel 247 ) 248 249 func wrapSkipModelFlags(w *modelCommandWrapper) { 250 w.skipModelFlags = true 251 } 252 253 func wrapSkipDefaultModel(w *modelCommandWrapper) { 254 w.useDefaultModel = false 255 } 256 257 // Wrap wraps the specified ModelCommand, returning a Command 258 // that proxies to each of the ModelCommand methods. 259 // Any provided options are applied to the wrapped command 260 // before it is returned. 261 func Wrap(c ModelCommand, options ...WrapOption) cmd.Command { 262 wrapper := &modelCommandWrapper{ 263 ModelCommand: c, 264 skipModelFlags: false, 265 useDefaultModel: true, 266 } 267 for _, option := range options { 268 option(wrapper) 269 } 270 return WrapBase(wrapper) 271 } 272 273 type modelCommandWrapper struct { 274 ModelCommand 275 276 skipModelFlags bool 277 useDefaultModel bool 278 modelName string 279 } 280 281 func (w *modelCommandWrapper) Run(ctx *cmd.Context) error { 282 return w.ModelCommand.Run(ctx) 283 } 284 285 func (w *modelCommandWrapper) SetFlags(f *gnuflag.FlagSet) { 286 if !w.skipModelFlags { 287 f.StringVar(&w.modelName, "m", "", "Model to operate in. Accepts [<controller name>:]<model name>") 288 f.StringVar(&w.modelName, "model", "", "") 289 } 290 w.ModelCommand.SetFlags(f) 291 } 292 293 func (w *modelCommandWrapper) Init(args []string) error { 294 store := w.ClientStore() 295 if store == nil { 296 store = jujuclient.NewFileClientStore() 297 } 298 store = QualifyingClientStore{store} 299 w.SetClientStore(store) 300 if !w.skipModelFlags { 301 if w.modelName == "" && w.useDefaultModel { 302 // Look for the default. 303 defaultModel, err := GetCurrentModel(store) 304 if err != nil { 305 return err 306 } 307 w.modelName = defaultModel 308 } 309 if w.modelName == "" && !w.useDefaultModel { 310 return errors.Trace(ErrNoModelSpecified) 311 } 312 } 313 if w.modelName != "" { 314 if err := w.SetModelName(w.modelName); err != nil { 315 return translateControllerError(store, err) 316 } 317 } 318 return w.ModelCommand.Init(args) 319 } 320 321 type bootstrapContext struct { 322 *cmd.Context 323 verifyCredentials bool 324 } 325 326 // ShouldVerifyCredentials implements BootstrapContext.ShouldVerifyCredentials 327 func (ctx *bootstrapContext) ShouldVerifyCredentials() bool { 328 return ctx.verifyCredentials 329 } 330 331 // BootstrapContext returns a new BootstrapContext constructed from a command Context. 332 func BootstrapContext(cmdContext *cmd.Context) environs.BootstrapContext { 333 return &bootstrapContext{ 334 Context: cmdContext, 335 verifyCredentials: true, 336 } 337 } 338 339 // BootstrapContextNoVerify returns a new BootstrapContext constructed from a command Context 340 // where the validation of credentials is false. 341 func BootstrapContextNoVerify(cmdContext *cmd.Context) environs.BootstrapContext { 342 return &bootstrapContext{ 343 Context: cmdContext, 344 verifyCredentials: false, 345 } 346 } 347 348 // SplitModelName splits a model name into its controller 349 // and model parts. If the model is unqualified, then the 350 // returned controller string will be empty, and the returned 351 // model string will be identical to the input. 352 func SplitModelName(name string) (controller, model string) { 353 if i := strings.IndexRune(name, ':'); i >= 0 { 354 return name[:i], name[i+1:] 355 } 356 return "", name 357 } 358 359 // JoinModelName joins a controller and model name into a 360 // qualified model name. 361 func JoinModelName(controller, model string) string { 362 return fmt.Sprintf("%s:%s", controller, model) 363 }