// Run checks to see if there is already an environments.yaml file. In one does not exist already, // a boilerplate version is created so that the user can edit it to get started. func (c *InitCommand) Run(context *cmd.Context) error { out := context.Stdout config := environs.BoilerplateConfig() if c.Show { fmt.Fprint(out, config) return nil } _, err := environs.ReadEnvirons("") if err == nil && !c.WriteFile { return errJujuEnvExists } if err != nil && !environs.IsNoEnv(err) { return err } filename, err := environs.WriteEnvirons("", config) if err != nil { return fmt.Errorf("A boilerplate environment configuration file could not be created: %s", err.Error()) } fmt.Fprintf(out, "A boilerplate environment configuration file has been written to %s.\n", filename) fmt.Fprint(out, "Edit the file to configure your juju environment and run bootstrap.\n") return nil }
// newAPIFromStore implements the bulk of NewAPIClientFromName // but is separate for testing purposes. func newAPIFromStore(envName string, store configstore.Storage, apiOpen apiOpenFunc) (apiState, error) { // Try to read the default environment configuration file. // If it doesn't exist, we carry on in case // there's some environment info for that environment. // This enables people to copy environment files // into their .juju/environments directory and have // them be directly useful with no further configuration changes. envs, err := environs.ReadEnvirons("") if err == nil { if envName == "" { envName = envs.Default } if envName == "" { return nil, fmt.Errorf("no default environment found") } } else if !environs.IsNoEnv(err) { return nil, err } // Try to connect to the API concurrently using two different // possible sources of truth for the API endpoint. Our // preference is for the API endpoint cached in the API info, // because we know that without needing to access any remote // provider. However, the addresses stored there may no longer // be current (and the network connection may take a very long // time to time out) so we also try to connect using information // found from the provider. We only start to make that // connection after some suitable delay, so that in the // hopefully usual case, we will make the connection to the API // and never hit the provider. By preference we use provider // attributes from the config store, but for backward // compatibility reasons, we fall back to information from // ReadEnvirons if that does not exist. chooseError := func(err0, err1 error) error { if err0 == nil { return err1 } if errorImportance(err0) < errorImportance(err1) { err0, err1 = err1, err0 } logger.Warningf("discarding API open error: %v", err1) return err0 } try := parallel.NewTry(0, chooseError) info, err := store.ReadInfo(envName) if err != nil && !errors.IsNotFound(err) { return nil, err } var delay time.Duration if info != nil && len(info.APIEndpoint().Addresses) > 0 { logger.Debugf("trying cached API connection settings") try.Start(func(stop <-chan struct{}) (io.Closer, error) { return apiInfoConnect(store, info, apiOpen, stop) }) // Delay the config connection until we've spent // some time trying to connect to the cached info. delay = providerConnectDelay } else { logger.Debugf("no cached API connection settings found") } try.Start(func(stop <-chan struct{}) (io.Closer, error) { cfg, err := getConfig(info, envs, envName) if err != nil { return nil, err } return apiConfigConnect(cfg, apiOpen, stop, delay) }) try.Close() val0, err := try.Result() if err != nil { if ierr, ok := err.(*infoConnectError); ok { // lose error encapsulation: err = ierr.error } return nil, err } st := val0.(apiState) // Even though we are about to update API addresses based on // APIHostPorts in cacheChangedAPIAddresses, we first cache the // addresses based on the provider lookup. This is because older API // servers didn't return their HostPort information on Login, and we // still want to cache our connection information to them. if cachedInfo, ok := st.(apiStateCachedInfo); ok { st = cachedInfo.apiState if cachedInfo.cachedInfo != nil && info != nil { // Cache the connection settings only if we used the // environment config, but any errors are just logged // as warnings, because they're not fatal. err = cacheAPIInfo(info, cachedInfo.cachedInfo) if err != nil { logger.Warningf("cannot cache API connection settings: %v", err.Error()) } else { logger.Infof("updated API connection settings cache") } } } // Update API addresses if they've changed. Error is non-fatal. if localerr := cacheChangedAPIAddresses(info, st); localerr != nil { logger.Warningf("cannot failed to cache API addresses: %v", localerr) } return st, nil }