// destroyInstances directly destroys all non-manager, // non-manual machine instances. func destroyInstances(st *state.State, machines []*state.Machine) error { var ids []instance.Id for _, m := range machines { if m.IsManager() { continue } if _, isContainer := m.ParentId(); isContainer { continue } manual, err := m.IsManual() if manual { continue } else if err != nil { return err } id, err := m.InstanceId() if err != nil { continue } ids = append(ids, id) } if len(ids) == 0 { return nil } envcfg, err := st.EnvironConfig() if err != nil { return err } env, err := environs.New(envcfg) if err != nil { return err } return env.StopInstances(ids...) }
// MachineConfig returns information from the environment config that is // needed for machine cloud-init (for non-state servers only). // It is exposed for testing purposes. // TODO(rog) fix environs/manual tests so they do not need to // call this, or move this elsewhere. func MachineConfig(st *state.State, machineId, nonce, dataDir string) (*cloudinit.MachineConfig, error) { environConfig, err := st.EnvironConfig() if err != nil { return nil, err } // Get the machine so we can get its series and arch. // If the Arch is not set in hardware-characteristics, // an error is returned. machine, err := st.Machine(machineId) if err != nil { return nil, err } hc, err := machine.HardwareCharacteristics() if err != nil { return nil, err } if hc.Arch == nil { return nil, fmt.Errorf("arch is not set for %q", machine.Tag()) } // Find the appropriate tools information. env, err := environs.New(environConfig) if err != nil { return nil, err } tools, err := findInstanceTools(env, machine.Series(), *hc.Arch) if err != nil { return nil, err } // Find the API endpoints. apiInfo, err := environs.APIInfo(env) if err != nil { return nil, err } auth := authentication.NewAuthenticator(st.MongoConnectionInfo(), apiInfo) mongoInfo, apiInfo, err := auth.SetupAuthentication(machine) if err != nil { return nil, err } // Find requested networks. networks, err := machine.RequestedNetworks() if err != nil { return nil, err } mcfg := environs.NewMachineConfig(machineId, nonce, networks, mongoInfo, apiInfo) if dataDir != "" { mcfg.DataDir = dataDir } mcfg.Tools = tools err = environs.FinishMachineConfig(mcfg, environConfig, constraints.Value{}) if err != nil { return nil, err } return mcfg, nil }
func addCharm(st *state.State, curl *charm.URL, ch charm.Charm) (*state.Charm, error) { var f *os.File name := charm.Quote(curl.String()) switch ch := ch.(type) { case *charm.CharmDir: var err error if f, err = ioutil.TempFile("", name); err != nil { return nil, err } defer os.Remove(f.Name()) defer f.Close() err = ch.ArchiveTo(f) if err != nil { return nil, fmt.Errorf("cannot bundle charm: %v", err) } if _, err := f.Seek(0, 0); err != nil { return nil, err } case *charm.CharmArchive: var err error if f, err = os.Open(ch.Path); err != nil { return nil, fmt.Errorf("cannot read charm bundle: %v", err) } defer f.Close() default: return nil, fmt.Errorf("unknown charm type %T", ch) } digest, size, err := utils.ReadSHA256(f) if err != nil { return nil, err } if _, err := f.Seek(0, 0); err != nil { return nil, err } cfg, err := st.EnvironConfig() if err != nil { return nil, err } env, err := environs.New(cfg) if err != nil { return nil, err } stor := env.Storage() if err := stor.Put(name, f, size); err != nil { return nil, fmt.Errorf("cannot put charm: %v", err) } ustr, err := stor.URL(name) if err != nil { return nil, fmt.Errorf("cannot get storage URL for charm: %v", err) } u, err := url.Parse(ustr) if err != nil { return nil, fmt.Errorf("cannot parse storage URL: %v", err) } sch, err := st.AddCharm(ch, curl, u, digest) if err != nil { return nil, fmt.Errorf("cannot add charm: %v", err) } return sch, nil }
// GetEnvironment returns the environs.Environ ("provider") associated // with the environment. func GetEnvironment(st *state.State) (environs.Environ, error) { envcfg, err := st.EnvironConfig() if err != nil { return nil, errors.Trace(err) } env, err := environs.New(envcfg) return env, errors.Trace(err) }
// GetStorage creates an Environ from the config in state and returns // its storage interface. func GetStorage(st *state.State) (storage.Storage, error) { envConfig, err := st.EnvironConfig() if err != nil { return nil, fmt.Errorf("cannot get environment config: %v", err) } env, err := New(envConfig) if err != nil { return nil, fmt.Errorf("cannot access environment: %v", err) } return env.Storage(), nil }
// New returns a new worker that maintains the mongo replica set // with respect to the given state. func New(st *state.State) (worker.Worker, error) { cfg, err := st.EnvironConfig() if err != nil { return nil, err } return newWorker(&stateShim{ State: st, mongoPort: cfg.StatePort(), apiPort: cfg.APIPort(), }, newPublisher(st, cfg.PreferIPv6())), nil }
// NewConnFromState returns a Conn that uses an Environ // made by reading the environment configuration. // The resulting Conn uses the given State - closing // it will close that State. func NewConnFromState(st *state.State) (*Conn, error) { cfg, err := st.EnvironConfig() if err != nil { return nil, err } environ, err := environs.New(cfg) if err != nil { return nil, err } return &Conn{ Environ: environ, State: st, }, nil }
// fetchAndCacheTools fetches tools with the specified version by searching for a URL // in simplestreams and GETting it, caching the result in toolstorage before returning // to the caller. func (h *toolsDownloadHandler) fetchAndCacheTools(v version.Binary, stor toolstorage.Storage, st *state.State) (io.ReadCloser, error) { envcfg, err := st.EnvironConfig() if err != nil { return nil, err } env, err := environs.New(envcfg) if err != nil { return nil, err } tools, err := envtools.FindExactTools(env, v.Number, v.Series, v.Arch) if err != nil { return nil, err } // No need to verify the server's identity because we verify the SHA-256 hash. logger.Infof("fetching %v tools from %v", v, tools.URL) resp, err := utils.GetNonValidatingHTTPClient().Get(tools.URL) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { msg := fmt.Sprintf("bad HTTP response: %v", resp.Status) if body, err := ioutil.ReadAll(resp.Body); err == nil { msg += fmt.Sprintf(" (%s)", bytes.TrimSpace(body)) } return nil, errors.New(msg) } data, sha256, err := readAndHash(resp.Body) if err != nil { return nil, err } if int64(len(data)) != tools.Size { return nil, errors.Errorf("size mismatch for %s", tools.URL) } if sha256 != tools.SHA256 { return nil, errors.Errorf("hash mismatch for %s", tools.URL) } // Cache tarball in toolstorage before returning. metadata := toolstorage.Metadata{ Version: v, Size: tools.Size, SHA256: tools.SHA256, } if err := stor.AddTools(bytes.NewReader(data), metadata); err != nil { return nil, errors.Annotate(err, "error caching tools") } return ioutil.NopCloser(bytes.NewReader(data)), nil }
// LegacyStorage creates an Environ from the config in state and returns // its provider storage interface if it supports one. If the environment // does not support provider storage, then it will return an error // satisfying errors.IsNotSupported. func LegacyStorage(st *state.State) (storage.Storage, error) { envConfig, err := st.EnvironConfig() if err != nil { return nil, fmt.Errorf("cannot get environment config: %v", err) } env, err := New(envConfig) if err != nil { return nil, fmt.Errorf("cannot access environment: %v", err) } if env, ok := env.(EnvironStorage); ok { return env.Storage(), nil } errmsg := fmt.Sprintf("%s provider does not support provider storage", envConfig.Type()) return nil, errors.NewNotSupported(nil, errmsg) }
func updateSecrets(env environs.Environ, st *state.State) error { secrets, err := env.Provider().SecretAttrs(env.Config()) if err != nil { return err } cfg, err := st.EnvironConfig() if err != nil { return err } secretAttrs := make(map[string]interface{}) attrs := cfg.AllAttrs() for k, v := range secrets { if _, exists := attrs[k]; exists { // Environment already has secrets. Won't send again. return nil } else { secretAttrs[k] = v } } return st.UpdateEnvironConfig(secretAttrs, nil, nil) }
// NewEnvironObserver waits for the state to have a valid environment // configuration and returns a new environment observer. While waiting // for the first environment configuration, it will return with // tomb.ErrDying if it receives a value on dying. func NewEnvironObserver(st *state.State) (*EnvironObserver, error) { config, err := st.EnvironConfig() if err != nil { return nil, err } environ, err := environs.New(config) if err != nil { return nil, fmt.Errorf("cannot make Environ: %v", err) } environWatcher := st.WatchForEnvironConfigChanges() obs := &EnvironObserver{ st: st, environ: environ, environWatcher: environWatcher, } go func() { defer obs.tomb.Done() defer watcher.Stop(environWatcher, &obs.tomb) obs.tomb.Kill(obs.loop()) }() return obs, nil }
// newMacaroonAuth returns an authenticator that can authenticate // macaroon-based logins. This is just a helper function for authCtxt.macaroonAuth. func newMacaroonAuth(st *state.State) (*authentication.MacaroonAuthenticator, error) { envCfg, err := st.EnvironConfig() if err != nil { return nil, errors.Annotate(err, "cannot get environment config") } idURL := envCfg.IdentityURL() if idURL == "" { return nil, errMacaroonAuthNotConfigured } // The identity server has been configured, // so configure the bakery service appropriately. idPK := envCfg.IdentityPublicKey() if idPK == nil { // No public key supplied - retrieve it from the identity manager. idPK, err = httpbakery.PublicKeyForLocation(http.DefaultClient, idURL) if err != nil { return nil, errors.Annotate(err, "cannot get identity public key") } } svc, err := bakery.NewService( bakery.NewServiceParams{ Location: "juju environment " + st.EnvironUUID(), Locator: bakery.PublicKeyLocatorMap{ idURL: idPK, }, }, ) if err != nil { return nil, errors.Annotate(err, "cannot make bakery service") } var auth authentication.MacaroonAuthenticator auth.Service = svc auth.Macaroon, err = svc.NewMacaroon("api-login", nil, nil) if err != nil { return nil, errors.Annotate(err, "cannot make macaroon") } auth.IdentityLocation = idURL return &auth, nil }
// ResolveCharm resolves the best available charm URLs with series, for charm // locations without a series specified. func ResolveCharms(st *state.State, args params.ResolveCharms) (params.ResolveCharmResults, error) { var results params.ResolveCharmResults envConfig, err := st.EnvironConfig() if err != nil { return params.ResolveCharmResults{}, err } repo := config.SpecializeCharmRepo( NewCharmStore(charmrepo.NewCharmStoreParams{}), envConfig) for _, ref := range args.References { result := params.ResolveCharmResult{} curl, err := resolveCharm(&ref, repo) if err != nil { result.Error = err.Error() } else { result.URL = curl } results.URLs = append(results.URLs, result) } return results, nil }
// destroyNonManagerMachines directly destroys all non-manager, non-manual // machine instances. func destroyNonManagerMachines(st *state.State, machines []*state.Machine) error { var ids []instance.Id for _, m := range machines { if m.IsManager() { continue } if _, isContainer := m.ParentId(); isContainer { continue } manual, err := m.IsManual() if err != nil { return err } else if manual { continue } // There is a possible race here if a machine is being // provisioned, but hasn't yet come up. id, err := m.InstanceId() if err != nil { continue } ids = append(ids, id) } if len(ids) == 0 { return nil } envcfg, err := st.EnvironConfig() if err != nil { return err } env, err := environs.New(envcfg) if err != nil { return err } return env.StopInstances(ids...) }
// DeployService takes a charm and various parameters and deploys it. func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) { if args.NumUnits > 1 && len(args.Placement) == 0 && args.ToMachineSpec != "" { return nil, fmt.Errorf("cannot use --num-units with --to") } settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings) if err != nil { return nil, err } if args.Charm.Meta().Subordinate { if args.NumUnits != 0 || args.ToMachineSpec != "" { return nil, fmt.Errorf("subordinate service must be deployed without units") } if !constraints.IsEmpty(&args.Constraints) { return nil, fmt.Errorf("subordinate service must be deployed without constraints") } } if args.ServiceOwner == "" { env, err := st.Environment() if err != nil { return nil, errors.Trace(err) } args.ServiceOwner = env.Owner().String() } // TODO(fwereade): transactional State.AddService including settings, constraints // (minimumUnitCount, initialMachineIds?). if len(args.Networks) > 0 || args.Constraints.HaveNetworks() { conf, err := st.EnvironConfig() if err != nil { return nil, err } env, err := environs.New(conf) if err != nil { return nil, err } if _, ok := environs.SupportsNetworking(env); !ok { return nil, fmt.Errorf("cannot deploy with networks: not suppored by the environment") } } service, err := st.AddService( args.ServiceName, args.ServiceOwner, args.Charm, args.Networks, stateStorageConstraints(args.Storage), ) if err != nil { return nil, err } if len(settings) > 0 { if err := service.UpdateConfigSettings(settings); err != nil { return nil, err } } if args.Charm.Meta().Subordinate { return service, nil } if !constraints.IsEmpty(&args.Constraints) { if err := service.SetConstraints(args.Constraints); err != nil { return nil, err } } if args.NumUnits > 0 { var err error // We either have a machine spec or a placement directive. // Placement directives take precedence. if len(args.Placement) > 0 || args.ToMachineSpec == "" { _, err = AddUnitsWithPlacement(st, service, args.NumUnits, args.Placement) } else { _, err = AddUnits(st, service, args.NumUnits, args.ToMachineSpec) } if err != nil { return nil, err } } return service, nil }
// AddCharmWithAuthorization adds the given charm URL (which must include revision) to // the environment, if it does not exist yet. Local charms are not // supported, only charm store URLs. See also AddLocalCharm(). // // The authorization macaroon, args.CharmStoreMacaroon, may be // omitted, in which case this call is equivalent to AddCharm. func AddCharmWithAuthorization(st *state.State, args params.AddCharmWithAuthorization) error { charmURL, err := charm.ParseURL(args.URL) if err != nil { return err } if charmURL.Schema != "cs" { return fmt.Errorf("only charm store charm URLs are supported, with cs: schema") } if charmURL.Revision < 0 { return fmt.Errorf("charm URL must include revision") } // First, check if a pending or a real charm exists in state. stateCharm, err := st.PrepareStoreCharmUpload(charmURL) if err != nil { return err } if stateCharm.IsUploaded() { // Charm already in state (it was uploaded already). return nil } // Get the charm and its information from the store. envConfig, err := st.EnvironConfig() if err != nil { return err } csURL, err := url.Parse(csclient.ServerURL) if err != nil { return err } csParams := charmrepo.NewCharmStoreParams{ URL: csURL.String(), HTTPClient: httpbakery.NewHTTPClient(), } if args.CharmStoreMacaroon != nil { // Set the provided charmstore authorizing macaroon // as a cookie in the HTTP client. // TODO discharge any third party caveats in the macaroon. ms := []*macaroon.Macaroon{args.CharmStoreMacaroon} httpbakery.SetCookie(csParams.HTTPClient.Jar, csURL, ms) } repo := config.SpecializeCharmRepo( NewCharmStore(csParams), envConfig, ) downloadedCharm, err := repo.Get(charmURL) if err != nil { cause := errors.Cause(err) if httpbakery.IsDischargeError(cause) || httpbakery.IsInteractionError(cause) { return errors.NewUnauthorized(err, "") } return errors.Trace(err) } // Open it and calculate the SHA256 hash. downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive) if !ok { return errors.Errorf("expected a charm archive, got %T", downloadedCharm) } archive, err := os.Open(downloadedBundle.Path) if err != nil { return errors.Annotate(err, "cannot read downloaded charm") } defer archive.Close() bundleSHA256, size, err := utils.ReadSHA256(archive) if err != nil { return errors.Annotate(err, "cannot calculate SHA256 hash of charm") } if _, err := archive.Seek(0, 0); err != nil { return errors.Annotate(err, "cannot rewind charm archive") } // Store the charm archive in environment storage. return StoreCharmArchive( st, charmURL, downloadedCharm, archive, size, bundleSHA256, ) }
// DeployService takes a charm and various parameters and deploys it. func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) { if args.NumUnits > 1 && args.ToMachineSpec != "" { return nil, fmt.Errorf("cannot use --num-units with --to") } settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings) if err != nil { return nil, err } if args.Charm.Meta().Subordinate { if args.NumUnits != 0 || args.ToMachineSpec != "" { return nil, fmt.Errorf("subordinate service must be deployed without units") } if !constraints.IsEmpty(&args.Constraints) { return nil, fmt.Errorf("subordinate service must be deployed without constraints") } } if args.ServiceOwner == "" { args.ServiceOwner = "user-admin" } // TODO(fwereade): transactional State.AddService including settings, constraints // (minimumUnitCount, initialMachineIds?). if len(args.Networks) > 0 || args.Constraints.HaveNetworks() { conf, err := st.EnvironConfig() if err != nil { return nil, err } env, err := environs.New(conf) if err != nil { return nil, err } if !env.SupportNetworks() { return nil, fmt.Errorf("cannot deploy with networks: not suppored by the environment") } } service, err := st.AddService( args.ServiceName, args.ServiceOwner, args.Charm, args.Networks, ) if err != nil { return nil, err } if len(settings) > 0 { if err := service.UpdateConfigSettings(settings); err != nil { return nil, err } } if args.Charm.Meta().Subordinate { return service, nil } if !constraints.IsEmpty(&args.Constraints) { if err := service.SetConstraints(args.Constraints); err != nil { return nil, err } } if args.NumUnits > 0 { if _, err := AddUnits(st, service, args.NumUnits, args.ToMachineSpec); err != nil { return nil, err } } return service, nil }
// InstanceConfig returns information from the environment config that // is needed for machine cloud-init (for non-state servers only). It // is exposed for testing purposes. // TODO(rog) fix environs/manual tests so they do not need to call this, or move this elsewhere. func InstanceConfig(st *state.State, machineId, nonce, dataDir string) (*instancecfg.InstanceConfig, error) { environConfig, err := st.EnvironConfig() if err != nil { return nil, err } // Get the machine so we can get its series and arch. // If the Arch is not set in hardware-characteristics, // an error is returned. machine, err := st.Machine(machineId) if err != nil { return nil, err } hc, err := machine.HardwareCharacteristics() if err != nil { return nil, err } if hc.Arch == nil { return nil, fmt.Errorf("arch is not set for %q", machine.Tag()) } // Find the appropriate tools information. agentVersion, ok := environConfig.AgentVersion() if !ok { return nil, errors.New("no agent version set in environment configuration") } environment, err := st.Environment() if err != nil { return nil, err } urlGetter := common.NewToolsURLGetter(environment.UUID(), st) toolsFinder := common.NewToolsFinder(st, st, urlGetter) findToolsResult, err := toolsFinder.FindTools(params.FindToolsParams{ Number: agentVersion, MajorVersion: -1, MinorVersion: -1, Series: machine.Series(), Arch: *hc.Arch, }) if err != nil { return nil, err } if findToolsResult.Error != nil { return nil, findToolsResult.Error } tools := findToolsResult.List[0] // Find the API endpoints. env, err := environs.New(environConfig) if err != nil { return nil, err } apiInfo, err := environs.APIInfo(env) if err != nil { return nil, err } auth := authentication.NewAuthenticator(st.MongoConnectionInfo(), apiInfo) mongoInfo, apiInfo, err := auth.SetupAuthentication(machine) if err != nil { return nil, err } // Find requested networks. networks, err := machine.RequestedNetworks() if err != nil { return nil, err } // Figure out if secure connections are supported. info, err := st.StateServingInfo() if err != nil { return nil, err } secureServerConnection := info.CAPrivateKey != "" icfg, err := instancecfg.NewInstanceConfig(machineId, nonce, env.Config().ImageStream(), machine.Series(), "", secureServerConnection, networks, mongoInfo, apiInfo, ) if err != nil { return nil, err } if dataDir != "" { icfg.DataDir = dataDir } icfg.Tools = tools err = instancecfg.FinishInstanceConfig(icfg, environConfig) if err != nil { return nil, err } return icfg, nil }