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