// Write implements EnvironInfo.Write. func (info *environInfo) Write() error { data, err := goyaml.Marshal(info.EnvInfo) if err != nil { return errgo.Annotate(err, "cannot marshal environment info") } // Create a temporary file and rename it, so that the data // changes atomically. parent, _ := filepath.Split(info.path) tmpFile, err := ioutil.TempFile(parent, "") if err != nil { return errgo.Annotate(err, "cannot create temporary file") } _, err = tmpFile.Write(data) // N.B. We need to close the file before renaming it // otherwise it will fail under Windows with a file-in-use // error. tmpFile.Close() if err != nil { return errgo.Annotate(err, "cannot write temporary file") } if err := utils.ReplaceFile(tmpFile.Name(), info.path); err != nil { os.Remove(tmpFile.Name()) return errgo.Annotate(err, "cannot rename new environment info file") } info.initialized = true return nil }
// processUploadedArchive opens the given charm archive from path, // inspects it to see if it has all files at the root of the archive // or it has subdirs. It repackages the archive so it has all the // files at the root dir, if necessary, replacing the original archive // at path. func (h *charmsHandler) processUploadedArchive(path string) error { // Open the archive as a zip. f, err := os.OpenFile(path, os.O_RDWR, 0644) if err != nil { return err } defer f.Close() fi, err := f.Stat() if err != nil { return err } zipr, err := zip.NewReader(f, fi.Size()) if err != nil { return errgo.Annotate(err, "cannot open charm archive") } // Find out the root dir prefix from the archive. rootDir, err := h.findArchiveRootDir(zipr) if err != nil { return errgo.Annotate(err, "cannot read charm archive") } if rootDir == "." { // Normal charm, just use charm.ReadBundle(). return nil } // There is one or more subdirs, so we need extract it to a temp // dir and then read it as a charm dir. tempDir, err := ioutil.TempDir("", "charm-extract") if err != nil { return errgo.Annotate(err, "cannot create temp directory") } defer os.RemoveAll(tempDir) if err := ziputil.Extract(zipr, tempDir, rootDir); err != nil { return errgo.Annotate(err, "cannot extract charm archive") } dir, err := charm.ReadDir(tempDir) if err != nil { return errgo.Annotate(err, "cannot read extracted archive") } // Now repackage the dir as a bundle at the original path. if err := f.Truncate(0); err != nil { return err } if err := dir.BundleTo(f); err != nil { return err } return nil }
func (h *RsyslogConfigHandler) SetUp() (watcher.NotifyWatcher, error) { if h.mode == RsyslogModeAccumulate { if err := h.ensureCertificates(); err != nil { return nil, errgo.Annotate(err, "failed to write rsyslog certificates") } } return h.st.WatchForEnvironConfigChanges() }
// FinishMachineConfig sets fields on a MachineConfig that can be determined by // inspecting a plain config.Config and the machine constraints at the last // moment before bootstrapping. It assumes that the supplied Config comes from // an environment that has passed through all the validation checks in the // Bootstrap func, and that has set an agent-version (via finding the tools to, // use for bootstrap, or otherwise). // TODO(fwereade) This function is not meant to be "good" in any serious way: // it is better that this functionality be collected in one place here than // that it be spread out across 3 or 4 providers, but this is its only // redeeming feature. func FinishMachineConfig(mcfg *cloudinit.MachineConfig, cfg *config.Config, cons constraints.Value) (err error) { defer errors.Maskf(&err, "cannot complete machine configuration") if err := PopulateMachineConfig( mcfg, cfg.Type(), cfg.AuthorizedKeys(), cfg.SSLHostnameVerification(), cfg.ProxySettings(), cfg.AptProxySettings(), ); err != nil { return err } // The following settings are only appropriate at bootstrap time. At the // moment, the only state server is the bootstrap node, but this // will probably change. if !mcfg.Bootstrap { return nil } if mcfg.APIInfo != nil || mcfg.StateInfo != nil { return fmt.Errorf("machine configuration already has api/state info") } caCert, hasCACert := cfg.CACert() if !hasCACert { return fmt.Errorf("environment configuration has no ca-cert") } password := cfg.AdminSecret() if password == "" { return fmt.Errorf("environment configuration has no admin-secret") } passwordHash := utils.UserPasswordHash(password, utils.CompatSalt) mcfg.APIInfo = &api.Info{Password: passwordHash, CACert: caCert} mcfg.StateInfo = &state.Info{Password: passwordHash, CACert: caCert} // These really are directly relevant to running a state server. cert, key, err := cfg.GenerateStateServerCertAndKey() if err != nil { return errgo.Annotate(err, "cannot generate state server certificate") } srvInfo := params.StateServingInfo{ StatePort: cfg.StatePort(), APIPort: cfg.APIPort(), Cert: string(cert), PrivateKey: string(key), SystemIdentity: mcfg.SystemPrivateSSHKey, } mcfg.StateServingInfo = &srvInfo mcfg.Constraints = cons if mcfg.Config, err = BootstrapConfig(cfg); err != nil { return err } return nil }
// Verify verifies that the given server certificate is valid with // respect to the given CA certificate at the given time. func Verify(srvCertPEM, caCertPEM string, when time.Time) error { caCert, err := ParseCert(caCertPEM) if err != nil { return errgo.Annotate(err, "cannot parse CA certificate") } srvCert, err := ParseCert(srvCertPEM) if err != nil { return errgo.Annotate(err, "cannot parse server certificate") } pool := x509.NewCertPool() pool.AddCert(caCert) opts := x509.VerifyOptions{ DNSName: "anyServer", Roots: pool, CurrentTime: when, } _, err = srvCert.Verify(opts) return err }
// DestroyInfo destroys the configuration data for the named // environment from the given store. func DestroyInfo(envName string, store configstore.Storage) error { info, err := store.ReadInfo(envName) if err != nil { if errors.IsNotFound(err) { return nil } return err } if err := info.Destroy(); err != nil { return errgo.Annotate(err, "cannot destroy environment configuration information") } return nil }
func (h *RsyslogConfigHandler) Handle() error { cfg, err := h.st.EnvironConfig() if err != nil { return errgo.Annotate(err, "cannot get environ config") } rsyslogCACert := cfg.RsyslogCACert() if rsyslogCACert == "" { return nil } // If neither syslog-port nor rsyslog-ca-cert // have changed, we can drop out now. if cfg.SyslogPort() == h.syslogPort && rsyslogCACert == h.rsyslogCACert { return nil } h.syslogConfig.Port = cfg.SyslogPort() if h.mode == RsyslogModeForwarding { if err := writeFileAtomic(h.syslogConfig.CACertPath(), []byte(rsyslogCACert), 0644, 0, 0); err != nil { return errgo.Annotate(err, "cannot write CA certificate") } } data, err := h.syslogConfig.Render() if err != nil { return errgo.Annotate(err, "failed to render rsyslog configuration file") } if err := writeFileAtomic(h.syslogConfig.ConfigFilePath(), []byte(data), 0644, 0, 0); err != nil { return errgo.Annotate(err, "failed to write rsyslog configuration file") } logger.Debugf("Reloading rsyslog configuration") if err := restartRsyslog(); err != nil { logger.Errorf("failed to reload rsyslog configuration") return errgo.Annotate(err, "cannot restart rsyslog") } // Record config values so we don't try again. // Do this last so we recover from intermittent // failures. h.syslogPort = cfg.SyslogPort() h.rsyslogCACert = rsyslogCACert return nil }
// addAgentInfo adds agent-required information to the agent's directory // and returns the agent directory name. func (cfg *MachineConfig) addAgentInfo(c *cloudinit.Config, tag string) (agent.Config, error) { acfg, err := cfg.agentConfig(tag) if err != nil { return nil, err } acfg.SetValue(agent.AgentServiceName, cfg.MachineAgentServiceName) cmds, err := acfg.WriteCommands() if err != nil { return nil, errgo.Annotate(err, "failed to write commands") } c.AddScripts(cmds...) return acfg, nil }
// unitsChanged responds to changes to the assigned units. func (fw *Firewaller) unitsChanged(change *unitsChange) error { changed := []*unitData{} for _, name := range change.units { unit, err := fw.st.Unit(names.UnitTag(name)) if err != nil && !params.IsCodeNotFound(err) { return err } var machineTag string if unit != nil { machineTag, err = unit.AssignedMachine() if params.IsCodeNotFound(err) { continue } else if err != nil && !params.IsCodeNotAssigned(err) { return err } } if unitd, known := fw.unitds[name]; known { knownMachineTag := fw.unitds[name].machined.tag if unit == nil || unit.Life() == params.Dead || machineTag != knownMachineTag { fw.forgetUnit(unitd) changed = append(changed, unitd) logger.Debugf("stopped watching unit %s", name) } } else if unit != nil && unit.Life() != params.Dead && fw.machineds[machineTag] != nil { err = fw.startUnit(unit, machineTag) if err != nil { return err } changed = append(changed, fw.unitds[name]) logger.Debugf("started watching unit %s", name) } } if err := fw.flushUnits(changed); err != nil { return errgo.Annotate(err, "cannot change firewall ports") } return nil }
// startMachine creates a new data value for tracking details of the // machine and starts watching the machine for units added or removed. func (fw *Firewaller) startMachine(tag string) error { machined := &machineData{ fw: fw, tag: tag, unitds: make(map[string]*unitData), ports: make([]instance.Port, 0), } m, err := machined.machine() if params.IsCodeNotFound(err) { return nil } else if err != nil { return errgo.Annotate(err, "cannot watch machine units") } unitw, err := m.WatchUnits() if err != nil { return err } select { case <-fw.tomb.Dying(): stop("units watcher", unitw) return tomb.ErrDying case change, ok := <-unitw.Changes(): if !ok { stop("units watcher", unitw) return watcher.MustErr(unitw) } fw.machineds[tag] = machined err = fw.unitsChanged(&unitsChange{machined, change}) if err != nil { stop("units watcher", unitw) delete(fw.machineds, tag) return errgo.Annotatef(err, "cannot respond to units changes for %q", tag) } } go machined.watchLoop(unitw) return nil }
// downloadCharm downloads the given charm name from the provider storage and // saves the corresponding zip archive to the given charmArchivePath. func (h *charmsHandler) downloadCharm(name, charmArchivePath string) error { // Get the provider storage. storage, err := environs.GetStorage(h.state) if err != nil { return errgo.Annotate(err, "cannot access provider storage") } // Use the storage to retrieve and save the charm archive. reader, err := storage.Get(name) if err != nil { return errgo.Annotate(err, "charm not found in the provider storage") } defer reader.Close() data, err := ioutil.ReadAll(reader) if err != nil { return errgo.Annotate(err, "cannot read charm data") } // In order to avoid races, the archive is saved in a temporary file which // is then atomically renamed. The temporary file is created in the // charm cache directory so that we can safely assume the rename source and // target live in the same file system. cacheDir := filepath.Dir(charmArchivePath) if err = os.MkdirAll(cacheDir, 0755); err != nil { return errgo.Annotate(err, "cannot create the charms cache") } tempCharmArchive, err := ioutil.TempFile(cacheDir, "charm") if err != nil { return errgo.Annotate(err, "cannot create charm archive temp file") } defer tempCharmArchive.Close() if err = ioutil.WriteFile(tempCharmArchive.Name(), data, 0644); err != nil { return errgo.Annotate(err, "error processing charm archive download") } if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != nil { defer os.Remove(tempCharmArchive.Name()) return errgo.Annotate(err, "error renaming the charm archive") } return nil }
// AddCharm 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(). func (c *Client) AddCharm(args params.CharmURL) 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 := c.api.state.PrepareStoreCharmUpload(charmURL) if err == nil && stateCharm.IsUploaded() { // Charm already in state (it was uploaded already). return nil } else if err != nil { return err } // Get the charm and its information from the store. envConfig, err := c.api.state.EnvironConfig() if err != nil { return err } store := config.SpecializeCharmRepo(CharmStore, envConfig) downloadedCharm, err := store.Get(charmURL) if err != nil { return errgo.Annotatef(err, "cannot download charm %q", charmURL.String()) } // Open it and calculate the SHA256 hash. downloadedBundle, ok := downloadedCharm.(*charm.Bundle) if !ok { return errgo.New("expected a charm archive, got %T", downloadedCharm) } archive, err := os.Open(downloadedBundle.Path) if err != nil { return errgo.Annotate(err, "cannot read downloaded charm") } defer archive.Close() bundleSHA256, size, err := utils.ReadSHA256(archive) if err != nil { return errgo.Annotate(err, "cannot calculate SHA256 hash of charm") } if _, err := archive.Seek(0, 0); err != nil { return errgo.Annotate(err, "cannot rewind charm archive") } // Get the environment storage and upload the charm. env, err := environs.New(envConfig) if err != nil { return errgo.Annotate(err, "cannot access environment") } storage := env.Storage() archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision) if err != nil { return errgo.Annotate(err, "cannot generate charm archive name") } if err := storage.Put(archiveName, archive, size); err != nil { return errgo.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(archiveName) if err != nil { return errgo.Annotate(err, "cannot get storage URL for charm") } bundleURL, err := url.Parse(storageURL) if err != nil { return errgo.Annotate(err, "cannot parse storage URL") } // Finally, update the charm data in state and mark it as no longer pending. _, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256) if err == state.ErrCharmRevisionAlreadyModified || state.IsCharmAlreadyUploadedError(err) { // This is not an error, it just signifies somebody else // managed to upload and update the charm in state before // us. This means we have to delete what we just uploaded // to storage. if err := storage.Remove(archiveName); err != nil { errgo.Annotate(err, "cannot remove duplicated charm from storage") } return nil } return err }
// Validate ensures that config is a valid configuration. If old is not nil, // it holds the previous environment configuration for consideration when // validating changes. func Validate(cfg, old *Config) error { // Check that we don't have any disallowed fields. for _, attr := range allowedWithDefaultsOnly { if _, ok := cfg.defined[attr]; ok { return fmt.Errorf("attribute %q is not allowed in configuration", attr) } } // Check that mandatory fields are specified. for _, attr := range mandatoryWithoutDefaults { if _, ok := cfg.defined[attr]; !ok { return fmt.Errorf("%s missing from environment configuration", attr) } } // Check that all other fields that have been specified are non-empty, // unless they're allowed to be empty for backward compatibility, for attr, val := range cfg.defined { if !isEmpty(val) { continue } if !allowEmpty(attr) { return fmt.Errorf("empty %s in environment configuration", attr) } } if strings.ContainsAny(cfg.mustString("name"), "/\\") { return fmt.Errorf("environment name contains unsafe characters") } // Check that the agent version parses ok if set explicitly; otherwise leave // it alone. if v, ok := cfg.defined["agent-version"].(string); ok { if _, err := version.Parse(v); err != nil { return fmt.Errorf("invalid agent version in environment configuration: %q", v) } } // If the logging config is set, make sure it is valid. if v, ok := cfg.defined["logging-config"].(string); ok { if _, err := loggo.ParseConfigurationString(v); err != nil { return err } } // Check firewall mode. if mode := cfg.FirewallMode(); mode != FwInstance && mode != FwGlobal { return fmt.Errorf("invalid firewall mode in environment configuration: %q", mode) } caCert, caCertOK := cfg.CACert() caKey, caKeyOK := cfg.CAPrivateKey() if caCertOK || caKeyOK { if err := verifyKeyPair(caCert, caKey); err != nil { return errgo.Annotate(err, "bad CA certificate/key in configuration") } } // Ensure that the auth token is a set of key=value pairs. authToken, _ := cfg.CharmStoreAuth() validAuthToken := regexp.MustCompile(`^([^\s=]+=[^\s=]+(,\s*)?)*$`) if !validAuthToken.MatchString(authToken) { return fmt.Errorf("charm store auth token needs to be a set"+ " of key-value pairs, not %q", authToken) } // Check the immutable config values. These can't change if old != nil { for _, attr := range immutableAttributes { if newv, oldv := cfg.defined[attr], old.defined[attr]; newv != oldv { return fmt.Errorf("cannot change %s from %#v to %#v", attr, oldv, newv) } } if _, oldFound := old.AgentVersion(); oldFound { if _, newFound := cfg.AgentVersion(); !newFound { return fmt.Errorf("cannot clear agent-version") } } } cfg.processDeprecatedAttributes() return nil }
func (fw *Firewaller) loop() error { defer fw.stopWatchers() var err error var reconciled bool fw.environ, err = worker.WaitForEnviron(fw.environWatcher, fw.st, fw.tomb.Dying()) if err != nil { return err } if fw.environ.Config().FirewallMode() == config.FwGlobal { fw.globalMode = true fw.globalPortRef = make(map[instance.Port]int) } for { select { case <-fw.tomb.Dying(): return tomb.ErrDying case _, ok := <-fw.environWatcher.Changes(): if !ok { return watcher.MustErr(fw.environWatcher) } config, err := fw.st.EnvironConfig() if err != nil { return err } if err := fw.environ.SetConfig(config); err != nil { logger.Errorf("loaded invalid environment configuration: %v", err) } case change, ok := <-fw.machinesWatcher.Changes(): if !ok { return watcher.MustErr(fw.machinesWatcher) } for _, machineId := range change { fw.machineLifeChanged(names.MachineTag(machineId)) } if !reconciled { reconciled = true var err error if fw.globalMode { err = fw.reconcileGlobal() } else { err = fw.reconcileInstances() } if err != nil { return err } } case change := <-fw.unitsChange: if err := fw.unitsChanged(change); err != nil { return err } case change := <-fw.portsChange: change.unitd.ports = change.ports if err := fw.flushUnits([]*unitData{change.unitd}); err != nil { return errgo.Annotate(err, "cannot change firewall ports") } case change := <-fw.exposedChange: change.serviced.exposed = change.exposed unitds := []*unitData{} for _, unitd := range change.serviced.unitds { unitds = append(unitds, unitd) } if err := fw.flushUnits(unitds); err != nil { return errgo.Annotate(err, "cannot change firewall ports") } } } }
// repackageAndUploadCharm expands the given charm archive to a // temporary directoy, repackages it with the given curl's revision, // then uploads it to providr storage, and finally updates the state. func (h *charmsHandler) repackageAndUploadCharm(archive *charm.Bundle, curl *charm.URL) error { // Create a temp dir to contain the extracted charm // dir and the repackaged archive. tempDir, err := ioutil.TempDir("", "charm-download") if err != nil { return errgo.Annotate(err, "cannot create temp directory") } defer os.RemoveAll(tempDir) extractPath := filepath.Join(tempDir, "extracted") repackagedPath := filepath.Join(tempDir, "repackaged.zip") repackagedArchive, err := os.Create(repackagedPath) if err != nil { return errgo.Annotate(err, "cannot repackage uploaded charm") } defer repackagedArchive.Close() // Expand and repack it with the revision specified by curl. archive.SetRevision(curl.Revision) if err := archive.ExpandTo(extractPath); err != nil { return errgo.Annotate(err, "cannot extract uploaded charm") } charmDir, err := charm.ReadDir(extractPath) if err != nil { return errgo.Annotate(err, "cannot read extracted charm") } // Bundle the charm and calculate its sha256 hash at the // same time. hash := sha256.New() err = charmDir.BundleTo(io.MultiWriter(hash, repackagedArchive)) if err != nil { return errgo.Annotate(err, "cannot repackage uploaded charm") } bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) size, err := repackagedArchive.Seek(0, 2) if err != nil { return errgo.Annotate(err, "cannot get charm file size") } // Now upload to provider storage. if _, err := repackagedArchive.Seek(0, 0); err != nil { return errgo.Annotate(err, "cannot rewind the charm file reader") } storage, err := environs.GetStorage(h.state) if err != nil { return errgo.Annotate(err, "cannot access provider storage") } name := charm.Quote(curl.String()) if err := storage.Put(name, repackagedArchive, size); err != nil { return errgo.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(name) if err != nil { return errgo.Annotate(err, "cannot get storage URL for charm") } bundleURL, err := url.Parse(storageURL) if err != nil { return errgo.Annotate(err, "cannot parse storage URL") } // And finally, update state. _, err = h.state.UpdateUploadedCharm(archive, curl, bundleURL, bundleSHA256) if err != nil { return errgo.Annotate(err, "cannot update uploaded charm in state") } return nil }