github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/commands/bootstrap.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils"
    16  	"github.com/juju/utils/featureflag"
    17  	"github.com/juju/version"
    18  	"gopkg.in/juju/charm.v6-unstable"
    19  	"launchpad.net/gnuflag"
    20  
    21  	apiblock "github.com/juju/juju/api/block"
    22  	"github.com/juju/juju/apiserver/params"
    23  	jujucloud "github.com/juju/juju/cloud"
    24  	"github.com/juju/juju/cmd/juju/block"
    25  	"github.com/juju/juju/cmd/juju/common"
    26  	"github.com/juju/juju/cmd/modelcmd"
    27  	"github.com/juju/juju/constraints"
    28  	"github.com/juju/juju/environs"
    29  	"github.com/juju/juju/environs/bootstrap"
    30  	"github.com/juju/juju/environs/config"
    31  	"github.com/juju/juju/feature"
    32  	"github.com/juju/juju/instance"
    33  	"github.com/juju/juju/juju"
    34  	"github.com/juju/juju/juju/osenv"
    35  	"github.com/juju/juju/jujuclient"
    36  	"github.com/juju/juju/network"
    37  	jujuversion "github.com/juju/juju/version"
    38  )
    39  
    40  // provisionalProviders is the names of providers that are hidden behind
    41  // feature flags.
    42  var provisionalProviders = map[string]string{
    43  	"vsphere": feature.VSphereProvider,
    44  }
    45  
    46  var usageBootstrapSummary = `
    47  Initializes a cloud environment.`[1:]
    48  
    49  var usageBootstrapDetails = `
    50  Initialization consists of creating an 'admin' model and provisioning a 
    51  machine to act as controller.
    52  Credentials are set beforehand and are distinct from any other 
    53  configuration (see `[1:] + "`juju add-credential`" + `).
    54  The 'admin' model typically does not run workloads. It should remain
    55  pristine to run and manage Juju's own infrastructure for the corresponding
    56  cloud. Additional (hosted) models should be created with ` + "`juju create-\nmodel`" + ` for workload purposes.
    57  Note that a 'default' model is also created and becomes the current model
    58  of the environment once the command completes. It can be discarded if
    59  other models are created.
    60  If '--bootstrap-constraints' is used, its values will also apply to any
    61  future controllers provisioned for high availability (HA).
    62  If '--constraints' is used, its values will be set as the default 
    63  constraints for all future workload machines in the model, exactly as if 
    64  the constraints were set with ` + "`juju set-model-constraints`" + `.
    65  It is possible to override constraints and the automatic machine selection
    66  algorithm by assigning a "placement directive" via the '--to' option. This
    67  dictates what machine to use for the controller. This would typically be 
    68  used with the MAAS provider ('--to <host>.maas').
    69  You can change the default timeout and retry delays used during the 
    70  bootstrap by changing the following settings in your configuration file
    71  (all values represent number of seconds):
    72      # How long to wait for a connection to the controller
    73      bootstrap-timeout: 600 # default: 10 minutes
    74      # How long to wait between connection attempts to a controller 
    75  address.
    76      bootstrap-retry-delay: 5 # default: 5 seconds
    77      # How often to refresh controller addresses from the API server.
    78      bootstrap-addresses-delay: 10 # default: 10 seconds
    79  Private clouds may need to specify their own custom image metadata and
    80  tools/agent. Use '--metadata-source' whose value is a local directory.
    81  The value of '--agent-version' will become the default tools version to
    82  use in all models for this controller. The full binary version is accepted
    83  (e.g.: 2.0.1-xenial-amd64) but only the numeric version (e.g.: 2.0.1) is
    84  used. Otherwise, by default, the version used is that of the client.
    85  
    86  Examples:
    87      juju bootstrap mycontroller google
    88      juju bootstrap --config=~/config-rs.yaml mycontroller rackspace
    89      juju bootstrap --config agent-version=1.25.3 mycontroller aws
    90      juju bootstrap --config bootstrap-timeout=1200 mycontroller azure
    91  
    92  See also: 
    93      add-credentials
    94      create-model
    95      set-constraints`
    96  
    97  // defaultHostedModelName is the name of the hosted model created in each
    98  // controller for deploying workloads to, in addition to the "admin" model.
    99  const defaultHostedModelName = "default"
   100  
   101  func newBootstrapCommand() cmd.Command {
   102  	return modelcmd.Wrap(
   103  		&bootstrapCommand{},
   104  		modelcmd.ModelSkipFlags, modelcmd.ModelSkipDefault,
   105  	)
   106  }
   107  
   108  // bootstrapCommand is responsible for launching the first machine in a juju
   109  // environment, and setting up everything necessary to continue working.
   110  type bootstrapCommand struct {
   111  	modelcmd.ModelCommandBase
   112  
   113  	Constraints           constraints.Value
   114  	BootstrapConstraints  constraints.Value
   115  	BootstrapSeries       string
   116  	BootstrapImage        string
   117  	UploadTools           bool
   118  	MetadataSource        string
   119  	Placement             string
   120  	KeepBrokenEnvironment bool
   121  	AutoUpgrade           bool
   122  	AgentVersionParam     string
   123  	AgentVersion          *version.Number
   124  	config                common.ConfigFlag
   125  
   126  	controllerName  string
   127  	hostedModelName string
   128  	CredentialName  string
   129  	Cloud           string
   130  	Region          string
   131  	noGUI           bool
   132  }
   133  
   134  func (c *bootstrapCommand) Info() *cmd.Info {
   135  	return &cmd.Info{
   136  		Name:    "bootstrap",
   137  		Args:    "<controller name> <cloud name>[/region]",
   138  		Purpose: usageBootstrapSummary,
   139  		Doc:     usageBootstrapDetails,
   140  	}
   141  }
   142  
   143  func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
   144  	f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "Set model constraints")
   145  	f.Var(constraints.ConstraintsValue{Target: &c.BootstrapConstraints}, "bootstrap-constraints", "Specify bootstrap machine constraints")
   146  	f.StringVar(&c.BootstrapSeries, "bootstrap-series", "", "Specify the series of the bootstrap machine")
   147  	if featureflag.Enabled(feature.ImageMetadata) {
   148  		f.StringVar(&c.BootstrapImage, "bootstrap-image", "", "Specify the image of the bootstrap machine")
   149  	}
   150  	f.BoolVar(&c.UploadTools, "upload-tools", false, "Upload local version of tools before bootstrapping")
   151  	f.StringVar(&c.MetadataSource, "metadata-source", "", "Local path to use as tools and/or metadata source")
   152  	f.StringVar(&c.Placement, "to", "", "Placement directive indicating an instance to bootstrap")
   153  	f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "Do not destroy the model if bootstrap fails")
   154  	f.BoolVar(&c.AutoUpgrade, "auto-upgrade", false, "Upgrade to the latest patch release tools on first bootstrap")
   155  	f.StringVar(&c.AgentVersionParam, "agent-version", "", "Version of tools to use for Juju agents")
   156  	f.StringVar(&c.CredentialName, "credential", "", "Credentials to use when bootstrapping")
   157  	f.Var(&c.config, "config", "Specify a controller configuration file, or one or more configuration\n    options\n    (--config config.yaml [--config key=value ...])")
   158  	f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "Name of the default hosted model for the controller")
   159  	f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "Name of the default hosted model for the controller")
   160  	f.BoolVar(&c.noGUI, "no-gui", false, "Do not install the Juju GUI in the controller when bootstrapping")
   161  }
   162  
   163  func (c *bootstrapCommand) Init(args []string) (err error) {
   164  	if c.AgentVersionParam != "" && c.UploadTools {
   165  		return fmt.Errorf("--agent-version and --upload-tools can't be used together")
   166  	}
   167  	if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) {
   168  		return errors.NotValidf("series %q", c.BootstrapSeries)
   169  	}
   170  	if c.BootstrapImage != "" {
   171  		if c.BootstrapSeries == "" {
   172  			return errors.Errorf("--bootstrap-image must be used with --bootstrap-series")
   173  		}
   174  		cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints)
   175  		if err != nil {
   176  			return errors.Trace(err)
   177  		}
   178  		if !cons.HasArch() {
   179  			return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture")
   180  		}
   181  	}
   182  
   183  	// Parse the placement directive. Bootstrap currently only
   184  	// supports provider-specific placement directives.
   185  	if c.Placement != "" {
   186  		_, err = instance.ParsePlacement(c.Placement)
   187  		if err != instance.ErrPlacementScopeMissing {
   188  			// We only support unscoped placement directives for bootstrap.
   189  			return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement)
   190  		}
   191  	}
   192  	if !c.AutoUpgrade {
   193  		// With no auto upgrade chosen, we default to the version matching the bootstrap client.
   194  		vers := jujuversion.Current
   195  		c.AgentVersion = &vers
   196  	}
   197  	if c.AgentVersionParam != "" {
   198  		if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil {
   199  			c.AgentVersion = &vers.Number
   200  		} else if vers, err := version.Parse(c.AgentVersionParam); err == nil {
   201  			c.AgentVersion = &vers
   202  		} else {
   203  			return err
   204  		}
   205  	}
   206  	if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) {
   207  		return fmt.Errorf("requested agent version major.minor mismatch")
   208  	}
   209  
   210  	// The user must specify two positional arguments: the controller name,
   211  	// and the cloud name (optionally with region specified).
   212  	if len(args) < 2 {
   213  		return errors.New("controller name and cloud name are required")
   214  	}
   215  	c.controllerName = bootstrappedControllerName(args[0])
   216  	c.Cloud = args[1]
   217  	if i := strings.IndexRune(c.Cloud, '/'); i > 0 {
   218  		c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:]
   219  	}
   220  	return cmd.CheckEmpty(args[2:])
   221  }
   222  
   223  var bootstrappedControllerName = func(controllerName string) string {
   224  	return fmt.Sprintf("local.%s", controllerName)
   225  }
   226  
   227  // BootstrapInterface provides bootstrap functionality that Run calls to support cleaner testing.
   228  type BootstrapInterface interface {
   229  	Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args bootstrap.BootstrapParams) error
   230  }
   231  
   232  type bootstrapFuncs struct{}
   233  
   234  func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error {
   235  	return bootstrap.Bootstrap(ctx, env, args)
   236  }
   237  
   238  var getBootstrapFuncs = func() BootstrapInterface {
   239  	return &bootstrapFuncs{}
   240  }
   241  
   242  var (
   243  	environsPrepare = environs.Prepare
   244  	environsDestroy = environs.Destroy
   245  )
   246  
   247  var ambiguousCredentialError = errors.New(`
   248  more than one credential detected
   249  run juju autoload-credentials and specify a credential using the --credential argument`[1:],
   250  )
   251  
   252  // Run connects to the environment specified on the command line and bootstraps
   253  // a juju in that environment if none already exists. If there is as yet no environments.yaml file,
   254  // the user is informed how to create one.
   255  func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
   256  	bootstrapFuncs := getBootstrapFuncs()
   257  
   258  	// Get the cloud definition identified by c.Cloud. If c.Cloud does not
   259  	// identify a cloud in clouds.yaml, but is the name of a provider, and
   260  	// that provider implements environs.CloudRegionDetector, we'll
   261  	// synthesise a Cloud structure with the detected regions and no auth-
   262  	// types.
   263  	cloud, err := jujucloud.CloudByName(c.Cloud)
   264  	if errors.IsNotFound(err) {
   265  		ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud)
   266  		provider, err := environs.Provider(c.Cloud)
   267  		if errors.IsNotFound(err) {
   268  			return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds"))
   269  		} else if err != nil {
   270  			return errors.Trace(err)
   271  		}
   272  		detector, ok := provider.(environs.CloudRegionDetector)
   273  		if !ok {
   274  			ctx.Verbosef(
   275  				"provider %q does not support detecting regions",
   276  				c.Cloud,
   277  			)
   278  			return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds"))
   279  		}
   280  		regions, err := detector.DetectRegions()
   281  		if err != nil && !errors.IsNotFound(err) {
   282  			// It's not an error to have no regions.
   283  			return errors.Annotatef(err,
   284  				"detecting regions for %q cloud provider",
   285  				c.Cloud,
   286  			)
   287  		}
   288  		cloud = &jujucloud.Cloud{
   289  			Type:    c.Cloud,
   290  			Regions: regions,
   291  		}
   292  	} else if err != nil {
   293  		return errors.Trace(err)
   294  	}
   295  	if err := checkProviderType(cloud.Type); errors.IsNotFound(err) {
   296  		// This error will get handled later.
   297  	} else if err != nil {
   298  		return errors.Trace(err)
   299  	}
   300  
   301  	// Get the credentials and region name.
   302  	store := c.ClientStore()
   303  	credential, credentialName, regionName, err := modelcmd.GetCredentials(
   304  		store, c.Region, c.CredentialName, c.Cloud, cloud.Type,
   305  	)
   306  	if errors.IsNotFound(err) && c.CredentialName == "" {
   307  		// No credential was explicitly specified, and no credential
   308  		// was found in credentials.yaml; have the provider detect
   309  		// credentials from the environment.
   310  		ctx.Verbosef("no credentials found, checking environment")
   311  		detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type)
   312  		if errors.Cause(err) == modelcmd.ErrMultipleCredentials {
   313  			return ambiguousCredentialError
   314  		} else if err != nil {
   315  			return errors.Trace(err)
   316  		}
   317  		// We have one credential so extract it from the map.
   318  		var oneCredential jujucloud.Credential
   319  		for _, oneCredential = range detected.AuthCredentials {
   320  		}
   321  		credential = &oneCredential
   322  		regionName = c.Region
   323  		if regionName == "" {
   324  			regionName = detected.DefaultRegion
   325  		}
   326  		logger.Tracef("authenticating with region %q and %v", regionName, credential)
   327  	} else if err != nil {
   328  		return errors.Trace(err)
   329  	}
   330  
   331  	region, err := getRegion(cloud, c.Cloud, regionName)
   332  	if err != nil {
   333  		return errors.Trace(err)
   334  	}
   335  
   336  	hostedModelUUID, err := utils.NewUUID()
   337  	if err != nil {
   338  		return errors.Trace(err)
   339  	}
   340  	controllerUUID, err := utils.NewUUID()
   341  	if err != nil {
   342  		return errors.Trace(err)
   343  	}
   344  
   345  	// Create an environment config from the cloud and credentials.
   346  	configAttrs := map[string]interface{}{
   347  		"type":                   cloud.Type,
   348  		"name":                   environs.ControllerModelName,
   349  		config.UUIDKey:           controllerUUID.String(),
   350  		config.ControllerUUIDKey: controllerUUID.String(),
   351  	}
   352  	userConfigAttrs, err := c.config.ReadAttrs(ctx)
   353  	if err != nil {
   354  		return errors.Trace(err)
   355  	}
   356  	for k, v := range userConfigAttrs {
   357  		configAttrs[k] = v
   358  	}
   359  	logger.Debugf("preparing controller with config: %v", configAttrs)
   360  
   361  	// Read existing current controller, account, model so we can clean up on error.
   362  	var oldCurrentController string
   363  	oldCurrentController, err = modelcmd.ReadCurrentController()
   364  	if err != nil {
   365  		return errors.Annotate(err, "error reading current controller")
   366  	}
   367  
   368  	defer func() {
   369  		if resultErr == nil || errors.IsAlreadyExists(resultErr) {
   370  			return
   371  		}
   372  		if oldCurrentController != "" {
   373  			if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil {
   374  				logger.Warningf(
   375  					"cannot reset current controller to %q: %v",
   376  					oldCurrentController, err,
   377  				)
   378  			}
   379  		}
   380  		if err := store.RemoveController(c.controllerName); err != nil {
   381  			logger.Warningf(
   382  				"cannot destroy newly created controller %q details: %v",
   383  				c.controllerName, err,
   384  			)
   385  		}
   386  	}()
   387  
   388  	environ, err := environsPrepare(
   389  		modelcmd.BootstrapContext(ctx), store,
   390  		environs.PrepareParams{
   391  			BaseConfig:           configAttrs,
   392  			ControllerName:       c.controllerName,
   393  			CloudName:            c.Cloud,
   394  			CloudRegion:          region.Name,
   395  			CloudEndpoint:        region.Endpoint,
   396  			CloudStorageEndpoint: region.StorageEndpoint,
   397  			Credential:           *credential,
   398  			CredentialName:       credentialName,
   399  		},
   400  	)
   401  	if err != nil {
   402  		return errors.Trace(err)
   403  	}
   404  
   405  	// Set the current model to the initial hosted model.
   406  	accountName, err := store.CurrentAccount(c.controllerName)
   407  	if err != nil {
   408  		return errors.Trace(err)
   409  	}
   410  	if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{
   411  		hostedModelUUID.String(),
   412  	}); err != nil {
   413  		return errors.Trace(err)
   414  	}
   415  	if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil {
   416  		return errors.Trace(err)
   417  	}
   418  
   419  	// Set the current controller so "juju status" can be run while
   420  	// bootstrapping is underway.
   421  	if err := modelcmd.WriteCurrentController(c.controllerName); err != nil {
   422  		return errors.Trace(err)
   423  	}
   424  
   425  	cloudRegion := c.Cloud
   426  	if region.Name != "" {
   427  		cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name)
   428  	}
   429  	ctx.Infof(
   430  		"Creating Juju controller %q on %s",
   431  		c.controllerName, cloudRegion,
   432  	)
   433  
   434  	// If we error out for any reason, clean up the environment.
   435  	defer func() {
   436  		if resultErr != nil {
   437  			if c.KeepBrokenEnvironment {
   438  				logger.Warningf(`
   439  bootstrap failed but --keep-broken was specified so model is not being destroyed.
   440  When you are finished diagnosing the problem, remember to run juju destroy-model --force
   441  to clean up the model.`[1:])
   442  			} else {
   443  				handleBootstrapError(ctx, resultErr, func() error {
   444  					return environsDestroy(
   445  						c.controllerName, environ, store,
   446  					)
   447  				})
   448  			}
   449  		}
   450  	}()
   451  
   452  	// Block interruption during bootstrap. Providers may also
   453  	// register for interrupt notification so they can exit early.
   454  	interrupted := make(chan os.Signal, 1)
   455  	defer close(interrupted)
   456  	ctx.InterruptNotify(interrupted)
   457  	defer ctx.StopInterruptNotify(interrupted)
   458  	go func() {
   459  		for _ = range interrupted {
   460  			ctx.Infof("Interrupt signalled: waiting for bootstrap to exit")
   461  		}
   462  	}()
   463  
   464  	// If --metadata-source is specified, override the default tools metadata source so
   465  	// SyncTools can use it, and also upload any image metadata.
   466  	var metadataDir string
   467  	if c.MetadataSource != "" {
   468  		metadataDir = ctx.AbsPath(c.MetadataSource)
   469  	}
   470  
   471  	// Merge environ and bootstrap-specific constraints.
   472  	constraintsValidator, err := environ.ConstraintsValidator()
   473  	if err != nil {
   474  		return errors.Trace(err)
   475  	}
   476  	bootstrapConstraints, err := constraintsValidator.Merge(
   477  		c.Constraints, c.BootstrapConstraints,
   478  	)
   479  	if err != nil {
   480  		return errors.Trace(err)
   481  	}
   482  	logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints)
   483  
   484  	hostedModelConfig := map[string]interface{}{
   485  		"name":         c.hostedModelName,
   486  		config.UUIDKey: hostedModelUUID.String(),
   487  	}
   488  
   489  	// We copy across any user supplied attributes to the hosted model config.
   490  	// But only if the attributes have not been removed from the controller
   491  	// model config as part of preparing the controller model.
   492  	controllerConfigAttrs := environ.Config().AllAttrs()
   493  	for k, v := range userConfigAttrs {
   494  		if _, ok := controllerConfigAttrs[k]; ok {
   495  			hostedModelConfig[k] = v
   496  		}
   497  	}
   498  	// Ensure that certain config attributes are not included in the hosted
   499  	// model config. These attributes may be modified during bootstrap; by
   500  	// removing them from this map, we ensure the modified values are
   501  	// inherited.
   502  	delete(hostedModelConfig, config.AuthKeysConfig)
   503  	delete(hostedModelConfig, config.AgentVersionKey)
   504  
   505  	// Check whether the Juju GUI must be installed in the controller.
   506  	// Leaving this value empty means no GUI will be installed.
   507  	var guiDataSourceBaseURL string
   508  	if !c.noGUI {
   509  		guiDataSourceBaseURL = common.GUIDataSourceBaseURL()
   510  	}
   511  
   512  	err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{
   513  		ModelConstraints:     c.Constraints,
   514  		BootstrapConstraints: bootstrapConstraints,
   515  		BootstrapSeries:      c.BootstrapSeries,
   516  		BootstrapImage:       c.BootstrapImage,
   517  		Placement:            c.Placement,
   518  		UploadTools:          c.UploadTools,
   519  		AgentVersion:         c.AgentVersion,
   520  		MetadataDir:          metadataDir,
   521  		HostedModelConfig:    hostedModelConfig,
   522  		GUIDataSourceBaseURL: guiDataSourceBaseURL,
   523  	})
   524  	if err != nil {
   525  		return errors.Annotate(err, "failed to bootstrap model")
   526  	}
   527  
   528  	if err := c.SetModelName(c.hostedModelName); err != nil {
   529  		return errors.Trace(err)
   530  	}
   531  
   532  	err = c.setBootstrapEndpointAddress(environ)
   533  	if err != nil {
   534  		return errors.Annotate(err, "saving bootstrap endpoint address")
   535  	}
   536  
   537  	// To avoid race conditions when running scripted bootstraps, wait
   538  	// for the controller's machine agent to be ready to accept commands
   539  	// before exiting this bootstrap command.
   540  	return c.waitForAgentInitialisation(ctx)
   541  }
   542  
   543  // getRegion returns the cloud.Region to use, based on the specified
   544  // region name, and the region name selected if none was specified.
   545  //
   546  // If no region name is specified, and there is at least one region,
   547  // we use the first region in the list.
   548  //
   549  // If no region name is specified, and there are no regions at all,
   550  // then we synthesise a region from the cloud's endpoint information
   551  // and just pass this on to the provider.
   552  func getRegion(cloud *jujucloud.Cloud, cloudName, regionName string) (jujucloud.Region, error) {
   553  	if len(cloud.Regions) == 0 {
   554  		// The cloud does not specify regions, so assume
   555  		// that the cloud provider does not have a concept
   556  		// of regions, or has no pre-defined regions, and
   557  		// defer validation to the provider.
   558  		region := jujucloud.Region{
   559  			regionName,
   560  			cloud.Endpoint,
   561  			cloud.StorageEndpoint,
   562  		}
   563  		return region, nil
   564  	}
   565  	if regionName == "" {
   566  		// No region was specified, use the first region in the list.
   567  		return cloud.Regions[0], nil
   568  	}
   569  	for _, region := range cloud.Regions {
   570  		// Do a case-insensitive comparison
   571  		if strings.EqualFold(region.Name, regionName) {
   572  			return region, nil
   573  		}
   574  	}
   575  	return jujucloud.Region{}, errors.NewNotFound(nil, fmt.Sprintf(
   576  		"region %q in cloud %q not found (expected one of %q)\nalternatively, try %q",
   577  		regionName, cloudName, cloudRegionNames(cloud), "juju update-clouds",
   578  	))
   579  }
   580  
   581  func cloudRegionNames(cloud *jujucloud.Cloud) []string {
   582  	var regionNames []string
   583  	for _, region := range cloud.Regions {
   584  		regionNames = append(regionNames, region.Name)
   585  	}
   586  	return regionNames
   587  }
   588  
   589  var (
   590  	bootstrapReadyPollDelay = 1 * time.Second
   591  	bootstrapReadyPollCount = 60
   592  	blockAPI                = getBlockAPI
   593  )
   594  
   595  // getBlockAPI returns a block api for listing blocks.
   596  func getBlockAPI(c *modelcmd.ModelCommandBase) (block.BlockListAPI, error) {
   597  	root, err := c.NewAPIRoot()
   598  	if err != nil {
   599  		return nil, err
   600  	}
   601  	return apiblock.NewClient(root), nil
   602  }
   603  
   604  // tryAPI attempts to open the API and makes a trivial call
   605  // to check if the API is available yet.
   606  func (c *bootstrapCommand) tryAPI() error {
   607  	client, err := blockAPI(&c.ModelCommandBase)
   608  	if err == nil {
   609  		_, err = client.List()
   610  		closeErr := client.Close()
   611  		if closeErr != nil {
   612  			logger.Debugf("Error closing client: %v", closeErr)
   613  		}
   614  	}
   615  	return err
   616  }
   617  
   618  // waitForAgentInitialisation polls the bootstrapped controller with a read-only
   619  // command which will fail until the controller is fully initialised.
   620  // TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose.
   621  func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) error {
   622  	attempts := utils.AttemptStrategy{
   623  		Min:   bootstrapReadyPollCount,
   624  		Delay: bootstrapReadyPollDelay,
   625  	}
   626  	var (
   627  		apiAttempts int
   628  		err         error
   629  	)
   630  
   631  	apiAttempts = 1
   632  	for attempt := attempts.Start(); attempt.Next(); apiAttempts++ {
   633  		err = c.tryAPI()
   634  		if err == nil {
   635  			ctx.Infof("Bootstrap complete, %s now available.", c.controllerName)
   636  			break
   637  		}
   638  		// As the API server is coming up, it goes through a number of steps.
   639  		// Initially the upgrade steps run, but the api server allows some
   640  		// calls to be processed during the upgrade, but not the list blocks.
   641  		// Logins are also blocked during space discovery.
   642  		// It is also possible that the underlying database causes connections
   643  		// to be dropped as it is initialising, or reconfiguring. These can
   644  		// lead to EOF or "connection is shut down" error messages. We skip
   645  		// these too, hoping that things come back up before the end of the
   646  		// retry poll count.
   647  		errorMessage := errors.Cause(err).Error()
   648  		switch {
   649  		case errors.Cause(err) == io.EOF,
   650  			strings.HasSuffix(errorMessage, "connection is shut down"),
   651  			strings.Contains(errorMessage, "spaces are still being discovered"):
   652  			ctx.Infof("Waiting for API to become available")
   653  			continue
   654  		case params.ErrCode(err) == params.CodeUpgradeInProgress:
   655  			ctx.Infof("Waiting for API to become available: %v", err)
   656  			continue
   657  		}
   658  		break
   659  	}
   660  	return errors.Annotatef(err, "unable to contact api server after %d attempts", apiAttempts)
   661  }
   662  
   663  // checkProviderType ensures the provider type is okay.
   664  func checkProviderType(envType string) error {
   665  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
   666  	flag, ok := provisionalProviders[envType]
   667  	if ok && !featureflag.Enabled(flag) {
   668  		msg := `the %q provider is provisional in this version of Juju. To use it anyway, set JUJU_DEV_FEATURE_FLAGS="%s" in your shell model`
   669  		return errors.Errorf(msg, envType, flag)
   670  	}
   671  	return nil
   672  }
   673  
   674  // handleBootstrapError is called to clean up if bootstrap fails.
   675  func handleBootstrapError(ctx *cmd.Context, err error, cleanup func() error) {
   676  	ch := make(chan os.Signal, 1)
   677  	ctx.InterruptNotify(ch)
   678  	defer ctx.StopInterruptNotify(ch)
   679  	defer close(ch)
   680  	go func() {
   681  		for _ = range ch {
   682  			fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap")
   683  		}
   684  	}()
   685  	if err := cleanup(); err != nil {
   686  		logger.Errorf("error cleaning up: %v", err)
   687  	}
   688  }
   689  
   690  var allInstances = func(environ environs.Environ) ([]instance.Instance, error) {
   691  	return environ.AllInstances()
   692  }
   693  
   694  // setBootstrapEndpointAddress writes the API endpoint address of the
   695  // bootstrap server into the connection information. This should only be run
   696  // once directly after Bootstrap. It assumes that there is just one instance
   697  // in the environment - the bootstrap instance.
   698  func (c *bootstrapCommand) setBootstrapEndpointAddress(environ environs.Environ) error {
   699  	instances, err := allInstances(environ)
   700  	if err != nil {
   701  		return errors.Trace(err)
   702  	}
   703  	length := len(instances)
   704  	if length == 0 {
   705  		return errors.Errorf("found no instances, expected at least one")
   706  	}
   707  	if length > 1 {
   708  		logger.Warningf("expected one instance, got %d", length)
   709  	}
   710  	bootstrapInstance := instances[0]
   711  
   712  	// Don't use c.ConnectionEndpoint as it attempts to contact the state
   713  	// server if no addresses are found in connection info.
   714  	netAddrs, err := bootstrapInstance.Addresses()
   715  	if err != nil {
   716  		return errors.Annotate(err, "failed to get bootstrap instance addresses")
   717  	}
   718  	cfg := environ.Config()
   719  	apiPort := cfg.APIPort()
   720  	apiHostPorts := network.AddressesWithPort(netAddrs, apiPort)
   721  	return juju.UpdateControllerAddresses(c.ClientStore(), c.controllerName, nil, apiHostPorts...)
   722  }