func appDetails(app *types.App) string { u := app.User if u == "" { u = "0" } g := app.Group if g == "" { g = "0" } rv := fmt.Sprintf(" Exec\t[%v:%v] %v\n", u, g, run.ShellEscape(app.Exec...)) if len(app.Ports) != 0 { ports := make([]string, len(app.Ports)) nameLen := 0 for _, port := range app.Ports { if nameLen < len(port.Name) { nameLen = len(port.Name) } } format := fmt.Sprintf("%%%dv:%%v/%%v", nameLen) for i, port := range app.Ports { ports[i] = fmt.Sprintf(format, port.Name, port.Protocol, port.Port) if port.Count > 1 { ports[i] += fmt.Sprintf("+%d", port.Count) } } rv += " Ports\t" + strings.Join(ports, "\n\t") + "\n" } if len(app.MountPoints) != 0 { mps := make([]string, len(app.MountPoints)) nameLen := 0 for _, mp := range app.MountPoints { if len(mp.Name) > nameLen { nameLen = len(mp.Name) } } format := fmt.Sprintf("%%%dv:%%v", nameLen) for i, mp := range app.MountPoints { mps[i] = fmt.Sprintf(format, mp.Name, mp.Path) if mp.ReadOnly { mps[i] += " (ro)" } } rv += " Mount Points\t" + strings.Join(mps, "\n\t") + "\n" } return rv }
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 }