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