func (cmd *Builder) writeACI() (string, error) { mode := os.O_CREATE | os.O_WRONLY | os.O_TRUNC filename, err := cmd.custom.GetImageFileName() if err != nil { return "", err } of, err := os.OpenFile(filename, mode, 0644) if err != nil { return "", fmt.Errorf("Error opening output file: %v", err) } defer of.Close() gw := gzip.NewWriter(of) defer gw.Close() tr := tar.NewWriter(gw) defer tr.Close() // FIXME: the files in the tar archive are added with the // wrong uid/gid. The uid/gid of the aci builder leaks in the // tar archive. See: https://github.com/appc/goaci/issues/16 iw := aci.NewImageWriter(*cmd.manifest, tr) paths := cmd.custom.GetCommonPaths() if err := filepath.Walk(paths.AciDir, aci.BuildWalker(paths.AciDir, iw, nil)); err != nil { return "", err } if err := iw.Close(); err != nil { return "", err } return of.Name(), nil }
func makeACI(output io.Writer, manifest schema.ImageManifest, files ...fileInfo) error { manblob, err := json.Marshal(detailedManifest()) if err != nil { return err } tmpexpandedaci := mustTempDir() defer os.RemoveAll(tmpexpandedaci) err = ioutil.WriteFile(path.Join(tmpexpandedaci, aci.ManifestFile), manblob, 0644) if err != nil { return err } err = os.Mkdir(path.Join(tmpexpandedaci, aci.RootfsDir), 0755) if err != nil { return err } for _, f := range files { err := ioutil.WriteFile(path.Join(tmpexpandedaci, aci.RootfsDir, f.name), f.contents, 0644) if err != nil { return err } } aw := aci.NewImageWriter(manifest, tar.NewWriter(output)) err = filepath.Walk(tmpexpandedaci, aci.BuildWalker(tmpexpandedaci, aw, nil)) aw.Close() if err != nil { return err } return nil }
// overwriteManifest takes an ACI and outputs another with the original manifest // overwritten by the given manifest. func overwriteManifest(in io.ReadSeeker, out io.Writer, manifest *schema.ImageManifest) error { outTar := tar.NewWriter(out) iw := aci.NewImageWriter(*manifest, outTar) defer iw.Close() tr, err := aci.NewCompressedTarReader(in) if err != nil { return err } for { hdr, err := tr.Next() switch err { case io.EOF: return nil case nil: if filepath.Clean(hdr.Name) != aci.ManifestFile { if err := iw.AddFile(hdr, tr); err != nil { return fmt.Errorf("error writing to image writer: %v", err) } } default: return fmt.Errorf("error extracting tarball: %v", err) } } }
func runBuild(args []string) (exit int) { if len(args) != 2 { stderr("build: Must provide directory and output file") return 1 } root := args[0] tgt := args[1] ext := filepath.Ext(tgt) if ext != schema.ACIExtension { stderr("build: Extension must be %s (given %s)", schema.ACIExtension, ext) return 1 } mode := os.O_CREATE | os.O_WRONLY if buildOverwrite { mode |= os.O_TRUNC } else { mode |= os.O_EXCL } fh, err := os.OpenFile(tgt, mode, 0644) if err != nil { if os.IsExist(err) { stderr("build: Target file exists (try --overwrite)") } else { stderr("build: Unable to open target %s: %v", tgt, err) } return 1 } var gw *gzip.Writer var r io.WriteCloser = fh if !buildNocompress { gw = gzip.NewWriter(fh) r = gw } tr := tar.NewWriter(r) defer func() { tr.Close() if !buildNocompress { gw.Close() } fh.Close() if exit != 0 && !buildOverwrite { os.Remove(tgt) } }() // TODO(jonboulle): stream the validation so we don't have to walk the rootfs twice if err := aci.ValidateLayout(root); err != nil { stderr("build: Layout failed validation: %v", err) return 1 } mpath := filepath.Join(root, aci.ManifestFile) b, err := ioutil.ReadFile(mpath) if err != nil { stderr("build: Unable to read Image Manifest: %v", err) return 1 } var im schema.ImageManifest if err := im.UnmarshalJSON(b); err != nil { stderr("build: Unable to load Image Manifest: %v", err) return 1 } iw := aci.NewImageWriter(im, tr) err = filepath.Walk(root, aci.BuildWalker(root, iw)) if err != nil { stderr("build: Error walking rootfs: %v", err) return 1 } err = iw.Close() if err != nil { stderr("build: Unable to close image %s: %v", tgt, err) return 1 } return }
// buildAci builds a target aci from the root directory using any uid shift // information from uidRange. func buildAci(root, manifestPath, target string, uidRange *user.UidRange) (e error) { mode := os.O_CREATE | os.O_WRONLY if flagOverwriteACI { mode |= os.O_TRUNC } else { mode |= os.O_EXCL } aciFile, err := os.OpenFile(target, mode, 0644) if err != nil { if os.IsExist(err) { return errors.New("target file exists (try --overwrite)") } else { return errwrap.Wrap(fmt.Errorf("unable to open target %s", target), err) } } gw := gzip.NewWriter(aciFile) tr := tar.NewWriter(gw) defer func() { tr.Close() gw.Close() aciFile.Close() // e is implicitly assigned by the return statement. As defer runs // after return, but before actually returning, this works. if e != nil { os.Remove(target) } }() b, err := ioutil.ReadFile(manifestPath) if err != nil { return errwrap.Wrap(errors.New("unable to read Image Manifest"), err) } var im schema.ImageManifest if err := im.UnmarshalJSON(b); err != nil { return errwrap.Wrap(errors.New("unable to load Image Manifest"), err) } iw := aci.NewImageWriter(im, tr) // Unshift uid and gid when pod was started with --private-user (user namespace) var walkerCb aci.TarHeaderWalkFunc = func(hdr *tar.Header) bool { if uidRange != nil { uid, gid, err := uidRange.UnshiftRange(uint32(hdr.Uid), uint32(hdr.Gid)) if err != nil { stderr.PrintE("error unshifting gid and uid", err) return false } hdr.Uid, hdr.Gid = int(uid), int(gid) } return true } if err := filepath.Walk(root, aci.BuildWalker(root, iw, walkerCb)); err != nil { return errwrap.Wrap(errors.New("error walking rootfs"), err) } if err = iw.Close(); err != nil { return errwrap.Wrap(fmt.Errorf("unable to close image %s", target), err) } return }
func createACI(dir string, imageName string) error { var errStr string var errRes error buildNocompress := true root := dir tgt := imageName ext := filepath.Ext(tgt) if ext != schema.ACIExtension { errStr = fmt.Sprintf("build: Extension must be %s (given %s)", schema.ACIExtension, ext) errRes = errors.New(errStr) return errRes } if err := aci.ValidateLayout(root); err != nil { if e, ok := err.(aci.ErrOldVersion); ok { if debugEnabled { log.Printf("build: Warning: %v. Please update your manifest.", e) } } else { errStr = fmt.Sprintf("build: Layout failed validation: %v", err) errRes = errors.New(errStr) return errRes } } mode := os.O_CREATE | os.O_WRONLY | os.O_TRUNC fh, err := os.OpenFile(tgt, mode, 0644) if err != nil { errStr = fmt.Sprintf("build: Unable to open target %s: %v", tgt, err) errRes = errors.New(errStr) return errRes } var gw *gzip.Writer var r io.WriteCloser = fh if !buildNocompress { gw = gzip.NewWriter(fh) r = gw } tr := tar.NewWriter(r) defer func() { tr.Close() if !buildNocompress { gw.Close() } fh.Close() }() mpath := filepath.Join(root, aci.ManifestFile) b, err := ioutil.ReadFile(mpath) if err != nil { errStr = fmt.Sprintf("build: Unable to read Image Manifest: %v", err) errRes = errors.New(errStr) return errRes } var im schema.ImageManifest if err := im.UnmarshalJSON(b); err != nil { errStr = fmt.Sprintf("build: Unable to load Image Manifest: %v", err) errRes = errors.New(errStr) return errRes } iw := aci.NewImageWriter(im, tr) err = filepath.Walk(root, aci.BuildWalker(root, iw, nil)) if err != nil { errStr = fmt.Sprintf("build: Error walking rootfs: %v", err) errRes = errors.New(errStr) return errRes } err = iw.Close() if err != nil { errStr = fmt.Sprintf("build: Unable to close image %s: %v", tgt, err) errRes = errors.New(errStr) return errRes } return nil }
// BuildACI takes an input directory that conforms to the ACI specification, // and outputs an optionally compressed ACI image. func BuildACI(root string, tgt string, overwrite bool, nocompress bool) (ret error) { ext := filepath.Ext(tgt) if ext != schema.ACIExtension { ret = fmt.Errorf("build: Extension must be %s (given %s)", schema.ACIExtension, ext) return } mode := os.O_CREATE | os.O_WRONLY if overwrite { mode |= os.O_TRUNC } else { mode |= os.O_EXCL } fh, err := os.OpenFile(tgt, mode, 0644) if err != nil { if os.IsExist(err) { ret = fmt.Errorf("build: Target file exists") } else { ret = fmt.Errorf("build: Unable to open target %s: %v", tgt, err) } return } var gw *gzip.Writer var r io.WriteCloser = fh if !nocompress { gw = gzip.NewWriter(fh) r = gw } tr := tar.NewWriter(r) defer func() { tr.Close() if !nocompress { gw.Close() } fh.Close() if ret != nil && !overwrite { os.Remove(tgt) } }() // TODO(jonboulle): stream the validation so we don't have to walk the rootfs twice if err := aci.ValidateLayout(root); err != nil { ret = fmt.Errorf("build: Layout failed validation: %v", err) return } mpath := filepath.Join(root, aci.ManifestFile) b, err := ioutil.ReadFile(mpath) if err != nil { ret = fmt.Errorf("build: Unable to read Image Manifest: %v", err) return } var im schema.ImageManifest if err := im.UnmarshalJSON(b); err != nil { ret = fmt.Errorf("build: Unable to load Image Manifest: %v", err) return } iw := aci.NewImageWriter(im, tr) err = filepath.Walk(root, aci.BuildWalker(root, iw)) if err != nil { ret = fmt.Errorf("build: Error walking rootfs: %v", err) return } err = iw.Close() if err != nil { ret = fmt.Errorf("build: Unable to close image %s: %v", tgt, err) return } return nil }
// Write will produce the resulting ACI from the current build context, saving // it to the given path, optionally signing it. func (a *ACBuild) Write(output string, overwrite, sign bool, gpgflags []string) (err error) { if err = a.lock(); err != nil { return err } defer func() { if err1 := a.unlock(); err == nil { err = err1 } }() man, err := util.GetManifest(a.CurrentACIPath) if err != nil { return err } if man.App != nil && len(man.App.Exec) == 0 { fmt.Fprintf(os.Stderr, "warning: exec command was never set.\n") } if man.Name == types.ACIdentifier(placeholdername) { return fmt.Errorf("can't write ACI, name was never set") } fileFlags := os.O_CREATE | os.O_WRONLY _, err = os.Stat(output) switch { case os.IsNotExist(err): break case err != nil: return err default: if !overwrite { return fmt.Errorf("ACI already exists: %s", output) } fileFlags |= os.O_TRUNC } // open/create the aci file ofile, err := os.OpenFile(output, fileFlags, 0644) if err != nil { return err } defer ofile.Close() defer func() { // When write is done, if an error is encountered remove the partial // ACI that had been written. if err != nil { os.Remove(output) os.Remove(output + ".asc") } }() // setup compression gzwriter := gzip.NewWriter(ofile) defer gzwriter.Close() // create the aci writer aw := aci.NewImageWriter(*man, tar.NewWriter(gzwriter)) err = filepath.Walk(a.CurrentACIPath, aci.BuildWalker(a.CurrentACIPath, aw, nil)) defer aw.Close() if err != nil { pathErr, ok := err.(*os.PathError) if !ok { fmt.Printf("not a path error!\n") return err } syscallErrno, ok := pathErr.Err.(syscall.Errno) if !ok { fmt.Printf("not a syscall errno!\n") return err } if pathErr.Op == "open" && syscallErrno != syscall.EACCES { return err } problemPath := pathErr.Path[len(path.Join(a.CurrentACIPath, aci.RootfsDir)):] return fmt.Errorf("%q: permission denied - call write as root", problemPath) } if sign { err = signACI(output, output+".asc", gpgflags) if err != nil { return err } } return nil }