예제 #1
0
파일: image.go 프로젝트: sdoumbouya/jetpack
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
}
예제 #2
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
}