func (pod *Pod) WriteCurrentManifest(manifest manifest.Manifest) (string, error) { // write the old manifest to a temporary location in case a launch fails. tmpDir, err := ioutil.TempDir("", "manifests") if err != nil { return "", util.Errorf("could not create a tempdir to write old manifest: %s", err) } lastManifest := filepath.Join(tmpDir, "last_manifest.yaml") if _, err := os.Stat(pod.currentPodManifestPath()); err == nil { podManifestURL, err := url.Parse(pod.currentPodManifestPath()) if err != nil { return "", util.Errorf("Couldn't parse manifest path '%s' as URL: %s", pod.currentPodManifestPath(), err) } err = uri.URICopy(podManifestURL, lastManifest) if err != nil && !os.IsNotExist(err) { return "", err } } f, err := os.OpenFile(pod.currentPodManifestPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { pod.logError(err, "Unable to open current manifest file") err = pod.revertCurrentManifest(lastManifest) if err != nil { pod.logError(err, "Couldn't replace old manifest as current") } return "", err } defer f.Close() err = manifest.Write(f) if err != nil { pod.logError(err, "Unable to write current manifest file") err = pod.revertCurrentManifest(lastManifest) if err != nil { pod.logError(err, "Couldn't replace old manifest as current") } return "", err } uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { pod.logError(err, "Unable to find pod UID/GID") // the write was still successful so we are not going to revert return "", err } err = f.Chown(uid, gid) if err != nil { pod.logError(err, "Unable to chown current manifest") return "", err } return lastManifest, nil }
func (pod *Pod) Launchables(manifest manifest.Manifest) ([]launch.Launchable, error) { launchableStanzas := manifest.GetLaunchableStanzas() launchables := make([]launch.Launchable, 0, len(launchableStanzas)) for launchableID, launchableStanza := range launchableStanzas { launchable, err := pod.getLaunchable(launchableID, launchableStanza, manifest.RunAsUser()) if err != nil { return nil, err } launchables = append(launchables, launchable) } return launchables, nil }
func (pod *Pod) Verify(manifest manifest.Manifest, authPolicy auth.Policy) error { for launchableID, stanza := range manifest.GetLaunchableStanzas() { if stanza.DigestLocation == "" { continue } launchable, err := pod.getLaunchable(launchableID, stanza, manifest.RunAsUser()) if err != nil { return err } digestLocationURL, err := url.Parse(stanza.DigestLocation) if err != nil { return util.Errorf("Couldn't parse digest location '%s' as a url: %s", stanza.DigestLocation, err) } digestSignatureLocationURL, err := url.Parse(stanza.DigestSignatureLocation) if err != nil { return util.Errorf("Couldn't parse digest signature location '%s' as a url: %s", stanza.DigestSignatureLocation, err) } // Retrieve the digest data launchableDigest, err := digest.ParseUris( uri.DefaultFetcher, digestLocationURL, digestSignatureLocationURL, ) if err != nil { return err } // Check that the digest is certified err = authPolicy.CheckDigest(launchableDigest) if err != nil { return err } // Check that the installed files match the digest err = launchableDigest.VerifyDir(launchable.InstallDir()) if err != nil { return err } } return nil }
// setupConfig does the following: // // 1) creates a directory in the pod's home directory called "config" which // contains YAML configuration files (named with pod's ID and the SHA of its // manifest's content) the path to which will be exported to a pods launchables // via the CONFIG_PATH environment variable // // 2) writes an "env" directory in the pod's home directory called "env" which // contains environment variables written as files that will be exported to all // processes started by all launchables (as described in // http://smarden.org/runit/chpst.8.html, with the -e option), including // CONFIG_PATH // // 3) writes an "env" directory for each launchable. The "env" directory // contains environment files specific to a launchable (such as // LAUNCHABLE_ROOT) // // We may wish to provide a "config" directory per launchable at some point as // well, so that launchables can have different config namespaces func (pod *Pod) setupConfig(manifest manifest.Manifest, launchables []launch.Launchable) error { uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { return util.Errorf("Could not determine pod UID/GID: %s", err) } var configData bytes.Buffer err = manifest.WriteConfig(&configData) if err != nil { return err } var platConfigData bytes.Buffer err = manifest.WritePlatformConfig(&platConfigData) if err != nil { return err } err = util.MkdirChownAll(pod.ConfigDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create config directory for pod %s: %s", manifest.ID(), err) } configFileName, err := manifest.ConfigFileName() if err != nil { return err } configPath := filepath.Join(pod.ConfigDir(), configFileName) err = writeFileChown(configPath, configData.Bytes(), uid, gid) if err != nil { return util.Errorf("Error writing config file for pod %s: %s", manifest.ID(), err) } platConfigFileName, err := manifest.PlatformConfigFileName() if err != nil { return err } platConfigPath := filepath.Join(pod.ConfigDir(), platConfigFileName) err = writeFileChown(platConfigPath, platConfigData.Bytes(), uid, gid) if err != nil { return util.Errorf("Error writing platform config file for pod %s: %s", manifest.ID(), err) } err = util.MkdirChownAll(pod.EnvDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create the environment dir for pod %s: %s", manifest.ID(), err) } err = writeEnvFile(pod.EnvDir(), ConfigPathEnvVar, configPath, uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PlatformConfigPathEnvVar, platConfigPath, uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PodHomeEnvVar, pod.Home(), uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PodIDEnvVar, pod.Id.String(), uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PodUniqueKeyEnvVar, pod.uniqueKey.String(), uid, gid) if err != nil { return err } for _, launchable := range launchables { // we need to remove any unset env vars from a previous pod err = os.RemoveAll(launchable.EnvDir()) if err != nil { return err } err = util.MkdirChownAll(launchable.EnvDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create the environment dir for pod %s launchable %s: %s", manifest.ID(), launchable.ServiceID(), err) } err = writeEnvFile(launchable.EnvDir(), LaunchableIDEnvVar, launchable.ID().String(), uid, gid) if err != nil { return err } err = writeEnvFile(launchable.EnvDir(), "LAUNCHABLE_ROOT", launchable.InstallDir(), uid, gid) if err != nil { return err } // last, write the user-supplied env variables to ensure priority of user-supplied values for envName, value := range launchable.EnvVars() { err = writeEnvFile(launchable.EnvDir(), envName, fmt.Sprint(value), uid, gid) if err != nil { return err } } } return nil }
// Install will ensure that executables for all required services are present on the host // machine and are set up to run. In the case of Hoist artifacts (which is the only format // supported currently, this will set up runit services.). func (pod *Pod) Install(manifest manifest.Manifest, verifier auth.ArtifactVerifier, artifactRegistry artifact.Registry) error { podHome := pod.home uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { return util.Errorf("Could not determine pod UID/GID for %s: %s", manifest.RunAsUser(), err) } err = util.MkdirChownAll(podHome, uid, gid, 0755) if err != nil { return util.Errorf("Could not create pod home: %s", err) } launchables, err := pod.Launchables(manifest) if err != nil { return err } downloader := artifact.NewLocationDownloader(pod.Fetcher, verifier) for launchableID, stanza := range manifest.GetLaunchableStanzas() { // TODO: investigate passing in necessary fields to InstallDir() launchable, err := pod.getLaunchable(launchableID, stanza, manifest.RunAsUser()) if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") return err } if launchable.Installed() { continue } launchableURL, verificationData, err := artifactRegistry.LocationDataForLaunchable(launchableID, stanza) if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") return err } err = downloader.Download(launchableURL, verificationData, launchable.InstallDir(), manifest.RunAsUser()) if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") _ = os.Remove(launchable.InstallDir()) return err } err = launchable.PostInstall() if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") _ = os.Remove(launchable.InstallDir()) return err } } // we may need to write config files to a unique directory per pod version, depending on restart semantics. Need // to think about this more. err = pod.setupConfig(manifest, launchables) if err != nil { pod.logError(err, "Could not setup config") return util.Errorf("Could not setup config: %s", err) } pod.logInfo("Successfully installed") return nil }