github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/envcmd/environmentcommand.go (about) 1 // Copyright 2013-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package envcmd 5 6 import ( 7 "io" 8 "os" 9 "strconv" 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/environs/config" 19 "github.com/juju/juju/environs/configstore" 20 "github.com/juju/juju/juju" 21 "github.com/juju/juju/juju/osenv" 22 "github.com/juju/juju/version" 23 ) 24 25 var logger = loggo.GetLogger("juju.cmd.envcmd") 26 27 // ErrNoEnvironmentSpecified is returned by commands that operate on 28 // an environment if there is no current environment, no environment 29 // has been explicitly specified, and there is no default environment. 30 var ErrNoEnvironmentSpecified = errors.New("no environment specified") 31 32 // GetDefaultEnvironment returns the name of the Juju default environment. 33 // There is simple ordering for the default environment. Firstly check the 34 // JUJU_ENV environment variable. If that is set, it gets used. If it isn't 35 // set, look in the $JUJU_HOME/current-environment file. If neither are 36 // available, read environments.yaml and use the default environment therein. 37 // If no default is specified in the environments file, an empty string is returned. 38 // Not having a default environment specified is not an error. 39 func GetDefaultEnvironment() (string, error) { 40 if defaultEnv := os.Getenv(osenv.JujuEnvEnvKey); defaultEnv != "" { 41 return defaultEnv, nil 42 } 43 if currentEnv, err := ReadCurrentEnvironment(); err != nil { 44 return "", errors.Trace(err) 45 } else if currentEnv != "" { 46 return currentEnv, nil 47 } 48 if currentSystem, err := ReadCurrentSystem(); err != nil { 49 return "", errors.Trace(err) 50 } else if currentSystem != "" { 51 return "", errors.Errorf("not operating on an environment, using system %q", currentSystem) 52 } 53 envs, err := environs.ReadEnvirons("") 54 if environs.IsNoEnv(err) { 55 // That's fine, not an error here. 56 return "", nil 57 } else if err != nil { 58 return "", errors.Trace(err) 59 } 60 return envs.Default, nil 61 } 62 63 // EnvironCommand extends cmd.Command with a SetEnvName method. 64 type EnvironCommand interface { 65 cmd.Command 66 67 // SetEnvName is called prior to the wrapped command's Init method 68 // with the active environment name. The environment name is guaranteed 69 // to be non-empty at entry of Init. 70 SetEnvName(envName string) 71 } 72 73 // EnvCommandBase is a convenience type for embedding in commands 74 // that wish to implement EnvironCommand. 75 type EnvCommandBase struct { 76 cmd.CommandBase 77 // EnvName will very soon be package visible only as we want to be able 78 // to specify an environment in multiple ways, and not always referencing 79 // a file on disk based on the EnvName or the environemnts.yaml file. 80 envName string 81 82 // compatVersion defines the minimum CLI version 83 // that this command should be compatible with. 84 compatVerson *int 85 86 envGetterClient EnvironmentGetter 87 envGetterErr error 88 } 89 90 func (c *EnvCommandBase) SetEnvName(envName string) { 91 c.envName = envName 92 } 93 94 func (c *EnvCommandBase) NewAPIClient() (*api.Client, error) { 95 root, err := c.NewAPIRoot() 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 return root.Client(), nil 100 } 101 102 // NewEnvironmentGetter returns a new object which implements the 103 // EnvironmentGetter interface. 104 func (c *EnvCommandBase) NewEnvironmentGetter() (EnvironmentGetter, error) { 105 if c.envGetterErr != nil { 106 return nil, c.envGetterErr 107 } 108 109 if c.envGetterClient != nil { 110 return c.envGetterClient, nil 111 } 112 113 return c.NewAPIClient() 114 } 115 116 func (c *EnvCommandBase) NewAPIRoot() (api.Connection, error) { 117 // This is work in progress as we remove the EnvName from downstream code. 118 // We want to be able to specify the environment in a number of ways, one of 119 // which is the connection name on the client machine. 120 if c.envName == "" { 121 return nil, errors.Trace(ErrNoEnvironmentSpecified) 122 } 123 return juju.NewAPIFromName(c.envName) 124 } 125 126 // Config returns the configuration for the environment; obtaining bootstrap 127 // information from the API if necessary. If callers already have an active 128 // client API connection, it will be used. Otherwise, a new API connection will 129 // be used if necessary. 130 func (c *EnvCommandBase) Config(store configstore.Storage, client EnvironmentGetter) (*config.Config, error) { 131 if c.envName == "" { 132 return nil, errors.Trace(ErrNoEnvironmentSpecified) 133 } 134 cfg, _, err := environs.ConfigForName(c.envName, store) 135 if err == nil { 136 return cfg, nil 137 } else if !environs.IsEmptyConfig(err) { 138 return nil, errors.Trace(err) 139 } 140 141 if client == nil { 142 client, err = c.NewEnvironmentGetter() 143 if err != nil { 144 return nil, errors.Trace(err) 145 } 146 defer client.Close() 147 } 148 149 bootstrapCfg, err := client.EnvironmentGet() 150 if err != nil { 151 return nil, errors.Trace(err) 152 } 153 return config.New(config.NoDefaults, bootstrapCfg) 154 } 155 156 // ConnectionCredentials returns the credentials used to connect to the API for 157 // the specified environment. 158 func (c *EnvCommandBase) ConnectionCredentials() (configstore.APICredentials, error) { 159 // TODO: the user may soon be specified through the command line 160 // or through an environment setting, so return these when they are ready. 161 var emptyCreds configstore.APICredentials 162 if c.envName == "" { 163 return emptyCreds, errors.Trace(ErrNoEnvironmentSpecified) 164 } 165 info, err := ConnectionInfoForName(c.envName) 166 if err != nil { 167 return emptyCreds, errors.Trace(err) 168 } 169 return info.APICredentials(), nil 170 } 171 172 // ConnectionEndpoint returns the end point information used to 173 // connect to the API for the specified environment. 174 func (c *EnvCommandBase) ConnectionEndpoint(refresh bool) (configstore.APIEndpoint, error) { 175 // TODO: the endpoint information may soon be specified through the command line 176 // or through an environment setting, so return these when they are ready. 177 // NOTE: refresh when specified through command line should error. 178 var emptyEndpoint configstore.APIEndpoint 179 if c.envName == "" { 180 return emptyEndpoint, errors.Trace(ErrNoEnvironmentSpecified) 181 } 182 info, err := ConnectionInfoForName(c.envName) 183 if err != nil { 184 return emptyEndpoint, errors.Trace(err) 185 } 186 endpoint := info.APIEndpoint() 187 if !refresh && len(endpoint.Addresses) > 0 { 188 logger.Debugf("found cached addresses, not connecting to API server") 189 return endpoint, nil 190 } 191 192 // We need to connect to refresh our endpoint settings 193 // The side effect of connecting is that we update the store with new API information 194 refresher, err := endpointRefresher(c) 195 if err != nil { 196 return emptyEndpoint, err 197 } 198 refresher.Close() 199 200 info, err = ConnectionInfoForName(c.envName) 201 if err != nil { 202 return emptyEndpoint, err 203 } 204 return info.APIEndpoint(), nil 205 } 206 207 // ConnectionWriter defines the methods needed to write information about 208 // a given connection. This is a subset of the methods in the interface 209 // defined in configstore.EnvironInfo. 210 type ConnectionWriter interface { 211 Write() error 212 SetAPICredentials(configstore.APICredentials) 213 SetAPIEndpoint(configstore.APIEndpoint) 214 SetBootstrapConfig(map[string]interface{}) 215 Location() string 216 } 217 218 var endpointRefresher = func(c *EnvCommandBase) (io.Closer, error) { 219 return c.NewAPIRoot() 220 } 221 222 var getConfigStore = func() (configstore.Storage, error) { 223 store, err := configstore.Default() 224 if err != nil { 225 return nil, errors.Trace(err) 226 } 227 return store, nil 228 } 229 230 // ConnectionInfoForName reads the environment information for the named 231 // environment (envName) and returns it. 232 func ConnectionInfoForName(envName string) (configstore.EnvironInfo, error) { 233 store, err := getConfigStore() 234 if err != nil { 235 return nil, errors.Trace(err) 236 } 237 info, err := store.ReadInfo(envName) 238 if err != nil { 239 return nil, errors.Trace(err) 240 } 241 return info, nil 242 } 243 244 // ConnectionWriter returns an instance that is able to be used 245 // to record information about the connection. When the connection 246 // is determined through either command line parameters or environment 247 // variables, an error is returned. 248 func (c *EnvCommandBase) ConnectionWriter() (ConnectionWriter, error) { 249 // TODO: when accessing with just command line params or environment 250 // variables, this should error. 251 if c.envName == "" { 252 return nil, errors.Trace(ErrNoEnvironmentSpecified) 253 } 254 return ConnectionInfoForName(c.envName) 255 } 256 257 // CompatVersion returns the minimum CLI version 258 // that this command should be compatible with. 259 func (c *EnvCommandBase) CompatVersion() int { 260 if c.compatVerson != nil { 261 return *c.compatVerson 262 } 263 compatVerson := 1 264 val := os.Getenv(osenv.JujuCLIVersion) 265 if val != "" { 266 vers, err := strconv.Atoi(val) 267 if err != nil { 268 logger.Warningf("invalid %s value: %v", osenv.JujuCLIVersion, val) 269 } else { 270 compatVerson = vers 271 } 272 } 273 c.compatVerson = &compatVerson 274 return *c.compatVerson 275 } 276 277 // ConnectionName returns the name of the connection if there is one. 278 // It is possible that the name of the connection is empty if the 279 // connection information is supplied through command line arguments 280 // or environment variables. 281 func (c *EnvCommandBase) ConnectionName() string { 282 return c.envName 283 } 284 285 // Wrap wraps the specified EnvironCommand, returning a Command 286 // that proxies to each of the EnvironCommand methods. 287 func Wrap(c EnvironCommand) cmd.Command { 288 return &environCommandWrapper{EnvironCommand: c} 289 } 290 291 type environCommandWrapper struct { 292 EnvironCommand 293 envName string 294 } 295 296 func (w *environCommandWrapper) SetFlags(f *gnuflag.FlagSet) { 297 f.StringVar(&w.envName, "e", "", "juju environment to operate in") 298 f.StringVar(&w.envName, "environment", "", "") 299 w.EnvironCommand.SetFlags(f) 300 } 301 302 func (w *environCommandWrapper) Init(args []string) error { 303 if w.envName == "" { 304 // Look for the default. 305 defaultEnv, err := GetDefaultEnvironment() 306 if err != nil { 307 return err 308 } 309 w.envName = defaultEnv 310 } 311 w.SetEnvName(w.envName) 312 return w.EnvironCommand.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 EnvironmentGetter interface { 343 EnvironmentGet() (map[string]interface{}, error) 344 Close() error 345 } 346 347 // GetEnvironmentVersion retrieves the environment's agent-version 348 // value from an API client. 349 func GetEnvironmentVersion(client EnvironmentGetter) (version.Number, error) { 350 noVersion := version.Number{} 351 attrs, err := client.EnvironmentGet() 352 if err != nil { 353 return noVersion, errors.Annotate(err, "unable to retrieve environment config") 354 } 355 vi, found := attrs["agent-version"] 356 if !found { 357 return noVersion, errors.New("version not found in environment config") 358 } 359 vs, ok := vi.(string) 360 if !ok { 361 return noVersion, errors.New("invalid environment version type in config") 362 } 363 v, err := version.Parse(vs) 364 if err != nil { 365 return noVersion, errors.Annotate(err, "unable to parse environment version") 366 } 367 return v, nil 368 }