Пример #1
0
// 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) error {
	podHome := pod.path
	uid, gid, err := user.IDs(manifest.RunAsUser())
	if err != nil {
		return util.Errorf("Could not determine pod UID/GID: %s", err)
	}

	err = util.MkdirChownAll(podHome, uid, gid, 0755)
	if err != nil {
		return util.Errorf("Could not create pod home: %s", 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)
	if err != nil {
		pod.logError(err, "Could not setup config")
		return util.Errorf("Could not setup config: %s", err)
	}

	launchables, err := pod.Launchables(manifest)
	if err != nil {
		return err
	}

	for _, launchable := range launchables {
		err := launchable.Install()
		if err != nil {
			pod.logLaunchableError(launchable.Id, err, "Unable to install launchable")
			return err
		}
	}

	pod.logInfo("Successfully installed")

	return nil
}
Пример #2
0
// ExtractTarGz reads a gzipped tar stream and extracts all files to the destination
// directory. If an owner name is specified, all files will be created to be owned by that
// user; otherwise, the tar specifies ownership.
//
// If any file would be extracted outside of the destination directory due to relative paths
// containing '..', the archive will be rejected with an error.
func ExtractTarGz(owner string, fp io.Reader, dest string) (err error) {
	fz, err := gzip.NewReader(fp)
	if err != nil {
		return util.Errorf("error reading gzip data: %s", err)
	}
	defer fz.Close()
	tr := tar.NewReader(fz)

	var ownerUID, ownerGID int
	if owner != "" {
		ownerUID, ownerGID, err = user.IDs(owner)
		if err != nil {
			return err
		}
	}

	err = util.MkdirChownAll(dest, ownerUID, ownerGID, 0755)
	if err != nil {
		return util.Errorf("error creating root directory %s: %s", dest, err)
	}
	err = os.Chown(dest, ownerUID, ownerGID)
	if err != nil {
		return util.Errorf("error setting ownership of root directory %s: %s", dest, err)
	}

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return util.Errorf("read error: %s", err)
		}
		fpath := filepath.Join(dest, hdr.Name)
		var uid, gid int
		if owner == "" {
			uid, gid = hdr.Uid, hdr.Gid
		} else {
			uid, gid = ownerUID, ownerGID
		}

		// Error on all files that would end up outside the destination directory.
		if !strings.HasPrefix(fpath, dest) {
			return util.Errorf(
				"cannot extract %s, as its target %s is outside the root directory %s",
				hdr.Name,
				fpath,
				dest,
			)
		}

		parent := filepath.Dir(fpath)
		if err := util.MkdirChownAll(parent, ownerUID, ownerGID, 0755); err != nil {
			return util.Errorf(
				"error creating directory %s (parent of %s): %s",
				parent,
				hdr.Name,
				err,
			)
		}

		switch hdr.Typeflag {
		case tar.TypeSymlink:
			err = os.Symlink(hdr.Linkname, fpath)
			if err != nil {
				return util.Errorf(
					"error creating symlink %s -> %s: %s",
					fpath,
					hdr.Linkname,
					err,
				)
			}
			err = os.Lchown(fpath, uid, gid)
			if err != nil {
				return util.Errorf("error setting owner of %s: %s", fpath, err)
			}
		case tar.TypeLink:
			// If you include a file multiple times in an invocation of
			// Gnu tar, it stores anything after the first as a hard link
			// to itself.  Since such a structure can't otherwise exist, we
			// can simply skip it.
			if hdr.Name == hdr.Linkname {
				continue
			}
			// hardlink paths are encoded relative to the tarball root, rather than
			// the path of the link itself, so we need to resolve that path
			linkTarget, err := filepath.Rel(filepath.Dir(hdr.Name), hdr.Linkname)
			if err != nil {
				return util.Errorf(
					"error resolving link: %s -> %s: %s",
					fpath,
					hdr.Linkname,
					err,
				)
			}
			// we can't make the hardlink right away because the target might not
			// exist, so we'll just make a symlink instead
			err = os.Symlink(linkTarget, fpath)
			if err != nil {
				return util.Errorf(
					"error creating symlink %s -> %s (originally hardlink): %s",
					fpath,
					linkTarget,
					err,
				)
			}
		case tar.TypeDir:
			err = os.Mkdir(fpath, hdr.FileInfo().Mode())
			if err != nil && !os.IsExist(err) {
				return util.Errorf("error creating directory %s: %s", fpath, err)
			}

			err = os.Chown(fpath, uid, gid)
			if err != nil {
				return util.Errorf("error setting ownership of %s: %s", fpath, err)
			}
		case tar.TypeReg, tar.TypeRegA:
			// Extract the file inside a closure to limit the scope of its open FD
			err = func() (innerErr error) {
				f, err := os.OpenFile(
					fpath,
					os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
					hdr.FileInfo().Mode(),
				)
				if err != nil {
					return util.Errorf("error creating %s: %s", fpath, err)
				}
				// Released at end of "case" statement
				defer func() {
					if closeErr := f.Close(); innerErr == nil {
						innerErr = closeErr
					}
				}()

				err = f.Chown(uid, gid)
				if err != nil {
					return util.Errorf("error setting file ownership of %s: %s", fpath, err)
				}

				_, err = io.Copy(f, tr)
				if err != nil {
					return util.Errorf("error extracting to %s: %s", fpath, err)
				}
				return nil
			}()
			if err != nil {
				return err
			}
		default:
			return util.Errorf("unhandled type flag %q (header %v)", hdr.Typeflag, hdr)
		}
	}
	return nil
}
Пример #3
0
// setupConfig creates two directories in the pod's home directory, called "env" and "config."
// the "config" directory contains the pod's config file, named with pod's ID and the
// SHA of its manifest's content. The "env" directory contains environment files
// (as described in http://smarden.org/runit/chpst.8.html, with the -e option) and includes a
// single file called CONFIG_PATH, which points at the file written in the "config" directory.
func (pod *Pod) setupConfig(manifest *Manifest) error {
	uid, gid, err := user.IDs(manifest.RunAsUser())
	if err != nil {
		return util.Errorf("Could not determine pod UID/GID: %s", 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)

	file, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	defer file.Close()
	if err != nil {
		return util.Errorf("Could not open config file for pod %s for writing: %s", manifest.ID(), err)
	}
	err = manifest.WriteConfig(file)
	if err != nil {
		return err
	}
	err = file.Chown(uid, gid)
	if err != nil {
		return err
	}

	platConfigFileName, err := manifest.PlatformConfigFileName()
	if err != nil {
		return err
	}
	platConfigPath := filepath.Join(pod.ConfigDir(), platConfigFileName)
	platFile, err := os.OpenFile(platConfigPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	defer platFile.Close()
	if err != nil {
		return util.Errorf("Could not open config file for pod %s for writing: %s", manifest.ID(), err)
	}
	err = manifest.WritePlatformConfig(platFile)
	if err != nil {
		return err
	}
	err = platFile.Chown(uid, gid)
	if err != nil {
		return 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(), "CONFIG_PATH", configPath, uid, gid)
	if err != nil {
		return err
	}
	err = writeEnvFile(pod.EnvDir(), "PLATFORM_CONFIG_PATH", platConfigPath, uid, gid)
	if err != nil {
		return err
	}
	err = writeEnvFile(pod.EnvDir(), "POD_HOME", pod.Path(), uid, gid)
	if err != nil {
		return err
	}

	return nil
}
Пример #4
0
// 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
}
Пример #5
0
// 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
}