Example #1
0
func (h *Host) getJailStatus(name string, refresh bool) (JailStatus, error) {
	if refresh || h.jailStatusCache == nil || time.Now().Sub(h.jailStatusTimestamp) > (2*time.Second) {
		// FIXME: nicer cache/expiry implementation?
		if lines, err := run.Command("/usr/sbin/jls", "-d", "jid", "dying", "name").OutputLines(); err != nil {
			return NoJailStatus, errors.Trace(err)
		} else {
			stat := make(map[string]JailStatus)
			for _, line := range lines {
				fields := strings.SplitN(line, " ", 3)
				status := NoJailStatus
				if len(fields) != 3 {
					return NoJailStatus, errors.Errorf("Cannot parse jls line %#v", line)
				}

				if jid, err := strconv.Atoi(fields[0]); err != nil {
					return NoJailStatus, errors.Annotatef(err, "Cannot parse jls line %#v", line)
				} else {
					status.Jid = jid
				}

				if dying, err := strconv.Atoi(fields[1]); err != nil {
					return NoJailStatus, errors.Annotatef(err, "Cannot parse jls line %#v", line)
				} else {
					status.Dying = (dying != 0)
				}

				stat[fields[2]] = status
			}
			h.jailStatusCache = stat
		}
	}
	return h.jailStatusCache[name], nil
}
Example #2
0
File: pod.go Project: saper/jetpack
func cmdPodCmd(cmd string, baseArgs ...string) func(*jetpack.Pod, []string) error {
	return func(pod *jetpack.Pod, args []string) error {
		jid := pod.Jid()
		if jid == 0 {
			return errors.New("Pod is not running")
		} else {
			return errors.Trace(run.Command(cmd, append(append(baseArgs, strconv.Itoa(jid)), args...)...).Run())
		}
	}
}
Example #3
0
File: pod.go Project: saper/jetpack
func (pod *Pod) runJail(op string) error {
	if err := pod.prepJail(); err != nil {
		return err
	}
	verbosity := "-q"
	if Config().GetBool("debug", false) {
		verbosity = "-v"
	}
	pod.ui.Debug("Running: jail", op)
	return run.Command("jail", "-f", pod.Path("jail.conf"), verbosity, op, pod.jailName()).Run()
}
Example #4
0
File: zfs.go Project: saper/jetpack
func zfs(command string, args []string) *run.Cmd {
	quiet := false
	if command[0] == '@' {
		quiet = true
		command = command[1:]
	}
	cmd := run.Command("/sbin/zfs", append([]string{command}, args...)...)
	if quiet {
		cmd.Cmd.Stderr = nil
	}
	return cmd
}
Example #5
0
File: pod.go Project: saper/jetpack
func cmdCp(args []string) error {
	// caches
	pods := map[string]*jetpack.Pod{}

	for i, arg := range args {
		// Leave switches and local paths alone
		if cmdCpSwitchOrLocalFileRegexp.MatchString(arg) {
			continue
		}
		pieces := strings.SplitN(arg, ":", 3)
		if len(pieces) != 3 {
			return ErrUsage
		}

		podUUID := uuid.Parse(pieces[0])
		if podUUID == nil {
			return ErrUsage
		}

		pod := pods[podUUID.String()]
		if pod == nil {
			pod_, err := Host.GetPod(podUUID)
			if err != nil {
				return err
			}
			pod = pod_
			pods[podUUID.String()] = pod_
		}

		isVolume := false
		if pieces[1] == "" {
			if len(pod.Manifest.Apps) != 1 {
				return ErrUsage
			}
			pieces[1] = pod.Manifest.Apps[0].Name.String()
		} else if pieces[1][0] == '@' {
			isVolume = true
			pieces[1] = pieces[1][1:]
		}

		// TODO: sanity check on paths?
		if isVolume {
			args[i] = pod.Path("rootfs", "vol", pieces[1], pieces[2])
		} else {
			args[i] = pod.Path("rootfs", "app", pieces[1], "rootfs", pieces[2])
		}
	}

	// fmt.Printf("cp %#v\n", args)
	return errors.Trace(run.Command("/bin/cp", args...).Run())
}
Example #6
0
File: zfs.go Project: saper/jetpack
func ZPools() ([]string, error) {
	return run.Command("/sbin/zpool", "list", "-Hp", "-oname").OutputLines()
}
Example #7
0
//  Write ACI to `, return its hash. If packlist file is nil, writes
//  flat ACI (and manifest omits the dependencies).
func (img *Image) writeACI(w io.Writer, packlist *os.File) (*types.Hash, error) {
	var sink io.WriteCloser
	var faucet io.Reader

	sw := ui.NewSpinningWriter("Writing ACI", w)
	defer sw.Close()
	sink = sw

	tarArgs := []string{"-C", img.Path(), "-c", "--null", "-f", "-"}
	if packlist != nil {
		img.ui.Debug("Writing an incremental ACI")
		tarArgs = append(tarArgs, "-n", "-T", "-")
	} else {
		img.ui.Debug("Writing a flat ACI")

		// no packlist -> flat ACI
		manifest := img.Manifest
		manifest.Dependencies = nil
		manifest.PathWhitelist = nil

		manifestF, err := ioutil.TempFile(img.Path(), "manifest.flat.")
		if err != nil {
			return nil, errors.Trace(err)
		}
		defer os.Remove(manifestF.Name())

		if manifestB, err := json.Marshal(manifest); err != nil {
			manifestF.Close()
			return nil, errors.Trace(err)
		} else {
			_, err := manifestF.Write(manifestB)
			manifestF.Close()
			if err != nil {
				return nil, errors.Trace(err)
			}
		}

		manifestN := filepath.Base(manifestF.Name())
		tarArgs = append(tarArgs, "-s", "/^"+manifestN+"$/manifest/", manifestN, "rootfs")
	}

	tar := run.Command("tar", tarArgs...).ReadFrom(packlist)
	if tarPipe, err := tar.StdoutPipe(); err != nil {
		return nil, errors.Trace(err)
	} else {
		faucet = tarPipe
	}

	hash := sha512.New()
	faucet = io.TeeReader(faucet, hash)

	var compressor *run.Cmd = nil
	if compression := Config().GetString("images.aci.compression", "no"); compression != "none" {
		switch compression {
		case "xz":
			compressor = run.Command("xz", "-z", "-c")
		case "bzip2":
			compressor = run.Command("bzip2", "-z", "-c")
		case "gz":
		case "gzip":
			compressor = run.Command("gzip", "-c")
		default:
			return nil, errors.Errorf("Invalid setting images.aci.compression=%#v (allowed values: xz, bzip2, gzip, none)", compression)
		}

		compressor.Cmd.Stdout = sink
		if cin, err := compressor.StdinPipe(); err != nil {
			return nil, errors.Trace(err)
		} else {
			sink = cin
		}
	}

	if err := tar.Start(); err != nil {
		return nil, errors.Trace(err)
	}

	if compressor != nil {
		if err := compressor.Start(); err != nil {
			tar.Kill()
			return nil, errors.Trace(err)
		}
	}

	if _, err := io.Copy(sink, faucet); err != nil {
		sink.Close()
		tar.Kill()
		compressor.Kill()
		return nil, errors.Trace(err)
	}

	if err := tar.Wait(); err != nil {
		sink.Close()
		compressor.Kill()
		return nil, errors.Trace(err)
	}

	sink.Close()

	if err := compressor.Wait(); err != nil {
		return nil, errors.Trace(err)
	}

	if hash, err := types.NewHash(fmt.Sprintf("sha512-%x", hash.Sum(nil))); err != nil {
		// CAN'T HAPPEN, srsly
		return nil, errors.Trace(err)
	} else {
		img.ui.Debug("Saved", hash)
		return hash, nil
	}
}
Example #8
0
func (img *Image) Build(buildDir string, addFiles []string, buildExec []string) (*Image, error) {
	img.ui.Println("Preparing build pod")
	abuilddir, _ := filepath.Abs(buildDir)
	img.ui.Debug("Build dir:", abuilddir)
	img.ui.Debug("Extra files:", run.ShellEscape(addFiles...))
	img.ui.Debug("Build command:", run.ShellEscape(buildExec...))
	buildPod, err := img.Host.CreatePod(img.buildPodManifest(buildExec))
	if err != nil {
		return nil, errors.Trace(err)
	}

	ui := ui.NewUI("cyan", "build", buildPod.UUID.String())

	workDir := buildPod.Manifest.Apps[0].App.WorkingDirectory
	ui.Debugf("Preparing build environment in %v", workDir)

	ds, err := img.Host.Dataset.GetDataset(path.Join("pods", buildPod.UUID.String(), "rootfs.0"))
	if err != nil {
		return nil, errors.Trace(err)
	}

	fullWorkDir := ds.Path(workDir)
	if err := os.Mkdir(fullWorkDir, 0700); err != nil {
		return nil, errors.Trace(err)
	}

	if buildDir[len(buildDir)-1] != '/' {
		buildDir += "/"
	}

	cpArgs := []string{"-R", buildDir}
	if addFiles != nil {
		cpArgs = append(cpArgs, addFiles...)
	}
	cpArgs = append(cpArgs, fullWorkDir)

	if err := run.Command("cp", cpArgs...).Run(); err != nil {
		return nil, errors.Trace(err)
	}

	ui.Println("Running the build")
	if err := buildPod.Apps()[0].Run(os.Stdin, os.Stdout, os.Stderr); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.Kill(); err != nil {
		return nil, errors.Trace(err)
	}

	ui.Debug("Reading new image manifest")
	manifestBytes, err := ioutil.ReadFile(filepath.Join(fullWorkDir, "manifest.json"))
	if err != nil {
		return nil, errors.Trace(err)
	}

	ui.Debug("Removing work dir")
	if err := os.RemoveAll(fullWorkDir); err != nil {
		return nil, errors.Trace(err)
	}

	if err := os.Remove(ds.Path("etc/resolv.conf")); err != nil && !os.IsNotExist(err) {
		return nil, errors.Trace(err)
	}

	ui.Println("Pivoting build pod into new image")

	// Pivot pod into an image
	childImage := NewImage(img.Host, buildPod.UUID)

	if err := ds.Set("mountpoint", childImage.Path("rootfs")); err != nil {
		return nil, errors.Trace(err)
	}

	if err := ds.Rename(img.Host.Dataset.ChildName(path.Join("images", childImage.UUID.String()))); err != nil {
		return nil, errors.Trace(err)
	}

	// We don't need build pod anymore
	if err := buildPod.Destroy(); err != nil {
		return nil, errors.Trace(err)
	}
	buildPod = nil

	// Construct the child image's manifest

	ui.Debug("Constructing new image manifest")

	if err := json.Unmarshal(manifestBytes, &childImage.Manifest); err != nil {
		savePath := childImage.Path("manifest.err")
		ioutil.WriteFile(savePath, manifestBytes, 0400)
		return nil, errors.Annotatef(err, "Parsing new image manifest; tried to save at %v", savePath)
	}

	if _, ok := childImage.Manifest.Annotations.Get("timestamp"); !ok {
		childImage.Manifest.Annotations.Set("timestamp", time.Now().Format(time.RFC3339))
	}

	for _, label := range []types.ACIdentifier{"os", "arch"} {
		if childValue, ok := childImage.Manifest.GetLabel(string(label)); !ok {
			// if child has no os/arch, copy from parent
			if parentValue, ok := img.Manifest.GetLabel(string(label)); ok {
				childImage.Manifest.Labels = append(childImage.Manifest.Labels,
					types.Label{Name: label, Value: parentValue})
			}
		} else if childValue == "" {
			// if child explicitly set to nil or empty string, remove the
			// label
			for i, l := range childImage.Manifest.Labels {
				if l.Name == label {
					childImage.Manifest.Labels = append(
						childImage.Manifest.Labels[:i],
						childImage.Manifest.Labels[i+1:]...)
					break
				}
			}
		}
	}

	childImage.Manifest.Dependencies = append(types.Dependencies{
		types.Dependency{
			ImageName: img.Manifest.Name,
			ImageID:   img.Hash,
			Labels:    img.Manifest.Labels,
		}}, childImage.Manifest.Dependencies...)

	// Get packing list out of `zfs diff`

	ui.Debug("Generating incremental packing list")

	packlist, err := ioutil.TempFile(childImage.Path(), "aci.packlist.")
	if err != nil {
		return nil, errors.Trace(err)
	}
	os.Remove(packlist.Name())
	defer packlist.Close()
	io.WriteString(packlist, "manifest")

	// To figure out whether a deleted file has been re-added (and
	// should be kept in PathWhitelist after all), we keep changes in a
	// map: a false value means there was an addition; true value means
	// a deletion. False overwrites true, true never overwrites false.
	deletionMap := make(map[string]bool)

	if snap, err := ds.GetSnapshot("parent"); err != nil {
		return nil, errors.Trace(err)
	} else if diffs, err := snap.ZfsFields("diff"); err != nil {
		return nil, errors.Trace(err)
	} else {
		for _, diff := range diffs {
			path1 := diff[1][len(ds.Mountpoint):]
			switch diff[0] {
			case "+", "M":
				io.WriteString(packlist, filepath.Join("\000rootfs", path1))
				deletionMap[path1] = false
			case "R":
				path2 := diff[2][len(ds.Mountpoint):]
				deletionMap[path2] = false
				io.WriteString(packlist, filepath.Join("\000rootfs", path2))
				fallthrough
			case "-":
				if _, ok := deletionMap[path1]; !ok {
					// if found in map, either already true (no need to set
					// again), or false (which should stay)
					deletionMap[path1] = true
				}
			default:
				return nil, errors.Errorf("Unknown `zfs diff` line: %v", diff)
			}
		}
	}
	packlist.Seek(0, os.SEEK_SET)

	// Check if there were any deletions. If there weren't any, we don't
	// need to prepare a path whitelist.
	haveDeletions := false
	for _, isDeletion := range deletionMap {
		if isDeletion {
			haveDeletions = true
			break
		}
	}

	// If any files from parent were deleted, fill in path whitelist
	if haveDeletions {
		ui.Debug("Some files were deleted, filling in path whitelist")
		prefixLen := len(ds.Mountpoint)
		if err := filepath.Walk(ds.Mountpoint, func(path string, _ os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if len(path) == prefixLen {
				// All paths are prefixed with ds.Mountpoint. Cheaper to compare lengths than whole string.
				return nil
			}
			childImage.Manifest.PathWhitelist = append(childImage.Manifest.PathWhitelist, path[prefixLen:])
			return nil
		}); err != nil {
			return nil, errors.Trace(err)
		}
		sort.Strings(childImage.Manifest.PathWhitelist)
	}

	if err := childImage.saveManifest(); err != nil {
		return nil, errors.Trace(err)
	}

	// Save the ACI
	if f, err := os.OpenFile(childImage.Path("aci"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0440); err != nil {
		return nil, errors.Trace(err)
	} else {
		defer f.Close()
		if hash, err := childImage.writeACI(f, packlist); err != nil {
			return nil, errors.Trace(err)
		} else {
			childImage.Hash = hash
		}
	}

	if err := childImage.sealImage(); err != nil {
		return nil, errors.Trace(err)
	}

	return childImage, nil
}
Example #9
0
File: app.go Project: saper/jetpack
func (app *App) Stage2(stdin io.Reader, stdout, stderr io.Writer, user, group string, cwd string, exec ...string) error {
	if app.IsRunning() {
		// One Jetpack process won't need to run multiple commands in the
		// same app at the same time. It's either sequential
		// hook-exec-hook, or an individual command, but not both in the
		// same binary. This assumption may change in the future.
		// FIXME: race condition between this place and setting app.cmd
		return errors.New("A stage2 command is already running for this app")
	}
	app.killed = false

	if strings.HasPrefix(user, "/") || strings.HasPrefix(group, "/") {
		return errors.New("Path-based user/group not supported yet, sorry")
	}

	if cwd == "" {
		cwd = app.app.WorkingDirectory
	}

	addSupplementaryGIDs := false

	if user == "" {
		user = app.app.User
		addSupplementaryGIDs = true
	}

	if group == "" {
		group = app.app.Group
		addSupplementaryGIDs = true
	}

	// Ensure jail is created
	jid := app.Pod.ensureJid()

	mds, err := app.Pod.MetadataURL()
	if err != nil {
		return errors.Trace(err)
	}

	pwf, err := passwd.ReadPasswd(app.Path("etc", "passwd"))
	if err != nil {
		return errors.Trace(err)
	}

	pwent := pwf.Find(user)
	if pwent == nil {
		return errors.Errorf("Cannot find user: %#v", user)
	}

	if group != "" {
		grf, err := passwd.ReadGroup(app.Path("etc", "group"))
		if err != nil {
			return errors.Trace(err)
		}
		pwent.Gid = grf.FindGid(group)
		if pwent.Gid < 0 {
			return errors.Errorf("Cannot find group: %#v", group)
		}
	}

	if cwd == "" {
		cwd = "/"
	}

	gids := strconv.Itoa(pwent.Gid)
	if addSupplementaryGIDs && len(app.app.SupplementaryGIDs) > 0 {
		gidsArr := make([]string, len(app.app.SupplementaryGIDs)+1)
		gidsArr[0] = gids
		for i, gid := range app.app.SupplementaryGIDs {
			gidsArr[i+1] = strconv.Itoa(gid)
		}
		gids = strings.Join(gidsArr, ",")
	}

	stage2 := filepath.Join(Config().MustGetString("path.libexec"), "stage2")
	args := []string{
		fmt.Sprintf("%d:%d:%s:%s:%s", jid, pwent.Uid, gids, app.Name, cwd),
		"AC_METADATA_URL=" + mds,
		"USER="******"LOGNAME=" + pwent.Username,
		"HOME=" + pwent.Home,
		"SHELL=" + pwent.Shell,
	}
	// TODO: move TERM= here if stdin (or stdout?) is a terminal
	args = append(args, app.env()...)
	args = append(args, exec...)
	app.cmd = run.Command(stage2, args...)
	app.cmd.Cmd.Stdin = stdin
	app.cmd.Cmd.Stdout = stdout
	app.cmd.Cmd.Stderr = stderr
	defer func() { app.cmd = nil }()

	return app.cmd.Run()
}
Example #10
0
func (h *Host) ImportImage(name types.ACIdentifier, aci, asc *os.File) (_ *Image, erv error) {
	newId := uuid.NewRandom()
	newIdStr := newId.String()
	ui := ui.NewUI("magenta", "import", newIdStr)
	if name.Empty() {
		ui.Println("Starting import")
	} else {
		ui.Printf("Starting import of %v", name)
	}
	if asc != nil {
		ui.Debug("Checking signature")
		didKeyDiscovery := false
		ks := h.Keystore()
	checkSig:
		if ety, err := ks.CheckSignature(name, aci, asc); err == openpgp_err.ErrUnknownIssuer && !didKeyDiscovery {
			ui.Println("Image signed by an unknown issuer, attempting to discover public key...")
			if err := h.TrustKey(name, "", ""); err != nil {
				return nil, errors.Trace(err)
			}
			didKeyDiscovery = true
			aci.Seek(0, os.SEEK_SET)
			asc.Seek(0, os.SEEK_SET)
			goto checkSig
		} else if err != nil {
			return nil, errors.Trace(err)
		} else {
			ui.Println("Valid signature for", name, "by:")
			ui.Println(keystore.KeyDescription(ety)) // FIXME:ui

			aci.Seek(0, os.SEEK_SET)
			asc.Seek(0, os.SEEK_SET)
		}
	} else {
		ui.Debug("No signature to check")
	}

	img := NewImage(h, newId)

	defer func() {
		if erv != nil {
			img.Destroy()
		}
	}()

	if err := os.MkdirAll(img.Path(), 0700); err != nil {
		return nil, errors.Trace(err)
	}

	// Save copy of the signature
	if asc != nil {
		ui.Debug("Saving signature copy")
		if ascCopy, err := os.OpenFile(img.Path("aci.asc"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400); err != nil {
			return nil, errors.Trace(err)
		} else {
			_, err := io.Copy(ascCopy, asc)
			ascCopy.Close()
			if err != nil {
				return nil, errors.Trace(err)
			}
		}
	}

	// Load manifest
	ui.Debug("Loading manifest")
	manifestBytes, err := run.Command("tar", "-xOqf", "-", "manifest").ReadFrom(aci).Output()
	if err != nil {
		return nil, errors.Trace(err)
	}
	aci.Seek(0, os.SEEK_SET)

	if err = json.Unmarshal(manifestBytes, &img.Manifest); err != nil {
		return nil, errors.Trace(err)
	}

	if !name.Empty() && name != img.Manifest.Name {
		return nil, errors.Errorf("ACI name mismatch: downloaded %#v, got %#v instead", name, img.Manifest.Name)
	}

	if len(img.Manifest.Dependencies) == 0 {
		ui.Debug("No dependencies to fetch")
		if _, err := h.Dataset.CreateDataset(path.Join("images", newIdStr), "-o", "mountpoint="+h.Dataset.Path("images", newIdStr, "rootfs")); err != nil {
			return nil, errors.Trace(err)
		}
	} else {
		for i, dep := range img.Manifest.Dependencies {
			ui.Println("Looking for dependency:", dep.ImageName, dep.Labels, dep.ImageID)
			if dimg, err := h.getImageDependency(dep); err != nil {
				return nil, errors.Trace(err)
			} else {
				// We get a copy of the dependency struct when iterating, not
				// a pointer to it. We need to write to the slice's index to
				// save the hash to the real manifest.
				img.Manifest.Dependencies[i].ImageID = dimg.Hash
				if i == 0 {
					ui.Printf("Cloning parent %v as base rootfs\n", dimg)
					if ds, err := dimg.Clone(path.Join(h.Dataset.Name, "images", newIdStr), h.Dataset.Path("images", newIdStr, "rootfs")); err != nil {
						return nil, errors.Trace(err)
					} else {
						img.rootfs = ds
					}
				} else {
					return nil, errors.New("Not implemented")
				}
			}
		}
	}

	if err := img.saveManifest(); err != nil {
		return nil, errors.Trace(err)
	}

	ui.Println("Unpacking rootfs")

	// Save us a copy of the original, compressed ACI
	aciCopy, err := os.OpenFile(img.Path("aci"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer aciCopy.Close()
	aciZRd := io.TeeReader(fetch.ProgressBarFileReader(aci), aciCopy)

	// Decompress tarball for checksum
	aciRd, err := DecompressingReader(aciZRd)
	if err != nil {
		return nil, errors.Trace(err)
	}
	hash := sha512.New()
	aciRd = io.TeeReader(aciRd, hash)

	// Unpack the image. We trust system's tar, no need to roll our own
	untarCmd := run.Command("tar", "-C", img.Path(), "-xf", "-", "rootfs")
	untar, err := untarCmd.StdinPipe()
	if err != nil {
		return nil, errors.Trace(err)
	}

	if err := untarCmd.Start(); err != nil {
		return nil, errors.Trace(err)
	}
	// FIXME: defer killing process if survived

	if _, err := io.Copy(untar, aciRd); err != nil {
		return nil, errors.Trace(err)
	}

	if err := untar.Close(); err != nil {
		return nil, errors.Trace(err)
	}

	if err := untarCmd.Wait(); err != nil {
		return nil, errors.Trace(err)
	}

	if hash, err := types.NewHash(fmt.Sprintf("sha512-%x", hash.Sum(nil))); err != nil {
		// CAN'T HAPPEN
		return nil, errors.Trace(err)
	} else {
		ui.Println("Successfully imported", hash)
		img.Hash = hash
	}

	// TODO: enforce PathWhiteList

	if err := img.sealImage(); err != nil {
		return nil, errors.Trace(err)
	}

	return img, nil
}