// 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.Environment.Id(); uuid == "" { return nil, errors.Trace(requiredError("environment")) } else if !names.IsValidEnvironment(uuid) { return nil, errors.Errorf("%q is not a valid environment 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, environment: configParams.Environment, 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 *useEnvironmentCommand) Init(args []string) error { if len(args) == 0 || strings.TrimSpace(args[0]) == "" { return errors.New("no environment 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.EnvName = 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.EnvName = bits[1] } // Environment 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.IsValidEnvironment(c.EnvName) { c.EnvUUID, c.EnvName = c.EnvName, "" } return cmd.CheckEmpty(args) }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(store configstore.Storage, info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, 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 environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } else { // For backwards-compatibility, we have to allow connections // with an empty UUID. Login will work for the same reasons. logger.Warningf("ignoring invalid API endpoint environment UUID %v", endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// 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 environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } 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 }
// validateEnvironUUID is the common validator for the various // apiserver components that need to check for a valid environment // UUID. An empty envUUID means that the connection has come in at // the root of the URL space and refers to the state server // environment. // // It returns the validated environment UUID. func validateEnvironUUID(args validateArgs) (string, error) { ssState := args.statePool.SystemState() if args.envUUID == "" { // We allow the environUUID to be empty for 2 cases // 1) Compatibility with older clients // 2) TODO: server a limited API at the root (empty envUUID) // with just the user manager and environment manager // if the connection comes over a sufficiently up to date // login command. if args.strict { return "", errors.Trace(common.UnknownEnvironmentError(args.envUUID)) } logger.Debugf("validate env uuid: empty envUUID") return ssState.EnvironUUID(), nil } if args.envUUID == ssState.EnvironUUID() { logger.Debugf("validate env uuid: state server environment - %s", args.envUUID) return args.envUUID, nil } if args.stateServerEnvOnly { return "", errors.Unauthorizedf("requested environment %q is not the state server environment", args.envUUID) } if !names.IsValidEnvironment(args.envUUID) { return "", errors.Trace(common.UnknownEnvironmentError(args.envUUID)) } envTag := names.NewEnvironTag(args.envUUID) if _, err := ssState.GetEnvironment(envTag); err != nil { return "", errors.Wrap(err, common.UnknownEnvironmentError(args.envUUID)) } logger.Debugf("validate env uuid: %s", args.envUUID) return args.envUUID, nil }
func dropEnvUUID(id string) string { fullID := id parts := strings.SplitN(fullID, ":", 2) if len(parts) == 2 { if names.IsValidEnvironment(parts[0]) { fullID = parts[1] } } return fullID }
// 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 apiState, info configstore.EnvironInfo, apiInfo *api.Info) (err error) { defer errors.DeferredAnnotatef(&err, "failed to cache API credentials") var environUUID string if names.IsValidEnvironment(apiInfo.EnvironTag.Id()) { environUUID = apiInfo.EnvironTag.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 environment UUID %v", apiInfo.EnvironTag.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), EnvironUUID: environUUID, } 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() }
// validateEnvironUUID is the common validator for the various apiserver // components that need to check for a valid environment UUID. // An empty envUUID means that the connection has come in at the root // of the URL space and refers to the state server environment. // The *state.State parameter is expected to be the state server State // connection. The return *state.State is a connection for the specified // environment UUID if the UUID refers to an environment contained in the // database. If the bool return value is true, the state connection must // be closed by the caller at the end of serving the client connection. func validateEnvironUUID(args validateArgs) (*state.State, bool, error) { if args.envUUID == "" { // We allow the environUUID to be empty for 2 cases // 1) Compatibility with older clients // 2) TODO: server a limited API at the root (empty envUUID) // with just the user manager and environment manager // if the connection comes over a sufficiently up to date // login command. if args.strict { return nil, false, errors.Trace(common.UnknownEnvironmentError(args.envUUID)) } logger.Debugf("validate env uuid: empty envUUID") return args.st, false, nil } if args.envUUID == args.st.EnvironUUID() { logger.Debugf("validate env uuid: state server environment - %s", args.envUUID) return args.st, false, nil } if args.stateServerEnvOnly { return nil, false, errors.Unauthorizedf("requested environment %q is not the state server environment", args.envUUID) } if !names.IsValidEnvironment(args.envUUID) { return nil, false, errors.Trace(common.UnknownEnvironmentError(args.envUUID)) } envTag := names.NewEnvironTag(args.envUUID) if env, err := args.st.GetEnvironment(envTag); err != nil { return nil, false, errors.Wrap(err, common.UnknownEnvironmentError(args.envUUID)) } else if env.Life() != state.Alive { return nil, false, errors.Errorf("environment %q is no longer live", args.envUUID) } logger.Debugf("validate env uuid: %s", args.envUUID) result, err := args.st.ForEnviron(envTag) if err != nil { return nil, false, errors.Trace(err) } return result, true, nil }
// 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 apiOpenFunc, stop <-chan struct{}) (apiState, 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 environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// Load causes all recorded collections to be created and indexed as specified; // the returned Database will filter queries and transactions according to the // suppplied environment UUID. func (schema collectionSchema) Load(db *mgo.Database, environUUID string) (Database, error) { if !names.IsValidEnvironment(environUUID) { return nil, errors.New("invalid environment 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, environUUID: environUUID, }, nil }