github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"bufio"
     8  	"fmt"
     9  	"os"
    10  	"os/user"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/gnuflag"
    17  	"github.com/juju/schema"
    18  	"github.com/juju/utils"
    19  	"github.com/juju/utils/featureflag"
    20  	"github.com/juju/version"
    21  	"gopkg.in/juju/charm.v6-unstable"
    22  
    23  	jujucloud "github.com/juju/juju/cloud"
    24  	"github.com/juju/juju/cmd/juju/common"
    25  	"github.com/juju/juju/cmd/modelcmd"
    26  	"github.com/juju/juju/constraints"
    27  	"github.com/juju/juju/controller"
    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/environs/sync"
    32  	"github.com/juju/juju/feature"
    33  	"github.com/juju/juju/instance"
    34  	"github.com/juju/juju/juju/osenv"
    35  	"github.com/juju/juju/jujuclient"
    36  	jujuversion "github.com/juju/juju/version"
    37  )
    38  
    39  // provisionalProviders is the names of providers that are hidden behind
    40  // feature flags.
    41  var provisionalProviders = map[string]string{
    42  	"vsphere": feature.VSphereProvider,
    43  }
    44  
    45  var usageBootstrapSummary = `
    46  Initializes a cloud environment.`[1:]
    47  
    48  var usageBootstrapDetails = `
    49  Used without arguments, bootstrap will step you through the process of
    50  initializing a Juju cloud environment. Initialization consists of creating
    51  a 'controller' model and provisioning a machine to act as controller.
    52  
    53  We recommend you call your controller ‘username-region’ e.g. ‘fred-us-east-1’
    54  See --clouds for a list of clouds and credentials.
    55  See --regions <cloud> for a list of available regions for a given cloud.
    56  
    57  Credentials are set beforehand and are distinct from any other
    58  configuration (see `[1:] + "`juju add-credential`" + `).
    59  The 'controller' model typically does not run workloads. It should remain
    60  pristine to run and manage Juju's own infrastructure for the corresponding
    61  cloud. Additional (hosted) models should be created with ` + "`juju create-\nmodel`" + ` for workload purposes.
    62  Note that a 'default' model is also created and becomes the current model
    63  of the environment once the command completes. It can be discarded if
    64  other models are created.
    65  
    66  If '--bootstrap-constraints' is used, its values will also apply to any
    67  future controllers provisioned for high availability (HA).
    68  
    69  If '--constraints' is used, its values will be set as the default
    70  constraints for all future workload machines in the model, exactly as if
    71  the constraints were set with ` + "`juju set-model-constraints`" + `.
    72  
    73  It is possible to override constraints and the automatic machine selection
    74  algorithm by assigning a "placement directive" via the '--to' option. This
    75  dictates what machine to use for the controller. This would typically be
    76  used with the MAAS provider ('--to <host>.maas').
    77  
    78  You can change the default timeout and retry delays used during the
    79  bootstrap by changing the following settings in your configuration
    80  (all values represent number of seconds):
    81      # How long to wait for a connection to the controller
    82      bootstrap-timeout: 600 # default: 10 minutes
    83      # How long to wait between connection attempts to a controller
    84  address.
    85      bootstrap-retry-delay: 5 # default: 5 seconds
    86      # How often to refresh controller addresses from the API server.
    87      bootstrap-addresses-delay: 10 # default: 10 seconds
    88  
    89  Private clouds may need to specify their own custom image metadata and
    90  tools/agent. Use '--metadata-source' whose value is a local directory.
    91  The value of '--agent-version' will become the default tools version to
    92  use in all models for this controller. The full binary version is accepted
    93  (e.g.: 2.0.1-xenial-amd64) but only the numeric version (e.g.: 2.0.1) is
    94  used. Otherwise, by default, the version used is that of the client.
    95  
    96  Examples:
    97      juju bootstrap
    98      juju bootstrap --clouds
    99      juju bootstrap --regions aws
   100      juju bootstrap joe-us-east1 google
   101      juju bootstrap --config=~/config-rs.yaml joe-syd rackspace
   102      juju bootstrap --config agent-version=1.25.3 joe-us-east-1 aws
   103      juju bootstrap --config bootstrap-timeout=1200 joe-eastus azure
   104  
   105  See also:
   106      add-credentials
   107      add-model
   108      set-constraints`
   109  
   110  // defaultHostedModelName is the name of the hosted model created in each
   111  // controller for deploying workloads to, in addition to the "controller" model.
   112  const defaultHostedModelName = "default"
   113  
   114  func newBootstrapCommand() cmd.Command {
   115  	return modelcmd.Wrap(
   116  		&bootstrapCommand{},
   117  		modelcmd.WrapSkipModelFlags, modelcmd.WrapSkipDefaultModel,
   118  	)
   119  }
   120  
   121  // bootstrapCommand is responsible for launching the first machine in a juju
   122  // environment, and setting up everything necessary to continue working.
   123  type bootstrapCommand struct {
   124  	modelcmd.ModelCommandBase
   125  
   126  	Constraints             constraints.Value
   127  	ConstraintsStr          string
   128  	BootstrapConstraints    constraints.Value
   129  	BootstrapConstraintsStr string
   130  	BootstrapSeries         string
   131  	BootstrapImage          string
   132  	BuildAgent              bool
   133  	MetadataSource          string
   134  	Placement               string
   135  	KeepBrokenEnvironment   bool
   136  	AutoUpgrade             bool
   137  	AgentVersionParam       string
   138  	AgentVersion            *version.Number
   139  	ForceAPIPort            bool
   140  	config                  common.ConfigFlag
   141  
   142  	showClouds          bool
   143  	showRegionsForCloud string
   144  	controllerName      string
   145  	hostedModelName     string
   146  	CredentialName      string
   147  	Cloud               string
   148  	Region              string
   149  	noGUI               bool
   150  	interactive         bool
   151  }
   152  
   153  func (c *bootstrapCommand) Info() *cmd.Info {
   154  	return &cmd.Info{
   155  		Name:    "bootstrap",
   156  		Args:    "<controller name> <cloud name>[/region]",
   157  		Purpose: usageBootstrapSummary,
   158  		Doc:     usageBootstrapDetails,
   159  	}
   160  }
   161  
   162  func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
   163  	c.ModelCommandBase.SetFlags(f)
   164  	f.StringVar(&c.ConstraintsStr, "constraints", "", "Set model constraints")
   165  	f.StringVar(&c.BootstrapConstraintsStr, "bootstrap-constraints", "", "Specify bootstrap machine constraints")
   166  	f.StringVar(&c.BootstrapSeries, "bootstrap-series", "", "Specify the series of the bootstrap machine")
   167  	if featureflag.Enabled(feature.ImageMetadata) {
   168  		f.StringVar(&c.BootstrapImage, "bootstrap-image", "", "Specify the image of the bootstrap machine")
   169  	}
   170  	f.BoolVar(&c.BuildAgent, "build-agent", false, "Build local version of agent binary before bootstrapping")
   171  	f.StringVar(&c.MetadataSource, "metadata-source", "", "Local path to use as tools and/or metadata source")
   172  	f.StringVar(&c.Placement, "to", "", "Placement directive indicating an instance to bootstrap")
   173  	f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "Do not destroy the model if bootstrap fails")
   174  	f.BoolVar(&c.AutoUpgrade, "auto-upgrade", false, "Upgrade to the latest patch release tools on first bootstrap")
   175  	f.BoolVar(&c.ForceAPIPort, "force-api-port", false, "Allow use of non-standard HTTPS port when official DNS name specified")
   176  	f.StringVar(&c.AgentVersionParam, "agent-version", "", "Version of tools to use for Juju agents")
   177  	f.StringVar(&c.CredentialName, "credential", "", "Credentials to use when bootstrapping")
   178  	f.Var(&c.config, "config", "Specify a controller configuration file, or one or more configuration\n    options\n    (--config config.yaml [--config key=value ...])")
   179  	f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "Name of the default hosted model for the controller")
   180  	f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "Name of the default hosted model for the controller")
   181  	f.BoolVar(&c.noGUI, "no-gui", false, "Do not install the Juju GUI in the controller when bootstrapping")
   182  	f.BoolVar(&c.showClouds, "clouds", false, "Print the available clouds which can be used to bootstrap a Juju environment")
   183  	f.StringVar(&c.showRegionsForCloud, "regions", "", "Print the available regions for the specified cloud")
   184  }
   185  
   186  func (c *bootstrapCommand) Init(args []string) (err error) {
   187  	if c.showClouds && c.showRegionsForCloud != "" {
   188  		return errors.New("--clouds and --regions can't be used together")
   189  	}
   190  	if c.showClouds {
   191  		return cmd.CheckEmpty(args)
   192  	}
   193  	if c.showRegionsForCloud != "" {
   194  		return cmd.CheckEmpty(args)
   195  	}
   196  	if c.AgentVersionParam != "" && c.BuildAgent {
   197  		return errors.New("--agent-version and --build-agent can't be used together")
   198  	}
   199  	if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) {
   200  		return errors.NotValidf("series %q", c.BootstrapSeries)
   201  	}
   202  
   203  	// Parse the placement directive. Bootstrap currently only
   204  	// supports provider-specific placement directives.
   205  	if c.Placement != "" {
   206  		_, err = instance.ParsePlacement(c.Placement)
   207  		if err != instance.ErrPlacementScopeMissing {
   208  			// We only support unscoped placement directives for bootstrap.
   209  			return errors.Errorf("unsupported bootstrap placement directive %q", c.Placement)
   210  		}
   211  	}
   212  	if !c.AutoUpgrade {
   213  		// With no auto upgrade chosen, we default to the version matching the bootstrap client.
   214  		vers := jujuversion.Current
   215  		c.AgentVersion = &vers
   216  	}
   217  	if c.AgentVersionParam != "" {
   218  		if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil {
   219  			c.AgentVersion = &vers.Number
   220  		} else if vers, err := version.Parse(c.AgentVersionParam); err == nil {
   221  			c.AgentVersion = &vers
   222  		} else {
   223  			return err
   224  		}
   225  	}
   226  	if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) {
   227  		return errors.New("requested agent version major.minor mismatch")
   228  	}
   229  
   230  	switch len(args) {
   231  	case 0:
   232  		// no args or flags, go interactive.
   233  		c.interactive = true
   234  		return nil
   235  	case 1:
   236  		// The user must specify both positional arguments: the controller name,
   237  		// and the cloud name (optionally with region specified).
   238  		return errors.New("controller name and cloud name are required")
   239  	}
   240  	c.controllerName = args[0]
   241  	c.Cloud = args[1]
   242  	if i := strings.IndexRune(c.Cloud, '/'); i > 0 {
   243  		c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:]
   244  	}
   245  	return cmd.CheckEmpty(args[2:])
   246  }
   247  
   248  // BootstrapInterface provides bootstrap functionality that Run calls to support cleaner testing.
   249  type BootstrapInterface interface {
   250  	Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args bootstrap.BootstrapParams) error
   251  	CloudRegionDetector(environs.EnvironProvider) (environs.CloudRegionDetector, bool)
   252  }
   253  
   254  type bootstrapFuncs struct{}
   255  
   256  func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error {
   257  	return bootstrap.Bootstrap(ctx, env, args)
   258  }
   259  
   260  func (b bootstrapFuncs) CloudRegionDetector(provider environs.EnvironProvider) (environs.CloudRegionDetector, bool) {
   261  	detector, ok := provider.(environs.CloudRegionDetector)
   262  	return detector, ok
   263  }
   264  
   265  var getBootstrapFuncs = func() BootstrapInterface {
   266  	return &bootstrapFuncs{}
   267  }
   268  
   269  var (
   270  	bootstrapPrepare           = bootstrap.Prepare
   271  	environsDestroy            = environs.Destroy
   272  	waitForAgentInitialisation = common.WaitForAgentInitialisation
   273  )
   274  
   275  var ambiguousDetectedCredentialError = errors.New(`
   276  more than one credential detected
   277  run juju autoload-credentials and specify a credential using the --credential argument`[1:],
   278  )
   279  
   280  var ambiguousCredentialError = errors.New(`
   281  more than one credential is available
   282  specify a credential using the --credential argument`[1:],
   283  )
   284  
   285  func (c *bootstrapCommand) parseConstraints(ctx *cmd.Context) (err error) {
   286  	allAliases := map[string]string{}
   287  	defer common.WarnConstraintAliases(ctx, allAliases)
   288  	if c.ConstraintsStr != "" {
   289  		cons, aliases, err := constraints.ParseWithAliases(c.ConstraintsStr)
   290  		for k, v := range aliases {
   291  			allAliases[k] = v
   292  		}
   293  		if err != nil {
   294  			return err
   295  		}
   296  		c.Constraints = cons
   297  	}
   298  	if c.BootstrapConstraintsStr != "" {
   299  		cons, aliases, err := constraints.ParseWithAliases(c.BootstrapConstraintsStr)
   300  		for k, v := range aliases {
   301  			allAliases[k] = v
   302  		}
   303  		if err != nil {
   304  			return err
   305  		}
   306  		c.BootstrapConstraints = cons
   307  	}
   308  	return nil
   309  }
   310  
   311  // Run connects to the environment specified on the command line and bootstraps
   312  // a juju in that environment if none already exists. If there is as yet no environments.yaml file,
   313  // the user is informed how to create one.
   314  func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
   315  	if err := c.parseConstraints(ctx); err != nil {
   316  		return err
   317  	}
   318  	if c.BootstrapImage != "" {
   319  		if c.BootstrapSeries == "" {
   320  			return errors.Errorf("--bootstrap-image must be used with --bootstrap-series")
   321  		}
   322  		cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints)
   323  		if err != nil {
   324  			return errors.Trace(err)
   325  		}
   326  		if !cons.HasArch() {
   327  			return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture")
   328  		}
   329  	}
   330  	if c.interactive {
   331  		if err := c.runInteractive(ctx); err != nil {
   332  			return errors.Trace(err)
   333  		}
   334  		// now run normal bootstrap using info gained above.
   335  	}
   336  	if c.showClouds {
   337  		return printClouds(ctx, c.ClientStore())
   338  	}
   339  	if c.showRegionsForCloud != "" {
   340  		return printCloudRegions(ctx, c.showRegionsForCloud)
   341  	}
   342  
   343  	bootstrapFuncs := getBootstrapFuncs()
   344  
   345  	// Get the cloud definition identified by c.Cloud. If c.Cloud does not
   346  	// identify a cloud in clouds.yaml, but is the name of a provider, and
   347  	// that provider implements environs.CloudRegionDetector, we'll
   348  	// synthesise a Cloud structure with the detected regions and no auth-
   349  	// types.
   350  	cloud, err := jujucloud.CloudByName(c.Cloud)
   351  	if errors.IsNotFound(err) {
   352  		ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud)
   353  		provider, err := environs.Provider(c.Cloud)
   354  		if errors.IsNotFound(err) {
   355  			return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds"))
   356  		} else if err != nil {
   357  			return errors.Trace(err)
   358  		}
   359  		detector, ok := bootstrapFuncs.CloudRegionDetector(provider)
   360  		if !ok {
   361  			ctx.Verbosef(
   362  				"provider %q does not support detecting regions",
   363  				c.Cloud,
   364  			)
   365  			return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds"))
   366  		}
   367  		var cloudEndpoint string
   368  		regions, err := detector.DetectRegions()
   369  		if errors.IsNotFound(err) {
   370  			// It's not an error to have no regions. If the
   371  			// provider does not support regions, then we
   372  			// reinterpret the supplied region name as the
   373  			// cloud's endpoint. This enables the user to
   374  			// supply, for example, maas/<IP> or manual/<IP>.
   375  			if c.Region != "" {
   376  				ctx.Verbosef("interpreting %q as the cloud endpoint", c.Region)
   377  				cloudEndpoint = c.Region
   378  				c.Region = ""
   379  			}
   380  		} else if err != nil {
   381  			return errors.Annotatef(err,
   382  				"detecting regions for %q cloud provider",
   383  				c.Cloud,
   384  			)
   385  		}
   386  		schemas := provider.CredentialSchemas()
   387  		authTypes := make([]jujucloud.AuthType, 0, len(schemas))
   388  		for authType := range schemas {
   389  			authTypes = append(authTypes, authType)
   390  		}
   391  		// Since we are iterating over a map, lets sort the authTypes so
   392  		// they are always in a consistent order.
   393  		sort.Sort(jujucloud.AuthTypes(authTypes))
   394  		cloud = &jujucloud.Cloud{
   395  			Type:      c.Cloud,
   396  			AuthTypes: authTypes,
   397  			Endpoint:  cloudEndpoint,
   398  			Regions:   regions,
   399  		}
   400  	} else if err != nil {
   401  		return errors.Trace(err)
   402  	}
   403  	if err := checkProviderType(cloud.Type); errors.IsNotFound(err) {
   404  		// This error will get handled later.
   405  	} else if err != nil {
   406  		return errors.Trace(err)
   407  	}
   408  
   409  	provider, err := environs.Provider(cloud.Type)
   410  	if err != nil {
   411  		return errors.Trace(err)
   412  	}
   413  	// Custom clouds may not have explicitly declared support for any auth-
   414  	// types, in which case we'll assume that they support everything that
   415  	// the provider supports.
   416  	if len(cloud.AuthTypes) == 0 {
   417  		for authType := range provider.CredentialSchemas() {
   418  			cloud.AuthTypes = append(cloud.AuthTypes, authType)
   419  		}
   420  	}
   421  
   422  	// Get the credentials and region name.
   423  	store := c.ClientStore()
   424  	var detectedCredentialName string
   425  	credential, credentialName, regionName, err := modelcmd.GetCredentials(
   426  		ctx, store, modelcmd.GetCredentialsParams{
   427  			Cloud:          *cloud,
   428  			CloudName:      c.Cloud,
   429  			CloudRegion:    c.Region,
   430  			CredentialName: c.CredentialName,
   431  		},
   432  	)
   433  	if errors.Cause(err) == modelcmd.ErrMultipleCredentials {
   434  		return ambiguousCredentialError
   435  	}
   436  	if errors.IsNotFound(err) && c.CredentialName == "" {
   437  		// No credential was explicitly specified, and no credential
   438  		// was found in credentials.yaml; have the provider detect
   439  		// credentials from the environment.
   440  		ctx.Verbosef("no credentials found, checking environment")
   441  		detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type)
   442  		if errors.Cause(err) == modelcmd.ErrMultipleCredentials {
   443  			return ambiguousDetectedCredentialError
   444  		} else if err != nil {
   445  			return errors.Trace(err)
   446  		}
   447  		// We have one credential so extract it from the map.
   448  		var oneCredential jujucloud.Credential
   449  		for detectedCredentialName, oneCredential = range detected.AuthCredentials {
   450  		}
   451  		credential = &oneCredential
   452  		regionName = c.Region
   453  		if regionName == "" {
   454  			regionName = detected.DefaultRegion
   455  		}
   456  		logger.Debugf(
   457  			"authenticating with region %q and credential %q (%v)",
   458  			regionName, detectedCredentialName, credential.Label,
   459  		)
   460  		logger.Tracef("credential: %v", credential)
   461  	} else if err != nil {
   462  		return errors.Trace(err)
   463  	}
   464  
   465  	region, err := getRegion(cloud, c.Cloud, regionName)
   466  	if err != nil {
   467  		fmt.Fprintf(ctx.GetStderr(),
   468  			"%s\n\nSpecify an alternative region, or try %q.",
   469  			err, "juju update-clouds",
   470  		)
   471  		return cmd.ErrSilent
   472  	}
   473  
   474  	controllerModelUUID, err := utils.NewUUID()
   475  	if err != nil {
   476  		return errors.Trace(err)
   477  	}
   478  	hostedModelUUID, err := utils.NewUUID()
   479  	if err != nil {
   480  		return errors.Trace(err)
   481  	}
   482  	controllerUUID, err := utils.NewUUID()
   483  	if err != nil {
   484  		return errors.Trace(err)
   485  	}
   486  
   487  	// Create a model config, and split out any controller
   488  	// and bootstrap config attributes.
   489  	modelConfigAttrs := map[string]interface{}{
   490  		"type":         cloud.Type,
   491  		"name":         bootstrap.ControllerModelName,
   492  		config.UUIDKey: controllerModelUUID.String(),
   493  	}
   494  	userConfigAttrs, err := c.config.ReadAttrs(ctx)
   495  	if err != nil {
   496  		return errors.Trace(err)
   497  	}
   498  
   499  	// The provider may define some custom attributes specific
   500  	// to the provider. These will be added to the model config.
   501  	providerAttrs := make(map[string]interface{})
   502  	if ps, ok := provider.(config.ConfigSchemaSource); ok {
   503  		for attr := range ps.ConfigSchema() {
   504  			if v, ok := userConfigAttrs[attr]; ok {
   505  				providerAttrs[attr] = v
   506  			}
   507  		}
   508  		fields := schema.FieldMap(ps.ConfigSchema(), ps.ConfigDefaults())
   509  		if coercedAttrs, err := fields.Coerce(providerAttrs, nil); err != nil {
   510  			return errors.Annotatef(err, "invalid attribute value(s) for %v cloud", cloud.Type)
   511  		} else {
   512  			providerAttrs = coercedAttrs.(map[string]interface{})
   513  		}
   514  	}
   515  	logger.Debugf("provider attrs: %v", providerAttrs)
   516  	for k, v := range userConfigAttrs {
   517  		modelConfigAttrs[k] = v
   518  	}
   519  	// Provider specific attributes are either already specified in model
   520  	// config (but may have been coerced), or were not present. Either way,
   521  	// copy them in.
   522  	for k, v := range providerAttrs {
   523  		modelConfigAttrs[k] = v
   524  	}
   525  	bootstrapConfigAttrs := make(map[string]interface{})
   526  	controllerConfigAttrs := make(map[string]interface{})
   527  	// Based on the attribute names in clouds.yaml, create
   528  	// a map of shared config for all models on this cloud.
   529  	inheritedControllerAttrs := make(map[string]interface{})
   530  	for k, v := range cloud.Config {
   531  		switch {
   532  		case bootstrap.IsBootstrapAttribute(k):
   533  			bootstrapConfigAttrs[k] = v
   534  			continue
   535  		case controller.ControllerOnlyAttribute(k):
   536  			controllerConfigAttrs[k] = v
   537  			continue
   538  		}
   539  		inheritedControllerAttrs[k] = v
   540  	}
   541  	for k, v := range modelConfigAttrs {
   542  		switch {
   543  		case bootstrap.IsBootstrapAttribute(k):
   544  			bootstrapConfigAttrs[k] = v
   545  			delete(modelConfigAttrs, k)
   546  		case controller.ControllerOnlyAttribute(k):
   547  			controllerConfigAttrs[k] = v
   548  			delete(modelConfigAttrs, k)
   549  		}
   550  	}
   551  	bootstrapConfig, err := bootstrap.NewConfig(bootstrapConfigAttrs)
   552  	if err != nil {
   553  		return errors.Annotate(err, "constructing bootstrap config")
   554  	}
   555  	controllerConfig, err := controller.NewConfig(
   556  		controllerUUID.String(), bootstrapConfig.CACert, controllerConfigAttrs,
   557  	)
   558  	if err != nil {
   559  		return errors.Annotate(err, "constructing controller config")
   560  	}
   561  	if controllerConfig.AutocertDNSName() != "" && controllerConfig.APIPort() != 443 && !c.ForceAPIPort {
   562  		return errors.Errorf(`autocert-dns-name is set but it's not usually possible to obtain official certificates without api-port=443 config; use --force-api-port to override this if you plan on using a port forwarder`)
   563  	}
   564  
   565  	if err := common.FinalizeAuthorizedKeys(ctx, modelConfigAttrs); err != nil {
   566  		return errors.Annotate(err, "finalizing authorized-keys")
   567  	}
   568  	logger.Debugf("preparing controller with config: %v", modelConfigAttrs)
   569  
   570  	// Read existing current controller so we can clean up on error.
   571  	var oldCurrentController string
   572  	oldCurrentController, err = store.CurrentController()
   573  	if errors.IsNotFound(err) {
   574  		oldCurrentController = ""
   575  	} else if err != nil {
   576  		return errors.Annotate(err, "error reading current controller")
   577  	}
   578  
   579  	defer func() {
   580  		if resultErr == nil || errors.IsAlreadyExists(resultErr) {
   581  			return
   582  		}
   583  		if oldCurrentController != "" {
   584  			if err := store.SetCurrentController(oldCurrentController); err != nil {
   585  				logger.Errorf(
   586  					"cannot reset current controller to %q: %v",
   587  					oldCurrentController, err,
   588  				)
   589  			}
   590  		}
   591  		if err := store.RemoveController(c.controllerName); err != nil {
   592  			logger.Errorf(
   593  				"cannot destroy newly created controller %q details: %v",
   594  				c.controllerName, err,
   595  			)
   596  		}
   597  	}()
   598  
   599  	bootstrapModelConfig := make(map[string]interface{})
   600  	for k, v := range inheritedControllerAttrs {
   601  		bootstrapModelConfig[k] = v
   602  	}
   603  	for k, v := range modelConfigAttrs {
   604  		bootstrapModelConfig[k] = v
   605  	}
   606  	// Add in any default attribute values if not already
   607  	// specified, making the recorded bootstrap config
   608  	// immutable to changes in Juju.
   609  	for k, v := range config.ConfigDefaults() {
   610  		if _, ok := bootstrapModelConfig[k]; !ok {
   611  			bootstrapModelConfig[k] = v
   612  		}
   613  	}
   614  
   615  	environ, err := bootstrapPrepare(
   616  		modelcmd.BootstrapContext(ctx), store,
   617  		bootstrap.PrepareParams{
   618  			ModelConfig:      bootstrapModelConfig,
   619  			ControllerConfig: controllerConfig,
   620  			ControllerName:   c.controllerName,
   621  			Cloud: environs.CloudSpec{
   622  				Type:             cloud.Type,
   623  				Name:             c.Cloud,
   624  				Region:           region.Name,
   625  				Endpoint:         region.Endpoint,
   626  				IdentityEndpoint: region.IdentityEndpoint,
   627  				StorageEndpoint:  region.StorageEndpoint,
   628  				Credential:       credential,
   629  			},
   630  			CredentialName: credentialName,
   631  			AdminSecret:    bootstrapConfig.AdminSecret,
   632  		},
   633  	)
   634  	if err != nil {
   635  		return errors.Trace(err)
   636  	}
   637  
   638  	// Set the current model to the initial hosted model.
   639  	if err := store.UpdateModel(c.controllerName, c.hostedModelName, jujuclient.ModelDetails{
   640  		hostedModelUUID.String(),
   641  	}); err != nil {
   642  		return errors.Trace(err)
   643  	}
   644  	if err := store.SetCurrentModel(c.controllerName, c.hostedModelName); err != nil {
   645  		return errors.Trace(err)
   646  	}
   647  
   648  	// Set the current controller so "juju status" can be run while
   649  	// bootstrapping is underway.
   650  	if err := store.SetCurrentController(c.controllerName); err != nil {
   651  		return errors.Trace(err)
   652  	}
   653  
   654  	cloudRegion := c.Cloud
   655  	if region.Name != "" {
   656  		cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name)
   657  	}
   658  	ctx.Infof(
   659  		"Creating Juju controller %q on %s",
   660  		c.controllerName, cloudRegion,
   661  	)
   662  
   663  	// If we error out for any reason, clean up the environment.
   664  	defer func() {
   665  		if resultErr != nil {
   666  			if c.KeepBrokenEnvironment {
   667  				ctx.Infof(`
   668  bootstrap failed but --keep-broken was specified so resources are not being destroyed.
   669  When you have finished diagnosing the problem, remember to clean up the failed controller.
   670  See `[1:] + "`juju kill-controller`" + `.`)
   671  			} else {
   672  				handleBootstrapError(ctx, resultErr, func() error {
   673  					return environsDestroy(
   674  						c.controllerName, environ, store,
   675  					)
   676  				})
   677  			}
   678  		}
   679  	}()
   680  
   681  	// Block interruption during bootstrap. Providers may also
   682  	// register for interrupt notification so they can exit early.
   683  	interrupted := make(chan os.Signal, 1)
   684  	defer close(interrupted)
   685  	ctx.InterruptNotify(interrupted)
   686  	defer ctx.StopInterruptNotify(interrupted)
   687  	go func() {
   688  		for _ = range interrupted {
   689  			ctx.Infof("Interrupt signalled: waiting for bootstrap to exit")
   690  		}
   691  	}()
   692  
   693  	// If --metadata-source is specified, override the default tools metadata source so
   694  	// SyncTools can use it, and also upload any image metadata.
   695  	var metadataDir string
   696  	if c.MetadataSource != "" {
   697  		metadataDir = ctx.AbsPath(c.MetadataSource)
   698  	}
   699  
   700  	// Merge environ and bootstrap-specific constraints.
   701  	constraintsValidator, err := environ.ConstraintsValidator()
   702  	if err != nil {
   703  		return errors.Trace(err)
   704  	}
   705  	bootstrapConstraints, err := constraintsValidator.Merge(
   706  		c.Constraints, c.BootstrapConstraints,
   707  	)
   708  	if err != nil {
   709  		return errors.Trace(err)
   710  	}
   711  	logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints)
   712  
   713  	hostedModelConfig := map[string]interface{}{
   714  		"name":         c.hostedModelName,
   715  		config.UUIDKey: hostedModelUUID.String(),
   716  	}
   717  	for k, v := range inheritedControllerAttrs {
   718  		hostedModelConfig[k] = v
   719  	}
   720  
   721  	// We copy across any user supplied attributes to the hosted model config.
   722  	// But only if the attributes have not been removed from the controller
   723  	// model config as part of preparing the controller model.
   724  	controllerModelConfigAttrs := environ.Config().AllAttrs()
   725  	for k, v := range userConfigAttrs {
   726  		if _, ok := controllerModelConfigAttrs[k]; ok {
   727  			hostedModelConfig[k] = v
   728  		}
   729  	}
   730  	// Ensure that certain config attributes are not included in the hosted
   731  	// model config. These attributes may be modified during bootstrap; by
   732  	// removing them from this map, we ensure the modified values are
   733  	// inherited.
   734  	delete(hostedModelConfig, config.AuthorizedKeysKey)
   735  	delete(hostedModelConfig, config.AgentVersionKey)
   736  
   737  	// Check whether the Juju GUI must be installed in the controller.
   738  	// Leaving this value empty means no GUI will be installed.
   739  	var guiDataSourceBaseURL string
   740  	if !c.noGUI {
   741  		guiDataSourceBaseURL = common.GUIDataSourceBaseURL()
   742  	}
   743  
   744  	if credentialName == "" {
   745  		// credentialName will be empty if the credential was detected.
   746  		// We must supply a name for the credential in the database,
   747  		// so choose one.
   748  		credentialName = detectedCredentialName
   749  	}
   750  
   751  	err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{
   752  		ModelConstraints:          c.Constraints,
   753  		BootstrapConstraints:      bootstrapConstraints,
   754  		BootstrapSeries:           c.BootstrapSeries,
   755  		BootstrapImage:            c.BootstrapImage,
   756  		Placement:                 c.Placement,
   757  		BuildAgent:                c.BuildAgent,
   758  		BuildAgentTarball:         sync.BuildAgentTarball,
   759  		AgentVersion:              c.AgentVersion,
   760  		MetadataDir:               metadataDir,
   761  		Cloud:                     *cloud,
   762  		CloudName:                 c.Cloud,
   763  		CloudRegion:               region.Name,
   764  		CloudCredential:           credential,
   765  		CloudCredentialName:       credentialName,
   766  		ControllerConfig:          controllerConfig,
   767  		ControllerInheritedConfig: inheritedControllerAttrs,
   768  		RegionInheritedConfig:     cloud.RegionConfig,
   769  		HostedModelConfig:         hostedModelConfig,
   770  		GUIDataSourceBaseURL:      guiDataSourceBaseURL,
   771  		AdminSecret:               bootstrapConfig.AdminSecret,
   772  		CAPrivateKey:              bootstrapConfig.CAPrivateKey,
   773  		DialOpts: environs.BootstrapDialOpts{
   774  			Timeout:        bootstrapConfig.BootstrapTimeout,
   775  			RetryDelay:     bootstrapConfig.BootstrapRetryDelay,
   776  			AddressesDelay: bootstrapConfig.BootstrapAddressesDelay,
   777  		},
   778  	})
   779  	if err != nil {
   780  		return errors.Annotate(err, "failed to bootstrap model")
   781  	}
   782  
   783  	if err := c.SetModelName(modelcmd.JoinModelName(c.controllerName, c.hostedModelName)); err != nil {
   784  		return errors.Trace(err)
   785  	}
   786  
   787  	agentVersion := jujuversion.Current
   788  	if c.AgentVersion != nil {
   789  		agentVersion = *c.AgentVersion
   790  	}
   791  	err = common.SetBootstrapEndpointAddress(c.ClientStore(), c.controllerName, agentVersion, controllerConfig.APIPort(), environ)
   792  	if err != nil {
   793  		return errors.Annotate(err, "saving bootstrap endpoint address")
   794  	}
   795  
   796  	// To avoid race conditions when running scripted bootstraps, wait
   797  	// for the controller's machine agent to be ready to accept commands
   798  	// before exiting this bootstrap command.
   799  	return waitForAgentInitialisation(ctx, &c.ModelCommandBase, c.controllerName, c.hostedModelName)
   800  }
   801  
   802  // runInteractive queries the user about bootstrap config interactively at the
   803  // command prompt.
   804  func (c *bootstrapCommand) runInteractive(ctx *cmd.Context) error {
   805  	scanner := bufio.NewScanner(ctx.Stdin)
   806  	clouds, err := assembleClouds()
   807  	if err != nil {
   808  		return errors.Trace(err)
   809  	}
   810  	c.Cloud, err = queryCloud(clouds, jujucloud.DefaultLXD, scanner, ctx.Stdout)
   811  	if err != nil {
   812  		return errors.Trace(err)
   813  	}
   814  	cloud, err := jujucloud.CloudByName(c.Cloud)
   815  	if err != nil {
   816  		return errors.Trace(err)
   817  	}
   818  
   819  	switch len(cloud.Regions) {
   820  	case 0:
   821  		// No region to choose, nothing to do.
   822  	case 1:
   823  		// If there's just one, don't prompt, just use it.
   824  		c.Region = cloud.Regions[0].Name
   825  	default:
   826  		c.Region, err = queryRegion(c.Cloud, cloud.Regions, scanner, ctx.Stdout)
   827  		if err != nil {
   828  			return errors.Trace(err)
   829  		}
   830  	}
   831  
   832  	var username string
   833  	if u, err := user.Current(); err == nil {
   834  		username = u.Username
   835  	}
   836  	defName := defaultControllerName(username, c.Cloud, c.Region, cloud)
   837  
   838  	c.controllerName, err = queryName(defName, scanner, ctx.Stdout)
   839  	if err != nil {
   840  		return errors.Trace(err)
   841  	}
   842  	return nil
   843  }
   844  
   845  // getRegion returns the cloud.Region to use, based on the specified
   846  // region name.  If no region name is specified, and there is at least
   847  // one region, we use the first region in the list.
   848  func getRegion(cloud *jujucloud.Cloud, cloudName, regionName string) (jujucloud.Region, error) {
   849  	if regionName != "" {
   850  		region, err := jujucloud.RegionByName(cloud.Regions, regionName)
   851  		if err != nil {
   852  			return jujucloud.Region{}, errors.Trace(err)
   853  		}
   854  		return *region, nil
   855  	}
   856  	if len(cloud.Regions) > 0 {
   857  		// No region was specified, use the first region in the list.
   858  		return cloud.Regions[0], nil
   859  	}
   860  	return jujucloud.Region{
   861  		"", // no region name
   862  		cloud.Endpoint,
   863  		cloud.IdentityEndpoint,
   864  		cloud.StorageEndpoint,
   865  	}, nil
   866  }
   867  
   868  // checkProviderType ensures the provider type is okay.
   869  func checkProviderType(envType string) error {
   870  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
   871  	flag, ok := provisionalProviders[envType]
   872  	if ok && !featureflag.Enabled(flag) {
   873  		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`
   874  		return errors.Errorf(msg, envType, flag)
   875  	}
   876  	return nil
   877  }
   878  
   879  // handleBootstrapError is called to clean up if bootstrap fails.
   880  func handleBootstrapError(ctx *cmd.Context, err error, cleanup func() error) {
   881  	ch := make(chan os.Signal, 1)
   882  	ctx.InterruptNotify(ch)
   883  	defer ctx.StopInterruptNotify(ch)
   884  	defer close(ch)
   885  	go func() {
   886  		for _ = range ch {
   887  			fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap")
   888  		}
   889  	}()
   890  	if err := cleanup(); err != nil {
   891  		logger.Errorf("error cleaning up: %v", err)
   892  	}
   893  }