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