1
// Copyright 2013-2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"github.com/juju/loggo"
14
"launchpad.net/gnuflag"
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"
22
var logger = loggo.GetLogger("juju.cmd.modelcmd")
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
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.
34
// GetCurrentModel returns the name of the current Juju model.
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.
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 != "" {
52
currentController, err := store.CurrentController()
53
if errors.IsNotFound(err) {
55
} else if err != nil {
56
return "", errors.Trace(err)
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)
65
return JoinModelName(currentController, currentModel), nil
68
// ModelCommand extends cmd.Command with a SetModelName method.
69
type ModelCommand interface {
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)
77
// ClientStore returns the controller store that the command is
79
ClientStore() jujuclient.ClientStore
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.
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
92
// ModelName returns the name of the model.
95
// ControllerName returns the name of the controller that contains
96
// the model returned by ModelName().
97
ControllerName() string
99
// SetAPIOpener allows the replacement of the default API opener,
100
// which ends up calling NewAPIRoot
101
SetAPIOpener(opener APIOpener)
104
// ModelCommandBase is a convenience type for embedding in commands
105
// that wish to implement ModelCommand.
106
type ModelCommandBase struct {
109
// store is the client controller store that contains information
110
// about controllers, models, etc.
111
store jujuclient.ClientStore
114
controllerName string
116
// opener is the strategy used to open the API connection.
120
// SetClientStore implements the ModelCommand interface.
121
func (c *ModelCommandBase) SetClientStore(store jujuclient.ClientStore) {
125
// ClientStore implements the ModelCommand interface.
126
func (c *ModelCommandBase) ClientStore() jujuclient.ClientStore {
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)
140
controllerName = currentController
143
if _, err = c.store.ControllerByName(controllerName); err != nil {
144
return errors.Trace(err)
147
c.controllerName = controllerName
148
c.modelName = modelName
152
// ModelName implements the ModelCommand interface.
153
func (c *ModelCommandBase) ModelName() string {
157
// ControllerName implements the ModelCommand interface.
158
func (c *ModelCommandBase) ControllerName() string {
159
return c.controllerName
162
// SetAPIOpener specifies the strategy used by the command to open
163
// the API connection.
164
func (c *ModelCommandBase) SetAPIOpener(opener APIOpener) {
168
func (c *ModelCommandBase) NewAPIClient() (*api.Client, error) {
169
root, err := c.NewAPIRoot()
171
return nil, errors.Trace(err)
173
return root.Client(), nil
176
// NewAPIRoot returns a new connection to the API server for the environment.
177
func (c *ModelCommandBase) NewAPIRoot() (api.Connection, error) {
178
// This is work in progress as we remove the ModelName from downstream code.
179
// We want to be able to specify the environment in a number of ways, one of
180
// which is the connection name on the client machine.
181
if c.controllerName == "" {
182
controllers, err := c.store.AllControllers()
184
return nil, errors.Trace(err)
186
if len(controllers) == 0 {
187
return nil, errors.Trace(ErrNoControllersDefined)
189
return nil, errors.Trace(ErrNotLoggedInToController)
191
if c.modelName == "" {
192
return nil, errors.Trace(ErrNoModelSpecified)
196
opener = OpenFunc(c.JujuCommandBase.NewAPIRoot)
198
_, err := c.store.ModelByName(c.controllerName, c.modelName)
200
if !errors.IsNotFound(err) {
201
return nil, errors.Trace(err)
203
// The model isn't known locally, so query the models
204
// available in the controller, and cache them locally.
205
if err := c.RefreshModels(c.store, c.controllerName); err != nil {
206
return nil, errors.Annotate(err, "refreshing models")
209
return opener.Open(c.store, c.controllerName, c.modelName)
212
// ConnectionName returns the name of the connection if there is one.
213
// It is possible that the name of the connection is empty if the
214
// connection information is supplied through command line arguments
215
// or environment variables.
216
func (c *ModelCommandBase) ConnectionName() string {
220
// WrapControllerOption sets various parameters of the
221
// ModelCommand wrapper.
222
type WrapEnvOption func(*modelCommandWrapper)
224
// ModelSkipFlags instructs the wrapper to skip --m and
225
// --model flag definition.
226
func ModelSkipFlags(w *modelCommandWrapper) {
230
// ModelSkipDefault instructs the wrapper not to
231
// use the default model.
232
func ModelSkipDefault(w *modelCommandWrapper) {
233
w.useDefaultModel = false
236
// Wrap wraps the specified ModelCommand, returning a Command
237
// that proxies to each of the ModelCommand methods.
238
// Any provided options are applied to the wrapped command
239
// before it is returned.
240
func Wrap(c ModelCommand, options ...WrapEnvOption) cmd.Command {
241
wrapper := &modelCommandWrapper{
244
useDefaultModel: true,
245
allowEmptyEnv: false,
247
for _, option := range options {
250
return WrapBase(wrapper)
253
type modelCommandWrapper struct {
262
func (w *modelCommandWrapper) Run(ctx *cmd.Context) error {
263
return w.ModelCommand.Run(ctx)
266
func (w *modelCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
268
f.StringVar(&w.modelName, "m", "", "Model to operate in. Accepts [<controller name>:]<model name>")
269
f.StringVar(&w.modelName, "model", "", "")
271
w.ModelCommand.SetFlags(f)
274
func (w *modelCommandWrapper) Init(args []string) error {
275
store := w.ClientStore()
277
store = jujuclient.NewFileClientStore()
279
store = QualifyingClientStore{store}
280
w.SetClientStore(store)
282
if w.modelName == "" && w.useDefaultModel {
283
// Look for the default.
284
defaultModel, err := GetCurrentModel(store)
288
w.modelName = defaultModel
290
if w.modelName == "" && !w.useDefaultModel {
292
return w.ModelCommand.Init(args)
294
return errors.Trace(ErrNoModelSpecified)
298
if w.modelName != "" {
299
if err := w.SetModelName(w.modelName); err != nil {
300
return errors.Annotate(err, "setting model name")
303
return w.ModelCommand.Init(args)
306
type bootstrapContext struct {
308
verifyCredentials bool
311
// ShouldVerifyCredentials implements BootstrapContext.ShouldVerifyCredentials
312
func (ctx *bootstrapContext) ShouldVerifyCredentials() bool {
313
return ctx.verifyCredentials
316
// BootstrapContext returns a new BootstrapContext constructed from a command Context.
317
func BootstrapContext(cmdContext *cmd.Context) environs.BootstrapContext {
318
return &bootstrapContext{
320
verifyCredentials: true,
324
// BootstrapContextNoVerify returns a new BootstrapContext constructed from a command Context
325
// where the validation of credentials is false.
326
func BootstrapContextNoVerify(cmdContext *cmd.Context) environs.BootstrapContext {
327
return &bootstrapContext{
329
verifyCredentials: false,
333
type ModelGetter interface {
334
ModelGet() (map[string]interface{}, error)
338
// SplitModelName splits a model name into its controller
339
// and model parts. If the model is unqualified, then the
340
// returned controller string will be empty, and the returned
341
// model string will be identical to the input.
342
func SplitModelName(name string) (controller, model string) {
343
if i := strings.IndexRune(name, ':'); i >= 0 {
344
return name[:i], name[i+1:]
349
// JoinModelName joins a controller and model name into a
350
// qualified model name.
351
func JoinModelName(controller, model string) string {
352
return fmt.Sprintf("%s:%s", controller, model)