func newPod(h *Host, id uuid.UUID) *Pod { if id == nil { id = uuid.NewRandom() } return &Pod{ Host: h, UUID: id, ui: ui.NewUI("yellow", "pod", id.String()), } }
func NewImage(h *Host, id uuid.UUID) *Image { if id == nil { id = uuid.NewRandom() } return &Image{ Host: h, UUID: id, Manifest: *schema.BlankImageManifest(), ui: ui.NewUI("blue", "image", id.String()), } }
func NewHost() (*Host, error) { h := Host{mdsUid: -1, mdsGid: -1} // FIXME: changing global switch based on struct instance // variable. There should be only one instance created at a time // anyway, but it's kind of ugly. // If debug is already on (e.g. from a command line switch), we keep // it. ui.Debug = ui.Debug || Config().GetBool("debug", false) h.ui = ui.NewUI("green", "jetpack", "") if ds, err := zfs.GetDataset(Config().MustGetString("root.zfs")); err == zfs.ErrNotFound { return &h, nil } else if err != nil { return nil, err } else { h.Dataset = ds } return &h, 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.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 }
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 }