func (s *ModelConfigSourceSuite) TestModelConfigDefaults(c *gc.C) { expectedValues := make(config.ModelDefaultAttributes) for attr, val := range config.ConfigDefaults() { expectedValues[attr] = config.AttributeDefaultValues{ Default: val, } } ds := expectedValues["http-proxy"] ds.Controller = "http://proxy" expectedValues["http-proxy"] = ds ds = expectedValues["apt-mirror"] ds.Controller = "http://mirror" ds.Regions = []config.RegionDefaultValue{{ Name: "dummy-region", Value: "http://dummy-mirror", }} expectedValues["apt-mirror"] = ds ds = expectedValues["no-proxy"] ds.Regions = []config.RegionDefaultValue{{ Name: "dummy-region", Value: "dummy-proxy"}} expectedValues["no-proxy"] = ds sources, err := s.State.ModelConfigDefaultValues() c.Assert(err, jc.ErrorIsNil) c.Assert(sources, jc.DeepEquals, expectedValues) }
func (s *ModelConfigSourceSuite) assertModelConfigValues(c *gc.C, modelCfg *config.Config, modelAttributes, controllerAttributes set.Strings) { expectedValues := make(config.ConfigValues) defaultAttributes := set.NewStrings() for defaultAttr := range config.ConfigDefaults() { defaultAttributes.Add(defaultAttr) } for attr, val := range modelCfg.AllAttrs() { source := "model" if defaultAttributes.Contains(attr) { source = "default" } if modelAttributes.Contains(attr) { source = "model" } if controllerAttributes.Contains(attr) { source = "controller" } expectedValues[attr] = config.ConfigValue{ Value: val, Source: source, } } sources, err := s.State.ModelConfigValues() c.Assert(err, jc.ErrorIsNil) c.Assert(sources, jc.DeepEquals, expectedValues) }
func (s *InitializeSuite) TestInitializeWithControllerInheritedConfig(c *gc.C) { cfg := testing.ModelConfig(c) uuid := cfg.UUID() initial := cfg.AllAttrs() controllerInheritedConfigIn := map[string]interface{}{ "default-series": initial["default-series"], } owner := names.NewLocalUserTag("initialize-admin") controllerCfg := testing.FakeControllerConfig() st, err := state.Initialize(state.InitializeParams{ Clock: clock.WallClock, ControllerConfig: controllerCfg, ControllerModelArgs: state.ModelArgs{ CloudName: "dummy", Owner: owner, Config: cfg, StorageProviderRegistry: storage.StaticProviderRegistry{}, }, CloudName: "dummy", Cloud: cloud.Cloud{ Type: "dummy", AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, }, ControllerInheritedConfig: controllerInheritedConfigIn, MongoInfo: statetesting.NewMongoInfo(), MongoDialOpts: mongotest.DialOpts(), }) c.Assert(err, jc.ErrorIsNil) c.Assert(st, gc.NotNil) modelTag := st.ModelTag() c.Assert(modelTag.Id(), gc.Equals, uuid) err = st.Close() c.Assert(err, jc.ErrorIsNil) s.openState(c, modelTag) controllerInheritedConfig, err := state.ReadSettings(s.State, state.GlobalSettingsC, state.ControllerInheritedSettingsGlobalKey) c.Assert(err, jc.ErrorIsNil) c.Assert(controllerInheritedConfig.Map(), jc.DeepEquals, controllerInheritedConfigIn) expected := cfg.AllAttrs() for k, v := range config.ConfigDefaults() { if _, ok := expected[k]; !ok { expected[k] = v } } // Config as read from state has resources tags coerced to a map. expected["resource-tags"] = map[string]string{} cfg, err = s.State.ModelConfig() c.Assert(err, jc.ErrorIsNil) c.Assert(cfg.AllAttrs(), jc.DeepEquals, expected) }
func (s *ModelConfigSourceSuite) TestUpdateModelConfigDefaults(c *gc.C) { // Set up values that will be removed. attrs := map[string]interface{}{ "http-proxy": "http://http-proxy", "https-proxy": "https://https-proxy", } err := s.State.UpdateModelConfigDefaultValues(attrs, nil, nil) c.Assert(err, jc.ErrorIsNil) attrs = map[string]interface{}{ "apt-mirror": "http://different-mirror", } err = s.State.UpdateModelConfigDefaultValues(attrs, []string{"http-proxy", "https-proxy"}, nil) c.Assert(err, jc.ErrorIsNil) info := statetesting.NewMongoInfo() anotherState, err := state.Open(s.modelTag, s.State.ControllerTag(), info, mongotest.DialOpts(), state.NewPolicyFunc(nil)) c.Assert(err, jc.ErrorIsNil) defer anotherState.Close() cfg, err := anotherState.ModelConfigDefaultValues() c.Assert(err, jc.ErrorIsNil) expectedValues := make(config.ModelDefaultAttributes) for attr, val := range config.ConfigDefaults() { expectedValues[attr] = config.AttributeDefaultValues{ Default: val, } } delete(expectedValues, "http-mirror") delete(expectedValues, "https-mirror") expectedValues["apt-mirror"] = config.AttributeDefaultValues{ Controller: "http://different-mirror", Default: "", Regions: []config.RegionDefaultValue{{ Name: "dummy-region", Value: "http://dummy-mirror", }}} expectedValues["no-proxy"] = config.AttributeDefaultValues{ Default: "", Regions: []config.RegionDefaultValue{{ Name: "dummy-region", Value: "dummy-proxy", }}} c.Assert(cfg, jc.DeepEquals, expectedValues) }
// defaultInheritedConfig returns config values which are defined // as defaults in either Juju or the state's environ provider. func (st *State) defaultInheritedConfig() (attrValues, error) { var defaults = make(map[string]interface{}) for k, v := range config.ConfigDefaults() { defaults[k] = v } providerDefaults, err := st.environsProviderConfigSchemaSource() if errors.IsNotImplemented(err) { return defaults, nil } else if err != nil { return nil, errors.Trace(err) } fields := schema.FieldMap(providerDefaults.ConfigSchema(), providerDefaults.ConfigDefaults()) if coercedAttrs, err := fields.Coerce(defaults, nil); err != nil { return nil, errors.Trace(err) } else { for k, v := range coercedAttrs.(map[string]interface{}) { defaults[k] = v } } return defaults, nil }
func (s *ModelConfigSourceSuite) TestUpdateModelConfigRegionDefaults(c *gc.C) { // The test env is setup with dummy/dummy-region having a no-proxy // dummy-proxy value and nether-region with a nether-proxy value. // // First we change the no-proxy setting in dummy-region attrs := map[string]interface{}{ "no-proxy": "changed-proxy", } rspec, err := environs.NewRegionSpec("dummy", "dummy-region") c.Assert(err, jc.ErrorIsNil) err = s.State.UpdateModelConfigDefaultValues(attrs, nil, rspec) c.Assert(err, jc.ErrorIsNil) // Then check in another state. info := statetesting.NewMongoInfo() anotherState, err := state.Open(s.modelTag, s.State.ControllerTag(), info, mongotest.DialOpts(), state.NewPolicyFunc(nil)) c.Assert(err, jc.ErrorIsNil) defer anotherState.Close() cfg, err := anotherState.ModelConfigDefaultValues() c.Assert(err, jc.ErrorIsNil) expectedValues := make(config.ModelDefaultAttributes) for attr, val := range config.ConfigDefaults() { expectedValues[attr] = config.AttributeDefaultValues{ Default: val, } } expectedValues["http-proxy"] = config.AttributeDefaultValues{ Controller: "http://proxy", Default: "", } expectedValues["apt-mirror"] = config.AttributeDefaultValues{ Controller: "http://mirror", Default: "", Regions: []config.RegionDefaultValue{{ Name: "dummy-region", Value: "http://dummy-mirror", }}} expectedValues["no-proxy"] = config.AttributeDefaultValues{ Default: "", Regions: []config.RegionDefaultValue{{ Name: "dummy-region", Value: "changed-proxy", }}} c.Assert(cfg, jc.DeepEquals, expectedValues) // remove the dummy-region setting err = s.State.UpdateModelConfigDefaultValues(nil, []string{"no-proxy"}, rspec) // and check again cfg, err = anotherState.ModelConfigDefaultValues() c.Assert(err, jc.ErrorIsNil) cfg, err = anotherState.ModelConfigDefaultValues() c.Assert(err, jc.ErrorIsNil) expectedValues = make(config.ModelDefaultAttributes) for attr, val := range config.ConfigDefaults() { expectedValues[attr] = config.AttributeDefaultValues{ Default: val, } } expectedValues["http-proxy"] = config.AttributeDefaultValues{ Controller: "http://proxy", Default: "", } expectedValues["apt-mirror"] = config.AttributeDefaultValues{ Controller: "http://mirror", Default: "", Regions: []config.RegionDefaultValue{{ Name: "dummy-region", Value: "http://dummy-mirror", }}} c.Assert(cfg, jc.DeepEquals, expectedValues) }
func (s *InitializeSuite) TestInitialize(c *gc.C) { cfg := testing.ModelConfig(c) uuid := cfg.UUID() owner := names.NewLocalUserTag("initialize-admin") userPassCredentialTag := names.NewCloudCredentialTag( "dummy/" + owner.Canonical() + "/some-credential", ) emptyCredentialTag := names.NewCloudCredentialTag( "dummy/" + owner.Canonical() + "/empty-credential", ) userpassCredential := cloud.NewCredential( cloud.UserPassAuthType, map[string]string{ "username": "******", "password": "******", }, ) userpassCredential.Label = userPassCredentialTag.Name() emptyCredential := cloud.NewEmptyCredential() emptyCredential.Label = emptyCredentialTag.Name() cloudCredentialsIn := map[names.CloudCredentialTag]cloud.Credential{ userPassCredentialTag: userpassCredential, emptyCredentialTag: emptyCredential, } controllerCfg := testing.FakeControllerConfig() st, err := state.Initialize(state.InitializeParams{ Clock: clock.WallClock, ControllerConfig: controllerCfg, ControllerModelArgs: state.ModelArgs{ Owner: owner, Config: cfg, CloudName: "dummy", CloudRegion: "dummy-region", CloudCredential: userPassCredentialTag, StorageProviderRegistry: storage.StaticProviderRegistry{}, }, CloudName: "dummy", Cloud: cloud.Cloud{ Type: "dummy", AuthTypes: []cloud.AuthType{ cloud.EmptyAuthType, cloud.UserPassAuthType, }, Regions: []cloud.Region{{Name: "dummy-region"}}, }, CloudCredentials: cloudCredentialsIn, MongoInfo: statetesting.NewMongoInfo(), MongoDialOpts: mongotest.DialOpts(), }) c.Assert(err, jc.ErrorIsNil) c.Assert(st, gc.NotNil) modelTag := st.ModelTag() c.Assert(modelTag.Id(), gc.Equals, uuid) err = st.Close() c.Assert(err, jc.ErrorIsNil) s.openState(c, modelTag) cfg, err = s.State.ModelConfig() c.Assert(err, jc.ErrorIsNil) expected := cfg.AllAttrs() for k, v := range config.ConfigDefaults() { if _, ok := expected[k]; !ok { expected[k] = v } } c.Assert(cfg.AllAttrs(), jc.DeepEquals, expected) // Check that the model has been created. model, err := s.State.Model() c.Assert(err, jc.ErrorIsNil) c.Assert(model.Tag(), gc.Equals, modelTag) c.Assert(model.CloudRegion(), gc.Equals, "dummy-region") // Check that the owner has been created. c.Assert(model.Owner(), gc.Equals, owner) // Check that the owner can be retrieved by the tag. entity, err := s.State.FindEntity(model.Owner()) c.Assert(err, jc.ErrorIsNil) c.Assert(entity.Tag(), gc.Equals, owner) // Check that the owner has an ModelUser created for the bootstrapped model. modelUser, err := s.State.UserAccess(model.Owner(), model.Tag()) c.Assert(err, jc.ErrorIsNil) c.Assert(modelUser.UserTag, gc.Equals, owner) c.Assert(modelUser.Object, gc.Equals, model.Tag()) // Check that the model can be found through the tag. entity, err = s.State.FindEntity(modelTag) c.Assert(err, jc.ErrorIsNil) cons, err := s.State.ModelConstraints() c.Assert(err, jc.ErrorIsNil) c.Assert(&cons, jc.Satisfies, constraints.IsEmpty) addrs, err := s.State.APIHostPorts() c.Assert(err, jc.ErrorIsNil) c.Assert(addrs, gc.HasLen, 0) info, err := s.State.ControllerInfo() c.Assert(err, jc.ErrorIsNil) c.Assert(info, jc.DeepEquals, &state.ControllerInfo{ModelTag: modelTag, CloudName: "dummy"}) // Check that the model's cloud and credential names are as // expected, and the owner's cloud credentials are initialised. c.Assert(model.Cloud(), gc.Equals, "dummy") credentialTag, ok := model.CloudCredential() c.Assert(ok, jc.IsTrue) c.Assert(credentialTag, gc.Equals, userPassCredentialTag) cloudCredentials, err := s.State.CloudCredentials(model.Owner(), "dummy") c.Assert(err, jc.ErrorIsNil) expectedCred := make(map[string]cloud.Credential, len(cloudCredentialsIn)) for tag, cred := range cloudCredentialsIn { expectedCred[tag.Canonical()] = cred } c.Assert(cloudCredentials, jc.DeepEquals, expectedCred) }
// modelSetupOps returns the transactions necessary to set up a model. func (st *State) modelSetupOps(controllerUUID string, args ModelArgs, inherited *lineage) ([]txn.Op, error) { if inherited != nil { if err := checkControllerInheritedConfig(inherited.ControllerConfig); err != nil { return nil, errors.Trace(err) } } if err := checkModelConfig(args.Config); err != nil { return nil, errors.Trace(err) } controllerModelUUID := st.controllerModelTag.Id() modelUUID := args.Config.UUID() modelStatusDoc := statusDoc{ ModelUUID: modelUUID, Updated: st.clock.Now().UnixNano(), Status: status.Available, } modelUserOps := createModelUserOps( modelUUID, args.Owner, args.Owner, args.Owner.Name(), st.NowToTheSecond(), permission.AdminAccess, ) ops := []txn.Op{ createStatusOp(st, modelGlobalKey, modelStatusDoc), createConstraintsOp(st, modelGlobalKey, args.Constraints), } // Inc ref count for hosted models. if controllerModelUUID != modelUUID { ops = append(ops, incHostedModelCountOp()) } // Create the default storage pools for the model. defaultStoragePoolsOps, err := st.createDefaultStoragePoolsOps(args.StorageProviderRegistry) if err != nil { return nil, errors.Trace(err) } ops = append(ops, defaultStoragePoolsOps...) // Create the final map of config attributes for the model. // If we have ControllerInheritedConfig passed in, that means state // is being initialised and there won't be any config sources // in state. var configSources []modelConfigSource if inherited != nil { configSources = []modelConfigSource{ { name: config.JujuDefaultSource, sourceFunc: modelConfigSourceFunc(func() (attrValues, error) { return config.ConfigDefaults(), nil })}, { name: config.JujuControllerSource, sourceFunc: modelConfigSourceFunc(func() (attrValues, error) { return inherited.ControllerConfig, nil })}, { name: config.JujuRegionSource, sourceFunc: modelConfigSourceFunc(func() (attrValues, error) { // We return the values specific to this region for this model. return attrValues(inherited.RegionConfig[args.CloudRegion]), nil })}, } } else { rspec := &environs.RegionSpec{Cloud: args.CloudName, Region: args.CloudRegion} configSources = modelConfigSources(st, rspec) } modelCfg, err := composeModelConfigAttributes(args.Config.AllAttrs(), configSources...) if err != nil { return nil, errors.Trace(err) } // Some values require marshalling before storage. modelCfg = config.CoerceForStorage(modelCfg) ops = append(ops, createSettingsOp(settingsC, modelGlobalKey, modelCfg), createModelEntityRefsOp(modelUUID), createModelOp( args.Owner, args.Config.Name(), modelUUID, controllerUUID, args.CloudName, args.CloudRegion, args.CloudCredential, args.MigrationMode, ), createUniqueOwnerModelNameOp(args.Owner, args.Config.Name()), ) ops = append(ops, modelUserOps...) return ops, nil }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { if err := c.parseConstraints(ctx); err != nil { return err } if c.BootstrapImage != "" { if c.BootstrapSeries == "" { return errors.Errorf("--bootstrap-image must be used with --bootstrap-series") } cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) if err != nil { return errors.Trace(err) } if !cons.HasArch() { return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") } } if c.interactive { if err := c.runInteractive(ctx); err != nil { return errors.Trace(err) } // now run normal bootstrap using info gained above. } if c.showClouds { return printClouds(ctx, c.ClientStore()) } if c.showRegionsForCloud != "" { return printCloudRegions(ctx, c.showRegionsForCloud) } bootstrapFuncs := getBootstrapFuncs() // Get the cloud definition identified by c.Cloud. If c.Cloud does not // identify a cloud in clouds.yaml, but is the name of a provider, and // that provider implements environs.CloudRegionDetector, we'll // synthesise a Cloud structure with the detected regions and no auth- // types. cloud, err := jujucloud.CloudByName(c.Cloud) if errors.IsNotFound(err) { ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) provider, err := environs.Provider(c.Cloud) if errors.IsNotFound(err) { return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } else if err != nil { return errors.Trace(err) } detector, ok := bootstrapFuncs.CloudRegionDetector(provider) if !ok { ctx.Verbosef( "provider %q does not support detecting regions", c.Cloud, ) return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } var cloudEndpoint string regions, err := detector.DetectRegions() if errors.IsNotFound(err) { // It's not an error to have no regions. If the // provider does not support regions, then we // reinterpret the supplied region name as the // cloud's endpoint. This enables the user to // supply, for example, maas/<IP> or manual/<IP>. if c.Region != "" { ctx.Verbosef("interpreting %q as the cloud endpoint", c.Region) cloudEndpoint = c.Region c.Region = "" } } else if err != nil { return errors.Annotatef(err, "detecting regions for %q cloud provider", c.Cloud, ) } schemas := provider.CredentialSchemas() authTypes := make([]jujucloud.AuthType, 0, len(schemas)) for authType := range schemas { authTypes = append(authTypes, authType) } // Since we are iterating over a map, lets sort the authTypes so // they are always in a consistent order. sort.Sort(jujucloud.AuthTypes(authTypes)) cloud = &jujucloud.Cloud{ Type: c.Cloud, AuthTypes: authTypes, Endpoint: cloudEndpoint, Regions: regions, } } else if err != nil { return errors.Trace(err) } if err := checkProviderType(cloud.Type); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } provider, err := environs.Provider(cloud.Type) if err != nil { return errors.Trace(err) } // Custom clouds may not have explicitly declared support for any auth- // types, in which case we'll assume that they support everything that // the provider supports. if len(cloud.AuthTypes) == 0 { for authType := range provider.CredentialSchemas() { cloud.AuthTypes = append(cloud.AuthTypes, authType) } } // Get the credentials and region name. store := c.ClientStore() var detectedCredentialName string credential, credentialName, regionName, err := modelcmd.GetCredentials( ctx, store, modelcmd.GetCredentialsParams{ Cloud: *cloud, CloudName: c.Cloud, CloudRegion: c.Region, CredentialName: c.CredentialName, }, ) if errors.Cause(err) == modelcmd.ErrMultipleCredentials { return ambiguousCredentialError } if errors.IsNotFound(err) && c.CredentialName == "" { // No credential was explicitly specified, and no credential // was found in credentials.yaml; have the provider detect // credentials from the environment. ctx.Verbosef("no credentials found, checking environment") detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type) if errors.Cause(err) == modelcmd.ErrMultipleCredentials { return ambiguousDetectedCredentialError } else if err != nil { return errors.Trace(err) } // We have one credential so extract it from the map. var oneCredential jujucloud.Credential for detectedCredentialName, oneCredential = range detected.AuthCredentials { } credential = &oneCredential regionName = c.Region if regionName == "" { regionName = detected.DefaultRegion } logger.Debugf( "authenticating with region %q and credential %q (%v)", regionName, detectedCredentialName, credential.Label, ) logger.Tracef("credential: %v", credential) } else if err != nil { return errors.Trace(err) } region, err := getRegion(cloud, c.Cloud, regionName) if err != nil { fmt.Fprintf(ctx.GetStderr(), "%s\n\nSpecify an alternative region, or try %q.", err, "juju update-clouds", ) return cmd.ErrSilent } controllerModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } controllerUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } // Create a model config, and split out any controller // and bootstrap config attributes. modelConfigAttrs := map[string]interface{}{ "type": cloud.Type, "name": bootstrap.ControllerModelName, config.UUIDKey: controllerModelUUID.String(), } userConfigAttrs, err := c.config.ReadAttrs(ctx) if err != nil { return errors.Trace(err) } // The provider may define some custom attributes specific // to the provider. These will be added to the model config. providerAttrs := make(map[string]interface{}) if ps, ok := provider.(config.ConfigSchemaSource); ok { for attr := range ps.ConfigSchema() { if v, ok := userConfigAttrs[attr]; ok { providerAttrs[attr] = v } } fields := schema.FieldMap(ps.ConfigSchema(), ps.ConfigDefaults()) if coercedAttrs, err := fields.Coerce(providerAttrs, nil); err != nil { return errors.Annotatef(err, "invalid attribute value(s) for %v cloud", cloud.Type) } else { providerAttrs = coercedAttrs.(map[string]interface{}) } } logger.Debugf("provider attrs: %v", providerAttrs) for k, v := range userConfigAttrs { modelConfigAttrs[k] = v } // Provider specific attributes are either already specified in model // config (but may have been coerced), or were not present. Either way, // copy them in. for k, v := range providerAttrs { modelConfigAttrs[k] = v } bootstrapConfigAttrs := make(map[string]interface{}) controllerConfigAttrs := make(map[string]interface{}) // Based on the attribute names in clouds.yaml, create // a map of shared config for all models on this cloud. inheritedControllerAttrs := make(map[string]interface{}) for k, v := range cloud.Config { switch { case bootstrap.IsBootstrapAttribute(k): bootstrapConfigAttrs[k] = v continue case controller.ControllerOnlyAttribute(k): controllerConfigAttrs[k] = v continue } inheritedControllerAttrs[k] = v } for k, v := range modelConfigAttrs { switch { case bootstrap.IsBootstrapAttribute(k): bootstrapConfigAttrs[k] = v delete(modelConfigAttrs, k) case controller.ControllerOnlyAttribute(k): controllerConfigAttrs[k] = v delete(modelConfigAttrs, k) } } bootstrapConfig, err := bootstrap.NewConfig(bootstrapConfigAttrs) if err != nil { return errors.Annotate(err, "constructing bootstrap config") } controllerConfig, err := controller.NewConfig( controllerUUID.String(), bootstrapConfig.CACert, controllerConfigAttrs, ) if err != nil { return errors.Annotate(err, "constructing controller config") } if err := common.FinalizeAuthorizedKeys(ctx, modelConfigAttrs); err != nil { return errors.Annotate(err, "finalizing authorized-keys") } logger.Debugf("preparing controller with config: %v", modelConfigAttrs) // Read existing current controller so we can clean up on error. var oldCurrentController string oldCurrentController, err = store.CurrentController() if errors.IsNotFound(err) { oldCurrentController = "" } else if err != nil { return errors.Annotate(err, "error reading current controller") } defer func() { if resultErr == nil || errors.IsAlreadyExists(resultErr) { return } if oldCurrentController != "" { if err := store.SetCurrentController(oldCurrentController); err != nil { logger.Errorf( "cannot reset current controller to %q: %v", oldCurrentController, err, ) } } if err := store.RemoveController(c.controllerName); err != nil { logger.Errorf( "cannot destroy newly created controller %q details: %v", c.controllerName, err, ) } }() bootstrapModelConfig := make(map[string]interface{}) for k, v := range inheritedControllerAttrs { bootstrapModelConfig[k] = v } for k, v := range modelConfigAttrs { bootstrapModelConfig[k] = v } // Add in any default attribute values if not already // specified, making the recorded bootstrap config // immutable to changes in Juju. for k, v := range config.ConfigDefaults() { if _, ok := bootstrapModelConfig[k]; !ok { bootstrapModelConfig[k] = v } } environ, err := bootstrapPrepare( modelcmd.BootstrapContext(ctx), store, bootstrap.PrepareParams{ ModelConfig: bootstrapModelConfig, ControllerConfig: controllerConfig, ControllerName: c.controllerName, Cloud: environs.CloudSpec{ Type: cloud.Type, Name: c.Cloud, Region: region.Name, Endpoint: region.Endpoint, IdentityEndpoint: region.IdentityEndpoint, StorageEndpoint: region.StorageEndpoint, Credential: credential, }, CredentialName: credentialName, AdminSecret: bootstrapConfig.AdminSecret, }, ) if err != nil { return errors.Trace(err) } // Set the current model to the initial hosted model. if err := store.UpdateModel(c.controllerName, c.hostedModelName, jujuclient.ModelDetails{ hostedModelUUID.String(), }); err != nil { return errors.Trace(err) } if err := store.SetCurrentModel(c.controllerName, c.hostedModelName); err != nil { return errors.Trace(err) } // Set the current controller so "juju status" can be run while // bootstrapping is underway. if err := store.SetCurrentController(c.controllerName); err != nil { return errors.Trace(err) } cloudRegion := c.Cloud if region.Name != "" { cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) } ctx.Infof( "Creating Juju controller %q on %s", c.controllerName, cloudRegion, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil { if c.KeepBrokenEnvironment { ctx.Infof(` bootstrap failed but --keep-broken was specified so resources are not being destroyed. When you have finished diagnosing the problem, remember to clean up the failed controller. See `[1:] + "`juju kill-controller`" + `.`) } else { handleBootstrapError(ctx, resultErr, func() error { return environsDestroy( c.controllerName, environ, store, ) }) } } }() // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // Merge environ and bootstrap-specific constraints. constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return errors.Trace(err) } bootstrapConstraints, err := constraintsValidator.Merge( c.Constraints, c.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) hostedModelConfig := map[string]interface{}{ "name": c.hostedModelName, config.UUIDKey: hostedModelUUID.String(), } for k, v := range inheritedControllerAttrs { hostedModelConfig[k] = v } // We copy across any user supplied attributes to the hosted model config. // But only if the attributes have not been removed from the controller // model config as part of preparing the controller model. controllerModelConfigAttrs := environ.Config().AllAttrs() for k, v := range userConfigAttrs { if _, ok := controllerModelConfigAttrs[k]; ok { hostedModelConfig[k] = v } } // Ensure that certain config attributes are not included in the hosted // model config. These attributes may be modified during bootstrap; by // removing them from this map, we ensure the modified values are // inherited. delete(hostedModelConfig, config.AuthorizedKeysKey) delete(hostedModelConfig, config.AgentVersionKey) // Check whether the Juju GUI must be installed in the controller. // Leaving this value empty means no GUI will be installed. var guiDataSourceBaseURL string if !c.noGUI { guiDataSourceBaseURL = common.GUIDataSourceBaseURL() } if credentialName == "" { // credentialName will be empty if the credential was detected. // We must supply a name for the credential in the database, // so choose one. credentialName = detectedCredentialName } err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ ModelConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, BuildAgent: c.BuildAgent, BuildAgentTarball: sync.BuildAgentTarball, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, Cloud: *cloud, CloudName: c.Cloud, CloudRegion: region.Name, CloudCredential: credential, CloudCredentialName: credentialName, ControllerConfig: controllerConfig, ControllerInheritedConfig: inheritedControllerAttrs, RegionInheritedConfig: cloud.RegionConfig, HostedModelConfig: hostedModelConfig, GUIDataSourceBaseURL: guiDataSourceBaseURL, AdminSecret: bootstrapConfig.AdminSecret, CAPrivateKey: bootstrapConfig.CAPrivateKey, DialOpts: environs.BootstrapDialOpts{ Timeout: bootstrapConfig.BootstrapTimeout, RetryDelay: bootstrapConfig.BootstrapRetryDelay, AddressesDelay: bootstrapConfig.BootstrapAddressesDelay, }, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } if err := c.SetModelName(modelcmd.JoinModelName(c.controllerName, c.hostedModelName)); err != nil { return errors.Trace(err) } agentVersion := jujuversion.Current if c.AgentVersion != nil { agentVersion = *c.AgentVersion } err = common.SetBootstrapEndpointAddress(c.ClientStore(), c.controllerName, agentVersion, controllerConfig.APIPort(), environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return waitForAgentInitialisation(ctx, &c.ModelCommandBase, c.controllerName, c.hostedModelName) }