// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(info configstore.EnvironInfo, apiOpen api.OpenFunc, stop <-chan struct{}, bClient *httpbakery.Client) (api.Connection, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var modelTag names.ModelTag if names.IsValidModel(endpoint.ModelUUID) { modelTag = names.NewModelTag(endpoint.ModelUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, ModelTag: modelTag, } if apiInfo.Tag == nil { apiInfo.UseMacaroons = true } dialOpts := api.DefaultDialOpts() dialOpts.BakeryClient = bClient st, err := apiOpen(apiInfo, dialOpts) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// Validate returns an error if the TargetInfo contains bad data. Nil // is returned otherwise. func (info *TargetInfo) Validate() error { if !names.IsValidModel(info.ControllerTag.Id()) { return errors.NotValidf("ControllerTag") } if len(info.Addrs) < 1 { return errors.NotValidf("empty Addrs") } for _, addr := range info.Addrs { _, err := network.ParseHostPort(addr) if err != nil { return errors.NotValidf("%q in Addrs", addr) } } if info.CACert == "" { return errors.NotValidf("empty CACert") } if info.AuthTag.Id() == "" { return errors.NotValidf("empty AuthTag") } if info.Password == "" { return errors.NotValidf("empty Password") } return nil }
// NewAgentConfig returns a new config object suitable for use for a // machine or unit agent. func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) { if configParams.Paths.DataDir == "" { return nil, errors.Trace(requiredError("data directory")) } if configParams.Tag == nil { return nil, errors.Trace(requiredError("entity tag")) } switch configParams.Tag.(type) { case names.MachineTag, names.UnitTag: // these are the only two type of tags that can represent an agent default: return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag) } if configParams.UpgradedToVersion == version.Zero { return nil, errors.Trace(requiredError("upgradedToVersion")) } if configParams.Password == "" { return nil, errors.Trace(requiredError("password")) } if uuid := configParams.Model.Id(); uuid == "" { return nil, errors.Trace(requiredError("model")) } else if !names.IsValidModel(uuid) { return nil, errors.Errorf("%q is not a valid model uuid", uuid) } if len(configParams.CACert) == 0 { return nil, errors.Trace(requiredError("CA certificate")) } // Note that the password parts of the state and api information are // blank. This is by design. config := &configInternal{ paths: NewPathsWithDefaults(configParams.Paths), jobs: configParams.Jobs, upgradedToVersion: configParams.UpgradedToVersion, tag: configParams.Tag, nonce: configParams.Nonce, model: configParams.Model, caCert: configParams.CACert, oldPassword: configParams.Password, values: configParams.Values, preferIPv6: configParams.PreferIPv6, } if len(configParams.StateAddresses) > 0 { config.stateDetails = &connectionDetails{ addresses: configParams.StateAddresses, } } if len(configParams.APIAddresses) > 0 { config.apiDetails = &connectionDetails{ addresses: configParams.APIAddresses, } } if err := config.check(); err != nil { return nil, err } if config.values == nil { config.values = make(map[string]string) } config.configFilePath = ConfigPath(config.paths.DataDir, config.tag) return config, nil }
// SetFlags implements Command.Init. func (c *useModelCommand) Init(args []string) error { if len(args) == 0 || strings.TrimSpace(args[0]) == "" { return errors.New("no model supplied") } name, args := args[0], args[1:] // First check to see if an owner has been specified. bits := strings.SplitN(name, "/", 2) switch len(bits) { case 1: // No user specified c.ModelName = bits[0] case 2: owner := bits[0] if names.IsValidUser(owner) { c.Owner = owner } else { return errors.Errorf("%q is not a valid user", owner) } c.ModelName = bits[1] } // Model names can generally be anything, but we take a good // stab at trying to determine if the user has specified a UUID // instead of a name. For now, we only accept a properly formatted UUID, // which means one with dashes in the right place. if names.IsValidModel(c.ModelName) { c.ModelUUID, c.ModelName = c.ModelName, "" } return cmd.CheckEmpty(args) }
// validateModelUUID is the common validator for the various // apiserver components that need to check for a valid model // UUID. An empty modelUUID means that the connection has come in at // the root of the URL space and refers to the controller // model. // // It returns the validated model UUID. func validateModelUUID(args validateArgs) (string, error) { ssState := args.statePool.SystemState() if args.modelUUID == "" { // We allow the modelUUID to be empty for 2 cases // 1) Compatibility with older clients // 2) TODO: server a limited API at the root (empty modelUUID) // with just the user manager and model manager // if the connection comes over a sufficiently up to date // login command. if args.strict { return "", errors.Trace(common.UnknownModelError(args.modelUUID)) } logger.Debugf("validate model uuid: empty modelUUID") return ssState.ModelUUID(), nil } if args.modelUUID == ssState.ModelUUID() { logger.Debugf("validate model uuid: controller model - %s", args.modelUUID) return args.modelUUID, nil } if args.controllerModelOnly { return "", errors.Unauthorizedf("requested model %q is not the controller model", args.modelUUID) } if !names.IsValidModel(args.modelUUID) { return "", errors.Trace(common.UnknownModelError(args.modelUUID)) } modelTag := names.NewModelTag(args.modelUUID) if _, err := ssState.GetModel(modelTag); err != nil { return "", errors.Wrap(err, common.UnknownModelError(args.modelUUID)) } logger.Debugf("validate model uuid: %s", args.modelUUID) return args.modelUUID, nil }
// WrapAgent wraps an agent.Agent (expected to be a machine agent, fwiw) // such that its references the supplied model rather than the controller // model; its config is immutable; and it doesn't use OldPassword. // // It's a strong sign that the agent package needs some work... func WrapAgent(a agent.Agent, uuid string) (agent.Agent, error) { if !names.IsValidModel(uuid) { return nil, errors.NotValidf("model uuid %q", uuid) } return &modelAgent{ Agent: a, uuid: uuid, }, nil }
func dropModelUUID(id string) string { fullID := id parts := strings.SplitN(fullID, ":", 2) if len(parts) == 2 { if names.IsValidModel(parts[0]) { fullID = parts[1] } } return fullID }
// Validate performs sanity checks on the migration configuration it // holds. func (s *ModelMigrationSpec) Validate() error { if !names.IsValidModel(s.ModelUUID) { return errors.NotValidf("model UUID") } if !names.IsValidModel(s.TargetControllerUUID) { return errors.NotValidf("controller UUID") } if len(s.TargetAddrs) < 1 { return errors.NotValidf("empty target API addresses") } if s.TargetCACert == "" { return errors.NotValidf("empty target CA cert") } if !names.IsValidUser(s.TargetUser) { return errors.NotValidf("target user") } if s.TargetPassword == "" { return errors.NotValidf("empty target password") } return nil }
// cacheAPIInfo updates the local environment settings (.jenv file) // with the provided apiInfo, assuming we've just successfully // connected to the API server. func cacheAPIInfo(st api.Connection, info configstore.EnvironInfo, apiInfo *api.Info) (err error) { defer errors.DeferredAnnotatef(&err, "failed to cache API credentials") var modelUUID string if names.IsValidModel(apiInfo.ModelTag.Id()) { modelUUID = apiInfo.ModelTag.Id() } else { // For backwards-compatibility, we have to allow connections // with an empty UUID. Login will work for the same reasons. logger.Warningf("ignoring invalid cached API endpoint model UUID %v", apiInfo.ModelTag.Id()) } hostPorts, err := network.ParseHostPorts(apiInfo.Addrs...) if err != nil { return errors.Annotatef(err, "invalid API addresses %v", apiInfo.Addrs) } addrConnectedTo, err := network.ParseHostPorts(st.Addr()) if err != nil { // Should never happen, since we've just connected with it. return errors.Annotatef(err, "invalid API address %q", st.Addr()) } addrs, hostnames, addrsChanged := PrepareEndpointsForCaching( info, [][]network.HostPort{hostPorts}, addrConnectedTo[0], ) endpoint := configstore.APIEndpoint{ CACert: string(apiInfo.CACert), ModelUUID: modelUUID, } if addrsChanged { endpoint.Addresses = addrs endpoint.Hostnames = hostnames } info.SetAPIEndpoint(endpoint) tag, ok := apiInfo.Tag.(names.UserTag) if !ok { return errors.Errorf("apiInfo.Tag was of type %T, expecting names.UserTag", apiInfo.Tag) } info.SetAPICredentials(configstore.APICredentials{ // This looks questionable. We have a tag, say "user-admin", but then only // the Id portion of the tag is recorded, "admin", so this is really a // username, not a tag, and cannot be reconstructed accurately. User: tag.Id(), Password: apiInfo.Password, }) return info.Write() }
func (c *Client) modifyModelUser(action params.ModelAction, user, access string, modelUUIDs []string) error { var args params.ModifyModelAccessRequest if !names.IsValidUser(user) { return errors.Errorf("invalid username: %q", user) } userTag := names.NewUserTag(user) accessPermission, err := ParseModelAccess(access) if err != nil { return errors.Trace(err) } for _, model := range modelUUIDs { if !names.IsValidModel(model) { return errors.Errorf("invalid model: %q", model) } modelTag := names.NewModelTag(model) args.Changes = append(args.Changes, params.ModifyModelAccess{ UserTag: userTag.String(), Action: action, Access: accessPermission, ModelTag: modelTag.String(), }) } var result params.ErrorResults err = c.facade.FacadeCall("ModifyModelAccess", args, &result) if err != nil { return errors.Trace(err) } if len(result.Results) != len(args.Changes) { return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results)) } for i, r := range result.Results { if r.Error != nil && r.Error.Code == params.CodeAlreadyExists { logger.Warningf("model %q is already shared with %q", modelUUIDs[i], userTag.Canonical()) result.Results[i].Error = nil } } return result.Combine() }
// Load causes all recorded collections to be created and indexed as specified; // the returned Database will filter queries and transactions according to the // suppplied model UUID. func (schema collectionSchema) Load(db *mgo.Database, modelUUID string) (Database, error) { if !names.IsValidModel(modelUUID) { return nil, errors.New("invalid model UUID") } for name, info := range schema { rawCollection := db.C(name) if spec := info.explicitCreate; spec != nil { if err := createCollection(rawCollection, spec); err != nil { message := fmt.Sprintf("cannot create collection %q", name) return nil, maybeUnauthorized(err, message) } } for _, index := range info.indexes { if err := rawCollection.EnsureIndex(index); err != nil { return nil, maybeUnauthorized(err, "cannot create index") } } } return &database{ raw: db, schema: schema, modelUUID: modelUUID, }, nil }