예제 #1
0
func initDataset() error {
	for _, cmd := range [][]string{
		[]string{"jetpack", "init"},
		[]string{"make", "-C", filepath.Join(ImagesPath, "freebsd-base.release")},
		[]string{"make", "-C", filepath.Join(ImagesPath, "freebsd-base")},
		[]string{"make", "-C", filepath.Join(ImagesPath, "example.showenv")},
	} {
		cmd := run.Command(cmd[0], cmd[1:]...)
		if HasParam("verbose") {
			fmt.Println(cmd)
		}
		if err := cmd.Run(); err != nil {
			return err
		}
	}

	if ds, err := zfs.GetDataset(RootDatasetName); err != nil {
		return err
	} else {
		RootDataset = ds
	}

	if snap, err := RootDataset.Snapshot(snapshotName, "-r"); err != nil {
		return err
	} else {
		RootDatasetSnapshot = snap
	}

	return nil
}
예제 #2
0
파일: pod.go 프로젝트: dataVerve/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())
		}
	}
}
예제 #3
0
파일: pod.go 프로젝트: dataVerve/jetpack
func (c *Pod) runJail(op string) error {
	if err := c.prepJail(); err != nil {
		return err
	}
	verbosity := "-q"
	if Config().GetBool("debug", false) {
		verbosity = "-v"
	}
	c.ui.Debug("Running: jail", op)
	return run.Command("jail", "-f", c.Path("jail.conf"), verbosity, op, c.jailName()).Run()
}
예제 #4
0
파일: zfs.go 프로젝트: sdoumbouya/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
}
예제 #5
0
func TestForSmoke(t *testing.T) {
	RollbackDataset(t)

	if out, err := run.Command("jetpack", "pod", "list").OutputString(); err != nil {
		t.Error(err)
	} else {
		t.Logf("jetpack list =>\n%v\n", out)
		if out != "No pods" {
			t.Fatalf("Expected no pods")
		}
	}

	if out, err := run.Command("jetpack", "image", "list").OutputLines(); err != nil {
		t.Error(err)
	} else {
		t.Logf("jetpack images =>\n%v\n", strings.Join(out, "\n"))
		if len(out) != 4 {
			t.Fatal("Expected four lines of output (header + 3 images), instead got", len(out))
		}
	}
}
예제 #6
0
파일: zfs.go 프로젝트: sdoumbouya/jetpack
func ZPools() ([]string, error) {
	return run.Command("/sbin/zpool", "list", "-Hp", "-oname").OutputLines()
}
예제 #7
0
파일: build.go 프로젝트: sdoumbouya/jetpack
//  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
	}
}
예제 #8
0
파일: build.go 프로젝트: sdoumbouya/jetpack
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.RunApp(buildPod.Manifest.Apps[0].Name); 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
}
예제 #9
0
파일: pod.go 프로젝트: dataVerve/jetpack
func (c *Pod) stage2(app types.ACName, user, group string, cwd string, env []string, exec ...string) error {
	if strings.HasPrefix(user, "/") || strings.HasPrefix(group, "/") {
		return errors.New("Path-based user/group not supported yet, sorry")
	}

	hasPath := false
	hasTerm := false

	for _, envVar := range env {
		if strings.HasPrefix(envVar, "PATH=") {
			hasPath = true
		}
		if strings.HasPrefix(envVar, "TERM=") {
			hasTerm = true
		}
	}

	if !hasPath {
		env = append(env, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
	}

	if !hasTerm {
		// TODO: TERM= only if we're attached to a terminal
		term := os.Getenv("TERM")
		if term == "" {
			term = "vt100"
		}
		env = append(env, "TERM="+term)
	}

	// Ensure jail is created
	jid := c.Jid()
	if jid == 0 {
		if err := errors.Trace(c.runJail("-c")); err != nil {
			return errors.Trace(err)
		}
		jid = c.Jid()
		if jid == 0 {
			panic("Could not start jail")
		}
	}

	mds, err := c.Host.MetadataURL(c.UUID)
	if err != nil {
		return errors.Trace(err)
	}

	pwf, err := passwd.ReadPasswd(c.Path("rootfs", "app", app.String(), "rootfs", "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(c.Path("rootfs", "app", app.String(), "rootfs", "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 = "/"
	}

	stage2 := filepath.Join(Config().MustGetString("path.libexec"), "stage2")
	args := []string{
		fmt.Sprintf("%d:%d:%d:%s:%s", jid, pwent.Uid, pwent.Gid, app, cwd),
		"AC_METADATA_URL=" + mds,
		"USER="******"LOGNAME=" + pwent.Username,
		"HOME=" + pwent.Home,
		"SHELL=" + pwent.Shell,
	}
	args = append(args, env...)
	args = append(args, exec...)
	return run.Command(stage2, args...).Run()
}
예제 #10
0
파일: pod.go 프로젝트: sdoumbouya/jetpack
func (c *Pod) stage2(app types.ACName, user, group string, cwd string, env []string, exec ...string) error {
	if strings.HasPrefix(user, "/") || strings.HasPrefix(group, "/") {
		return errors.New("Path-based user/group not supported yet, sorry")
	}

	// Ensure jail is created
	jid := c.Jid()
	if jid == 0 {
		if err := errors.Trace(c.runJail("-c")); err != nil {
			return errors.Trace(err)
		}
		jid = c.Jid()
		if jid == 0 {
			panic("Could not start jail")
		}
	}

	mds, err := c.Host.MetadataURL(c.UUID)
	if err != nil {
		return errors.Trace(err)
	}

	pwf, err := passwd.ReadPasswd(c.Path("rootfs", "app", app.String(), "rootfs", "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(c.Path("rootfs", "app", app.String(), "rootfs", "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 = "/"
	}

	stage2 := filepath.Join(Config().MustGetString("path.libexec"), "stage2")
	args := []string{
		"-jid", strconv.Itoa(jid),
		"-app", string(app),
		"-mds", mds,
		"-uid", strconv.Itoa(pwent.Uid),
		"-gid", strconv.Itoa(pwent.Gid),
		"-cwd", cwd,
		"-setenv", "USER="******"-setenv", "LOGNAME=" + pwent.Username,
		"-setenv", "HOME=" + pwent.Home,
		"-setenv", "SHELL=" + pwent.Shell,
	}
	args = append(args, env...)
	args = append(args, exec...)
	return run.Command(stage2, args...).Run()
}