~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/modelcmd/modelcommand.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 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
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()
 
183
                if err != nil {
 
184
                        return nil, errors.Trace(err)
 
185
                }
 
186
                if len(controllers) == 0 {
 
187
                        return nil, errors.Trace(ErrNoControllersDefined)
 
188
                }
 
189
                return nil, errors.Trace(ErrNotLoggedInToController)
 
190
        }
 
191
        if c.modelName == "" {
 
192
                return nil, errors.Trace(ErrNoModelSpecified)
 
193
        }
 
194
        opener := c.opener
 
195
        if opener == nil {
 
196
                opener = OpenFunc(c.JujuCommandBase.NewAPIRoot)
 
197
        }
 
198
        _, err := c.store.ModelByName(c.controllerName, c.modelName)
 
199
        if err != nil {
 
200
                if !errors.IsNotFound(err) {
 
201
                        return nil, errors.Trace(err)
 
202
                }
 
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")
 
207
                }
 
208
        }
 
209
        return opener.Open(c.store, c.controllerName, c.modelName)
 
210
}
 
211
 
 
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 {
 
217
        return c.modelName
 
218
}
 
219
 
 
220
// WrapControllerOption sets various parameters of the
 
221
// ModelCommand wrapper.
 
222
type WrapEnvOption func(*modelCommandWrapper)
 
223
 
 
224
// ModelSkipFlags instructs the wrapper to skip --m and
 
225
// --model flag definition.
 
226
func ModelSkipFlags(w *modelCommandWrapper) {
 
227
        w.skipFlags = true
 
228
}
 
229
 
 
230
// ModelSkipDefault instructs the wrapper not to
 
231
// use the default model.
 
232
func ModelSkipDefault(w *modelCommandWrapper) {
 
233
        w.useDefaultModel = false
 
234
}
 
235
 
 
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{
 
242
                ModelCommand:    c,
 
243
                skipFlags:       false,
 
244
                useDefaultModel: true,
 
245
                allowEmptyEnv:   false,
 
246
        }
 
247
        for _, option := range options {
 
248
                option(wrapper)
 
249
        }
 
250
        return WrapBase(wrapper)
 
251
}
 
252
 
 
253
type modelCommandWrapper struct {
 
254
        ModelCommand
 
255
 
 
256
        skipFlags       bool
 
257
        useDefaultModel bool
 
258
        allowEmptyEnv   bool
 
259
        modelName       string
 
260
}
 
261
 
 
262
func (w *modelCommandWrapper) Run(ctx *cmd.Context) error {
 
263
        return w.ModelCommand.Run(ctx)
 
264
}
 
265
 
 
266
func (w *modelCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
 
267
        if !w.skipFlags {
 
268
                f.StringVar(&w.modelName, "m", "", "Model to operate in. Accepts [<controller name>:]<model name>")
 
269
                f.StringVar(&w.modelName, "model", "", "")
 
270
        }
 
271
        w.ModelCommand.SetFlags(f)
 
272
}
 
273
 
 
274
func (w *modelCommandWrapper) Init(args []string) error {
 
275
        store := w.ClientStore()
 
276
        if store == nil {
 
277
                store = jujuclient.NewFileClientStore()
 
278
        }
 
279
        store = QualifyingClientStore{store}
 
280
        w.SetClientStore(store)
 
281
        if !w.skipFlags {
 
282
                if w.modelName == "" && w.useDefaultModel {
 
283
                        // Look for the default.
 
284
                        defaultModel, err := GetCurrentModel(store)
 
285
                        if err != nil {
 
286
                                return err
 
287
                        }
 
288
                        w.modelName = defaultModel
 
289
                }
 
290
                if w.modelName == "" && !w.useDefaultModel {
 
291
                        if w.allowEmptyEnv {
 
292
                                return w.ModelCommand.Init(args)
 
293
                        } else {
 
294
                                return errors.Trace(ErrNoModelSpecified)
 
295
                        }
 
296
                }
 
297
        }
 
298
        if w.modelName != "" {
 
299
                if err := w.SetModelName(w.modelName); err != nil {
 
300
                        return errors.Annotate(err, "setting model name")
 
301
                }
 
302
        }
 
303
        return w.ModelCommand.Init(args)
 
304
}
 
305
 
 
306
type bootstrapContext struct {
 
307
        *cmd.Context
 
308
        verifyCredentials bool
 
309
}
 
310
 
 
311
// ShouldVerifyCredentials implements BootstrapContext.ShouldVerifyCredentials
 
312
func (ctx *bootstrapContext) ShouldVerifyCredentials() bool {
 
313
        return ctx.verifyCredentials
 
314
}
 
315
 
 
316
// BootstrapContext returns a new BootstrapContext constructed from a command Context.
 
317
func BootstrapContext(cmdContext *cmd.Context) environs.BootstrapContext {
 
318
        return &bootstrapContext{
 
319
                Context:           cmdContext,
 
320
                verifyCredentials: true,
 
321
        }
 
322
}
 
323
 
 
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{
 
328
                Context:           cmdContext,
 
329
                verifyCredentials: false,
 
330
        }
 
331
}
 
332
 
 
333
type ModelGetter interface {
 
334
        ModelGet() (map[string]interface{}, error)
 
335
        Close() error
 
336
}
 
337
 
 
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:]
 
345
        }
 
346
        return "", name
 
347
}
 
348
 
 
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)
 
353
}