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 }
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()) } } }
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() }
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 }
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)) } } }
func ZPools() ([]string, error) { return run.Command("/sbin/zpool", "list", "-Hp", "-oname").OutputLines() }
// 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 } }
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 }
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() }
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() }