func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error { defer func() { if rc, ok := im.(io.Closer); ok { rc.Close() } }() if !imOK { return ErrNoManifest } if !rfsOK { return ErrNoRootFS } b, err := ioutil.ReadAll(im) if err != nil { return fmt.Errorf("error reading image manifest: %v", err) } var a schema.ImageManifest if err := a.UnmarshalJSON(b); err != nil { return fmt.Errorf("image manifest validation failed: %v", err) } if a.ACVersion.LessThanMajor(schema.AppContainerVersion) { return ErrOldVersion{ version: a.ACVersion, } } for _, f := range files { if !strings.HasPrefix(f, "rootfs") { return fmt.Errorf("unrecognized file path in layout: %q", f) } } return nil }
// ManifestFromImage extracts a new schema.ImageManifest from the given ACI image. func ManifestFromImage(rs io.ReadSeeker) (*schema.ImageManifest, error) { var im schema.ImageManifest tr, err := NewCompressedTarReader(rs) if err != nil { return nil, err } defer tr.Close() for { hdr, err := tr.Next() switch err { case io.EOF: return nil, errors.New("missing manifest") case nil: if filepath.Clean(hdr.Name) == ManifestFile { data, err := ioutil.ReadAll(tr) if err != nil { return nil, err } if err := im.UnmarshalJSON(data); err != nil { return nil, err } return &im, nil } default: return nil, fmt.Errorf("error extracting tarball: %v", err) } } }
func createImageManifest(imj string) (*schema.ImageManifest, error) { var im schema.ImageManifest err := im.UnmarshalJSON([]byte(imj)) if err != nil { return nil, err } return &im, nil }
func loadAndValidateACI(path, server string) (data []byte, labels map[string]string, err error) { rc, err := openFileMaybeGzipped(path) if err != nil { return nil, nil, fmt.Errorf("Failed to open %q: %v", path, err) } defer rc.Close() tr := tar.NewReader(rc) var manifest []byte var foundRootfs bool for { header, err := tr.Next() if err != nil { break } if header.Name == "manifest" { buf := bytes.NewBuffer(nil) if _, err := io.Copy(buf, tr); err != nil { return nil, nil, fmt.Errorf("Failed reading archive: %v", err) } manifest = buf.Bytes() } else if header.Name == "rootfs" { foundRootfs = true } else if !strings.HasPrefix(header.Name, "rootfs/") { return nil, nil, fmt.Errorf("Invalid aci, contains unexpected filename: %q.", header.Name) } } if !foundRootfs { return nil, nil, fmt.Errorf("Didn't find rootfs.") } var im schema.ImageManifest if err := im.UnmarshalJSON(manifest); err != nil { return nil, nil, fmt.Errorf("Failed to parse manifest: %v", err) } labels = make(map[string]string) for _, label := range im.Labels { switch label.Name.String() { case "version": labels["version"] = label.Value case "os": labels["os"] = label.Value case "arch": labels["arch"] = label.Value } } if labels["version"] == "" { return nil, nil, fmt.Errorf("Unspecified version is not supported.") } // if !strings.HasPrefix(im.Name.String(), server+"/") && server != testServer { // return nil, nil, fmt.Errorf("Image name is %q which is not part of the server %q.", im.Name, server) // } labels["name"] = im.Name.String() data, err = ioutil.ReadFile(path) if err != nil { return nil, nil, fmt.Errorf("Unable to read file %q: %v", path, err) } return data, labels, nil }
func readManifest(path string) (*schema.ImageManifest, error) { b, err := ioutil.ReadFile(path) if err != nil { return nil, errorf(err.Error()) } i := schema.ImageManifest{} if err := i.UnmarshalJSON(b); err != nil { return nil, errorf(err.Error()) } return &i, nil }
func prettyPrintMan(manblob []byte) []byte { var man schema.ImageManifest err := man.UnmarshalJSON(manblob) if err != nil { panic(err) } manblob2, err := json.MarshalIndent(man, "", " ") if err != nil { panic(err) } return manblob2 }
// CatManifest will print to stdout the manifest from the ACI stored at // aciPath, optionally inserting whitespace to make it more human readable. func CatManifest(aciPath string, prettyPrint bool) (err error) { finfo, err := os.Stat(aciPath) switch { case os.IsNotExist(err): return fmt.Errorf("no such file or directory: %s", aciPath) case err != nil: return err case finfo.IsDir(): return fmt.Errorf("%s is a directory, not an ACI", aciPath) default: break } file, err := os.Open(aciPath) if err != nil { return err } defer file.Close() tr, err := aci.NewCompressedTarReader(file) if err != nil { return fmt.Errorf("error decompressing image: %v", err) } defer tr.Close() for { hdr, err := tr.Next() switch { case err == io.EOF: return fmt.Errorf("manifest not found in ACI %s", aciPath) case err != nil: return err case hdr.Name == "manifest": manblob, err := ioutil.ReadAll(tr) if err != nil { return err } var man schema.ImageManifest err = man.UnmarshalJSON(manblob) if err != nil { return err } return util.PrintManifest(&man, prettyPrint) } } }
func checkManifest(t *testing.T, workingDir string, wantedManifest schema.ImageManifest) { acipath := path.Join(workingDir, ".acbuild", "currentaci") manblob, err := ioutil.ReadFile(path.Join(acipath, aci.ManifestFile)) if err != nil { panic(err) } var man schema.ImageManifest err = man.UnmarshalJSON(manblob) if err != nil { t.Errorf("invalid manifest schema: %v", err) } if str := pretty.Compare(man, wantedManifest); str != "" { t.Errorf("unexpected manifest:\n%s", str) } }
func ExtractManifestFromAci(aciPath string) schema.ImageManifest { input, err := os.Open(aciPath) if err != nil { panic("cat-manifest: Cannot open %s: %v" + aciPath + err.Error()) } defer input.Close() tr, err := aci.NewCompressedTarReader(input) if err != nil { panic("cat-manifest: Cannot open tar %s: %v" + aciPath + err.Error()) } im := schema.ImageManifest{} Tar: for { hdr, err := tr.Next() switch err { case io.EOF: break Tar case nil: if filepath.Clean(hdr.Name) == aci.ManifestFile { bytes, err := ioutil.ReadAll(tr) if err != nil { panic(err) } err = im.UnmarshalJSON(bytes) if err != nil { panic(err) } return im } default: panic("error reading tarball: %v" + err.Error()) } } panic("Cannot found manifest if aci") return im }
// NewACI creates a new ACI in the given directory with the given image // manifest and entries. // Used for testing. func NewACI(dir string, manifest string, entries []*ACIEntry) (*os.File, error) { var im schema.ImageManifest if err := im.UnmarshalJSON([]byte(manifest)); err != nil { return nil, errwrap.Wrap(errors.New("invalid image manifest"), err) } tf, err := ioutil.TempFile(dir, "") if err != nil { return nil, err } defer os.Remove(tf.Name()) tw := tar.NewWriter(tf) aw := NewImageWriter(im, tw) for _, entry := range entries { // Add default mode if entry.Header.Mode == 0 { if entry.Header.Typeflag == tar.TypeDir { entry.Header.Mode = 0755 } else { entry.Header.Mode = 0644 } } // Add calling user uid and gid or tests will fail entry.Header.Uid = os.Getuid() entry.Header.Gid = os.Getgid() sr := strings.NewReader(entry.Contents) if err := aw.AddFile(entry.Header, sr); err != nil { return nil, err } } if err := aw.Close(); err != nil { return nil, err } return tf, nil }
func GetDependencyDgrVersion(acName common.ACFullname) (int, error) { depFields := data.WithField("dependency", acName.String()) out, err := Home.Rkt.CatManifest(acName.String()) if err != nil { return 0, errs.WithEF(err, depFields, "Dependency not found") } im := schema.ImageManifest{} if err := im.UnmarshalJSON([]byte(out)); err != nil { return 0, errs.WithEF(err, depFields.WithField("content", out), "Cannot read manifest cat by rkt image") } version, ok := im.Annotations.Get(common.ManifestDrgVersion) var val int if ok { val, err = strconv.Atoi(version) if err != nil { return 0, errs.WithEF(err, depFields.WithField("version", version), "Failed to parse "+common.ManifestDrgVersion+" from manifest") } } return val, nil }
func testCat(t *testing.T, workingDir string) { wantedManblob, err := ioutil.ReadFile(path.Join(workingDir, ".acbuild", "currentaci", aci.ManifestFile)) if err != nil { panic(err) } wantedManblob = append(wantedManblob, byte('\n')) var man schema.ImageManifest err = man.UnmarshalJSON(wantedManblob) if err != nil { panic(err) } _, manblob, _, err := runACBuild(workingDir, "cat-manifest") if err != nil { t.Fatalf("%v", err) } if manblob != string(wantedManblob) { t.Fatalf("printed manifest and manifest on disk differ") } wantedManblob = prettyPrintMan(wantedManblob) wantedManblob = append(wantedManblob, byte('\n')) _, manblob, _, err = runACBuild(workingDir, "cat-manifest", "--pretty-print") if err != nil { t.Fatalf("%v", err) } if manblob != string(wantedManblob) { t.Fatalf("pretty printed manifest and manifest on disk differ") } checkManifest(t, workingDir, man) checkEmptyRootfs(t, workingDir) }
// 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 }
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 }
func runValidate(args []string) (exit int) { if len(args) < 1 { stderr("must pass one or more files") return 1 } for _, path := range args { vt := valType fi, err := os.Stat(path) if err != nil { stderr("unable to access %s: %v", path, err) return 1 } var fh *os.File if fi.IsDir() { switch vt { case typeImageLayout: case "": vt = typeImageLayout case typeManifest, typeAppImage: stderr("%s is a directory (wrong --type?)", path) return 1 default: // should never happen panic(fmt.Sprintf("unexpected type: %v", vt)) } } else { fh, err = os.Open(path) if err != nil { stderr("%s: unable to open: %v", path, err) return 1 } } if vt == "" { vt, err = detectValType(fh) if err != nil { stderr("%s: error detecting file type: %v", path, err) return 1 } } switch vt { case typeImageLayout: err = aci.ValidateLayout(path) if err != nil { stderr("%s: invalid image layout: %v", path, err) exit = 1 } else if globalFlags.Debug { stderr("%s: valid image layout", path) } case typeAppImage: tr, err := aci.NewCompressedTarReader(fh) if err != nil { stderr("%s: error decompressing file: %v", path, err) return 1 } err = aci.ValidateArchive(tr.Reader) tr.Close() fh.Close() if err != nil { if e, ok := err.(aci.ErrOldVersion); ok { stderr("%s: warning: %v", path, e) } else { stderr("%s: error validating: %v", path, err) exit = 1 } } else if globalFlags.Debug { stderr("%s: valid app container image", path) } case typeManifest: b, err := ioutil.ReadAll(fh) fh.Close() if err != nil { stderr("%s: unable to read file %s", path, err) return 1 } k := schema.Kind{} if err := k.UnmarshalJSON(b); err != nil { stderr("%s: error unmarshaling manifest: %v", path, err) return 1 } switch k.ACKind { case "ImageManifest": m := schema.ImageManifest{} err = m.UnmarshalJSON(b) case "PodManifest": m := schema.PodManifest{} err = m.UnmarshalJSON(b) default: // Should not get here; schema.Kind unmarshal should fail panic("bad ACKind") } if err != nil { stderr("%s: invalid %s: %v", path, k.ACKind, err) exit = 1 } else if globalFlags.Debug { stderr("%s: valid %s", path, k.ACKind) } default: stderr("%s: unable to detect filetype (try --type)", path) 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 BasicImageManifest() *schema.ImageManifest { im := new(schema.ImageManifest) im.UnmarshalJSON([]byte(IMAGE_MANIFEST)) return im }
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 }