// SetInstanceInfo is used to provision a machine and in one steps set // it's instance id, nonce, hardware characteristics, add networks and // network interfaces as needed. // // TODO(dimitern) Do all the operations described in a single // transaction, rather than using separate calls. Alternatively, // we can add all the things to create/set in a document in some // collection and have a worker that takes care of the actual work. // Merge SetProvisioned() in here or drop it at that point. func (m *Machine) SetInstanceInfo( id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics, networks []NetworkInfo, interfaces []NetworkInterfaceInfo, volumes map[names.VolumeTag]VolumeInfo, volumeAttachments map[names.VolumeTag]VolumeAttachmentInfo, ) error { // Add the networks and interfaces first. for _, network := range networks { _, err := m.st.AddNetwork(network) if err != nil && errors.IsAlreadyExists(err) { // Ignore already existing networks. continue } else if err != nil { return errors.Trace(err) } } for _, iface := range interfaces { _, err := m.AddNetworkInterface(iface) if err != nil && errors.IsAlreadyExists(err) { // Ignore already existing network interfaces. continue } else if err != nil { return errors.Trace(err) } } if err := setProvisionedVolumeInfo(m.st, volumes); err != nil { return errors.Trace(err) } if err := setMachineVolumeAttachmentInfo(m.st, m.Id(), volumeAttachments); err != nil { return errors.Trace(err) } return m.SetProvisioned(id, nonce, characteristics) }
// SetInstanceInfo is used to provision a machine and in one steps set // it's instance id, nonce, hardware characteristics, add networks and // network interfaces as needed. // // TODO(dimitern) Do all the operations described in a single // transaction, rather than using separate calls. Alternatively, // we can add all the things to create/set in a document in some // collection and have a worker that takes care of the actual work. // Merge SetProvisioned() in here or drop it at that point. func (m *Machine) SetInstanceInfo( id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics, networks []NetworkInfo, interfaces []NetworkInterfaceInfo) error { // Add the networks and interfaces first. for _, network := range networks { _, err := m.st.AddNetwork(network) if err != nil && errors.IsAlreadyExists(err) { // Ignore already existing networks. continue } else if err != nil { return err } } for _, iface := range interfaces { _, err := m.AddNetworkInterface(iface) if err != nil && errors.IsAlreadyExists(err) { // Ignore already existing network interfaces. continue } else if err != nil { return err } } return m.SetProvisioned(id, nonce, characteristics) }
func (s *EnvironSuite) TestNewEnvironmentSameUserSameNameFails(c *gc.C) { cfg, _ := s.createTestEnvConfig(c) owner := s.factory.MakeUser(c, nil).UserTag() // Create the first environment. _, st1, err := s.State.NewEnvironment(cfg, owner) c.Assert(err, jc.ErrorIsNil) defer st1.Close() // Attempt to create another environment with a different UUID but the // same owner and name as the first. newUUID, err := utils.NewUUID() c.Assert(err, jc.ErrorIsNil) cfg2 := testing.CustomEnvironConfig(c, testing.Attrs{ "name": cfg.Name(), "uuid": newUUID.String(), }) _, _, err = s.State.NewEnvironment(cfg2, owner) errMsg := fmt.Sprintf("environment %q for %s already exists", cfg2.Name(), owner.Username()) c.Assert(err, gc.ErrorMatches, errMsg) c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) // Remove the first environment. err = st1.RemoveAllEnvironDocs() c.Assert(err, jc.ErrorIsNil) // We should now be able to create the other environment. env2, st2, err := s.State.NewEnvironment(cfg2, owner) c.Assert(err, jc.ErrorIsNil) defer st2.Close() c.Assert(env2, gc.NotNil) c.Assert(st2, gc.NotNil) }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } logger.Tracef("server RPC error %v", errors.Details(err)) msg := err.Error() // Skip past annotations when looking for the code. err = errors.Cause(err) code, ok := singletonCode(err) var info *params.ErrorInfo switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsUserNotFound(err): code = params.CodeUserNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case errors.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case state.IsHasHostedModelsError(err): code = params.CodeHasHostedModels case isNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): code = params.CodeNotProvisioned case IsUpgradeInProgressError(err): code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage case isUnknownModelError(err): code = params.CodeModelNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported case errors.IsBadRequest(err): code = params.CodeBadRequest case errors.IsMethodNotAllowed(err): code = params.CodeMethodNotAllowed default: if err, ok := err.(*DischargeRequiredError); ok { code = params.CodeDischargeRequired info = ¶ms.ErrorInfo{ Macaroon: err.Macaroon, // One macaroon fits all. MacaroonPath: "/", } break } code = params.ErrCode(err) } return ¶ms.Error{ Message: msg, Code: code, Info: info, } }
func grantControllerAccess(accessor *state.State, targetUserTag, apiUser names.UserTag, access permission.Access) error { _, err := accessor.AddControllerUser(state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: access}) if errors.IsAlreadyExists(err) { controllerTag := accessor.ControllerTag() controllerUser, err := accessor.UserAccess(targetUserTag, controllerTag) if errors.IsNotFound(err) { // Conflicts with prior check, must be inconsistent state. err = txn.ErrExcessiveContention } if err != nil { return errors.Annotate(err, "could not look up controller access for user") } // Only set access if greater access is being granted. if controllerUser.Access.EqualOrGreaterControllerAccessThan(access) { return errors.Errorf("user already has %q access or greater", access) } if _, err = accessor.SetUserAccess(controllerUser.UserTag, controllerUser.Object, access); err != nil { return errors.Annotate(err, "could not set controller access for user") } return nil } if err != nil { return errors.Trace(err) } return nil }
func (s *ModelSuite) TestNewModelSameUserSameNameFails(c *gc.C) { cfg, _ := s.createTestModelConfig(c) owner := s.Factory.MakeUser(c, nil).UserTag() // Create the first model. _, st1, err := s.State.NewModel(state.ModelArgs{ CloudName: "dummy", CloudRegion: "dummy-region", Config: cfg, Owner: owner, StorageProviderRegistry: storage.StaticProviderRegistry{}, }) c.Assert(err, jc.ErrorIsNil) defer st1.Close() // Attempt to create another model with a different UUID but the // same owner and name as the first. newUUID, err := utils.NewUUID() c.Assert(err, jc.ErrorIsNil) cfg2 := testing.CustomModelConfig(c, testing.Attrs{ "name": cfg.Name(), "uuid": newUUID.String(), }) _, _, err = s.State.NewModel(state.ModelArgs{ CloudName: "dummy", CloudRegion: "dummy-region", Config: cfg2, Owner: owner, StorageProviderRegistry: storage.StaticProviderRegistry{}, }) errMsg := fmt.Sprintf("model %q for %s already exists", cfg2.Name(), owner.Canonical()) c.Assert(err, gc.ErrorMatches, errMsg) c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) // Remove the first model. env1, err := st1.Model() c.Assert(err, jc.ErrorIsNil) err = env1.Destroy() c.Assert(err, jc.ErrorIsNil) // Destroy only sets the model to dying and RemoveAllModelDocs can // only be called on a dead model. Normally, the environ's lifecycle // would be set to dead after machines and services have been cleaned up. err = state.SetModelLifeDead(st1, env1.ModelTag().Id()) c.Assert(err, jc.ErrorIsNil) err = st1.RemoveAllModelDocs() c.Assert(err, jc.ErrorIsNil) // We should now be able to create the other model. env2, st2, err := s.State.NewModel(state.ModelArgs{ CloudName: "dummy", CloudRegion: "dummy-region", Config: cfg2, Owner: owner, StorageProviderRegistry: storage.StaticProviderRegistry{}, }) c.Assert(err, jc.ErrorIsNil) defer st2.Close() c.Assert(env2, gc.NotNil) c.Assert(st2, gc.NotNil) }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } code, ok := singletonCode(err) switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case state.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case IsNoAddressSetError(err): code = params.CodeNoAddressSet case state.IsNotProvisionedError(err): code = params.CodeNotProvisioned case IsUnknownEnviromentError(err): code = params.CodeNotFound default: code = params.ErrCode(err) } return ¶ms.Error{ Message: err.Error(), Code: code, } }
func (s *UserSuite) TestCaseSensitiveUsersErrors(c *gc.C) { s.Factory.MakeUser(c, &factory.UserParams{Name: "Bob"}) _, err := s.State.AddUser( "boB", "ignored", "ignored", "ignored") c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) c.Assert(err, gc.ErrorMatches, "user already exists") }
func (s *EnvUserSuite) TestCaseSensitiveEnvUserErrors(c *gc.C) { env, err := s.State.Environment() c.Assert(err, jc.ErrorIsNil) s.Factory.MakeEnvUser(c, &factory.EnvUserParams{User: "******"}) _, err = s.State.AddEnvironmentUser(names.NewUserTag("boB@ubuntuone"), env.Owner(), "") c.Assert(err, gc.ErrorMatches, `environment user "boB@ubuntuone" already exists`) c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) }
// PickNewAddress returns a new IPAddress that isn't in use for the subnet. // The address starts with AddressStateUnknown, for later allocation. // This will fail if the subnet is not alive. func (s *Subnet) PickNewAddress() (*IPAddress, error) { for { addr, err := s.attemptToPickNewAddress() if err == nil { return addr, err } if !errors.IsAlreadyExists(err) { return addr, err } } }
func (s *ModelUserSuite) TestCaseSensitiveModelUserErrors(c *gc.C) { model, err := s.State.Model() c.Assert(err, jc.ErrorIsNil) s.Factory.MakeModelUser(c, &factory.ModelUserParams{User: "******"}) _, err = s.State.AddModelUser(state.ModelUserSpec{ User: names.NewUserTag("boB@ubuntuone"), CreatedBy: model.Owner()}) c.Assert(err, gc.ErrorMatches, `model user "boB@ubuntuone" already exists`) c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) }
// Start implements Service. func (s *Service) Start() error { err := s.start() if errors.IsAlreadyExists(err) { logger.Debugf("service %q already running", s.Name()) return nil } else if err != nil { logger.Errorf("service %q failed to start: %v", s.Name(), err) return err } logger.Debugf("service %q successfully started", s.Name()) return nil }
// Install implements Service. func (s *Service) Install() error { if s.NoConf() { return s.errorf(nil, "missing conf") } err := s.install() if errors.IsAlreadyExists(err) { logger.Debugf("service %q already installed", s.Name()) return nil } else if err != nil { logger.Errorf("failed to install service %q: %v", s.Name(), err) return err } logger.Debugf("service %q successfully installed", s.Name()) return nil }
func (p *ProvisionerAPI) createOrFetchStateSubnet(subnetInfo network.SubnetInfo) (*state.Subnet, error) { stateSubnetInfo := state.SubnetInfo{ ProviderId: subnetInfo.ProviderId, CIDR: subnetInfo.CIDR, VLANTag: subnetInfo.VLANTag, AllocatableIPHigh: subnetInfo.AllocatableIPHigh.String(), AllocatableIPLow: subnetInfo.AllocatableIPLow.String(), } subnet, err := p.st.AddSubnet(stateSubnetInfo) if err != nil { if errors.IsAlreadyExists(err) { subnet, err = p.st.Subnet(subnetInfo.CIDR) } if err != nil { return subnet, errors.Trace(err) } } return subnet, nil }
func (s *EnvironSuite) TestNewEnvironmentSameUserSameNameFails(c *gc.C) { cfg, _ := s.createTestEnvConfig(c) owner := s.Factory.MakeUser(c, nil).UserTag() // Create the first environment. _, st1, err := s.State.NewEnvironment(cfg, owner) c.Assert(err, jc.ErrorIsNil) defer st1.Close() // Attempt to create another environment with a different UUID but the // same owner and name as the first. newUUID, err := utils.NewUUID() c.Assert(err, jc.ErrorIsNil) cfg2 := testing.CustomEnvironConfig(c, testing.Attrs{ "name": cfg.Name(), "uuid": newUUID.String(), }) _, _, err = s.State.NewEnvironment(cfg2, owner) errMsg := fmt.Sprintf("environment %q for %s already exists", cfg2.Name(), owner.Canonical()) c.Assert(err, gc.ErrorMatches, errMsg) c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) // Remove the first environment. env1, err := st1.Environment() c.Assert(err, jc.ErrorIsNil) err = env1.Destroy() c.Assert(err, jc.ErrorIsNil) // Destroy only sets the environment to dying and RemoveAllEnvironDocs can // only be called on a dead environment. Normally, the environ's lifecycle // would be set to dead after machines and services have been cleaned up. err = state.SetEnvLifeDead(st1, env1.EnvironTag().Id()) c.Assert(err, jc.ErrorIsNil) err = st1.RemoveAllEnvironDocs() c.Assert(err, jc.ErrorIsNil) // We should now be able to create the other environment. env2, st2, err := s.State.NewEnvironment(cfg2, owner) c.Assert(err, jc.ErrorIsNil) defer st2.Close() c.Assert(env2, gc.NotNil) c.Assert(st2, gc.NotNil) }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } msg := err.Error() // Skip past annotations when looking for the code. err = errors.Cause(err) code, ok := singletonCode(err) switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case errors.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case IsNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): code = params.CodeNotProvisioned case state.IsUpgradeInProgressError(err): code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage case IsUnknownEnviromentError(err): code = params.CodeNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported default: code = params.ErrCode(err) } return ¶ms.Error{ Message: msg, Code: code, } }
// 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) { 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 := provider.(environs.CloudRegionDetector) 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")) } regions, err := detector.DetectRegions() if err != nil && !errors.IsNotFound(err) { // It's not an error to have no regions. return errors.Annotatef(err, "detecting regions for %q cloud provider", c.Cloud, ) } cloud = &jujucloud.Cloud{ Type: c.Cloud, 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) } // Get the credentials and region name. store := c.ClientStore() credential, credentialName, regionName, err := modelcmd.GetCredentials( store, c.Region, c.CredentialName, c.Cloud, cloud.Type, ) 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 ambiguousCredentialError } else if err != nil { return errors.Trace(err) } // We have one credential so extract it from the map. var oneCredential jujucloud.Credential for _, oneCredential = range detected.AuthCredentials { } credential = &oneCredential regionName = c.Region if regionName == "" { regionName = detected.DefaultRegion } logger.Tracef("authenticating with region %q and %v", regionName, credential) } else if err != nil { return errors.Trace(err) } region, err := getRegion(cloud, c.Cloud, regionName) 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 an environment config from the cloud and credentials. configAttrs := map[string]interface{}{ "type": cloud.Type, "name": environs.ControllerModelName, config.UUIDKey: controllerUUID.String(), config.ControllerUUIDKey: controllerUUID.String(), } userConfigAttrs, err := c.config.ReadAttrs(ctx) if err != nil { return errors.Trace(err) } for k, v := range userConfigAttrs { configAttrs[k] = v } logger.Debugf("preparing controller with config: %v", configAttrs) // Read existing current controller, account, model so we can clean up on error. var oldCurrentController string oldCurrentController, err = modelcmd.ReadCurrentController() if err != nil { return errors.Annotate(err, "error reading current controller") } defer func() { if resultErr == nil || errors.IsAlreadyExists(resultErr) { return } if oldCurrentController != "" { if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil { logger.Warningf( "cannot reset current controller to %q: %v", oldCurrentController, err, ) } } if err := store.RemoveController(c.controllerName); err != nil { logger.Warningf( "cannot destroy newly created controller %q details: %v", c.controllerName, err, ) } }() environ, err := environsPrepare( modelcmd.BootstrapContext(ctx), store, environs.PrepareParams{ BaseConfig: configAttrs, ControllerName: c.controllerName, CloudName: c.Cloud, CloudRegion: region.Name, CloudEndpoint: region.Endpoint, CloudStorageEndpoint: region.StorageEndpoint, Credential: *credential, CredentialName: credentialName, }, ) if err != nil { return errors.Trace(err) } // Set the current model to the initial hosted model. accountName, err := store.CurrentAccount(c.controllerName) if err != nil { return errors.Trace(err) } if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{ hostedModelUUID.String(), }); err != nil { return errors.Trace(err) } if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil { return errors.Trace(err) } // Set the current controller so "juju status" can be run while // bootstrapping is underway. if err := modelcmd.WriteCurrentController(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 { logger.Warningf(` bootstrap failed but --keep-broken was specified so model is not being destroyed. When you are finished diagnosing the problem, remember to run juju destroy-model --force to clean up the model.`[1:]) } 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(), } // 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. controllerConfigAttrs := environ.Config().AllAttrs() for k, v := range userConfigAttrs { if _, ok := controllerConfigAttrs[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.AuthKeysConfig) 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() } err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ ModelConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, UploadTools: c.UploadTools, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, HostedModelConfig: hostedModelConfig, GUIDataSourceBaseURL: guiDataSourceBaseURL, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } if err := c.SetModelName(c.hostedModelName); err != nil { return errors.Trace(err) } err = c.setBootstrapEndpointAddress(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 c.waitForAgentInitialisation(ctx) }
// changeModelAccess performs the requested access grant or revoke action for the // specified user on the specified model. func changeModelAccess(accessor common.ModelManagerBackend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.Access, userIsAdmin bool) error { st, err := accessor.ForModel(modelTag) if err != nil { return errors.Annotate(err, "could not lookup model") } defer st.Close() if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil { return errors.Trace(err) } switch action { case params.GrantModelAccess: _, err = st.AddModelUser(modelTag.Id(), state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: access}) if errors.IsAlreadyExists(err) { modelUser, err := st.UserAccess(targetUserTag, modelTag) if errors.IsNotFound(err) { // Conflicts with prior check, must be inconsistent state. err = txn.ErrExcessiveContention } if err != nil { return errors.Annotate(err, "could not look up model access for user") } // Only set access if greater access is being granted. if modelUser.Access.EqualOrGreaterModelAccessThan(access) { return errors.Errorf("user already has %q access or greater", access) } if _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, access); err != nil { return errors.Annotate(err, "could not set model access for user") } return nil } return errors.Annotate(err, "could not grant model access") case params.RevokeModelAccess: switch access { case permission.ReadAccess: // Revoking read access removes all access. err := st.RemoveUserAccess(targetUserTag, modelTag) return errors.Annotate(err, "could not revoke model access") case permission.WriteAccess: // Revoking write access sets read-only. modelUser, err := st.UserAccess(targetUserTag, modelTag) if err != nil { return errors.Annotate(err, "could not look up model access for user") } _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.ReadAccess) return errors.Annotate(err, "could not set model access to read-only") case permission.AdminAccess: // Revoking admin access sets read-write. modelUser, err := st.UserAccess(targetUserTag, modelTag) if err != nil { return errors.Annotate(err, "could not look up model access for user") } _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.WriteAccess) return errors.Annotate(err, "could not set model access to read-write") default: return errors.Errorf("don't know how to revoke %q access", access) } default: return errors.Errorf("unknown action %q", action) } }
// ChangeModelAccess performs the requested access grant or revoke action for the // specified user on the specified model. func ChangeModelAccess(accessor Backend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.ModelAccess, userIsAdmin bool) error { st, err := accessor.ForModel(modelTag) if err != nil { return errors.Annotate(err, "could not lookup model") } defer st.Close() if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil { return errors.Trace(err) } stateAccess, err := resolveStateAccess(access) if err != nil { return errors.Annotate(err, "could not resolve model access") } switch action { case params.GrantModelAccess: _, err = st.AddModelUser(state.ModelUserSpec{User: targetUserTag, CreatedBy: apiUser, Access: stateAccess}) if errors.IsAlreadyExists(err) { modelUser, err := st.ModelUser(targetUserTag) if errors.IsNotFound(err) { // Conflicts with prior check, must be inconsistent state. err = txn.ErrExcessiveContention } if err != nil { return errors.Annotate(err, "could not look up model access for user") } // Only set access if greater access is being granted. if isGreaterAccess(modelUser.Access(), stateAccess) { err = modelUser.SetAccess(stateAccess) if err != nil { return errors.Annotate(err, "could not set model access for user") } } else { return errors.Errorf("user already has %q access", modelUser.Access()) } return nil } return errors.Annotate(err, "could not grant model access") case params.RevokeModelAccess: if stateAccess == state.ModelReadAccess { // Revoking read access removes all access. err := st.RemoveModelUser(targetUserTag) return errors.Annotate(err, "could not revoke model access") } else if stateAccess == state.ModelAdminAccess { // Revoking admin access sets read-only. modelUser, err := st.ModelUser(targetUserTag) if err != nil { return errors.Annotate(err, "could not look up model access for user") } err = modelUser.SetAccess(state.ModelReadAccess) return errors.Annotate(err, "could not set model access to read-only") } else { return errors.Errorf("don't know how to revoke %q access", stateAccess) } default: return errors.Errorf("unknown action %q", action) } }
// 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) }