func ExtractManifestContentFromAci(aciPath string) ([]byte, error) { fields := data.WithField("file", aciPath) input, err := os.Open(aciPath) if err != nil { return nil, errs.WithEF(err, fields, "Cannot open file") } defer input.Close() tr, err := aci.NewCompressedTarReader(input) if err != nil { return nil, errs.WithEF(err, fields, "Cannot open file as tar") } 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 { return nil, errs.WithEF(err, fields, "Cannot read manifest content in tar") } return bytes, nil } default: return nil, errs.WithEF(err, fields, "error reading tarball file") } } return nil, errs.WithEF(err, fields, "Cannot found manifest in file") }
func runCatManifest(args []string) (exit int) { if len(args) != 1 { stderr("cat-manifest: Must provide one file") return 1 } inputFile = args[0] input, err := os.Open(inputFile) if err != nil { stderr("cat-manifest: Cannot open %s: %v", inputFile, err) return 1 } defer input.Close() tr, err := aci.NewCompressedTarReader(input) if err != nil { stderr("cat-manifest: Cannot extract %s: %v", inputFile, err) return 1 } defer tr.Close() err = extractManifest(tr.Reader, nil, true, nil) if err != nil { stderr("cat-manifest: Unable to read %s: %v", inputFile, err) return 1 } return }
// 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 Validate(f io.ReadSeeker) error { tr, err := specaci.NewCompressedTarReader(f) defer tr.Close() if err != nil { return err } if err := specaci.ValidateArchive(tr.Reader); err != nil { return err } return nil }
// Extract expands the ACI file to a temporary directory, returning // the directory path where the ACI was expanded or an error func Extract(f io.ReadSeeker) (string, error) { fileMode := os.FileMode(0755) tr, err := specaci.NewCompressedTarReader(f) if err != nil { return "", err } defer tr.Close() // Extract archive to temporary directory dir, err := ioutil.TempDir("", "") if err != nil { return "", err } for { hdr, err := tr.Reader.Next() if err == io.EOF { break } if err != nil { return "", fmt.Errorf("%v\n%v", ErrNext, err) } file := filepath.Join(dir, hdr.Name) switch hdr.Typeflag { case tar.TypeReg: w, err := os.Create(file) if err != nil { return "", fmt.Errorf("%v: %v\n%v", ErrCreatingFile, file, err) } defer w.Close() _, err = io.Copy(w, tr) if err != nil { return "", fmt.Errorf("%v: %v\n%v", ErrCopyingFile, file, err) } err = os.Chmod(file, fileMode) if err != nil { return "", fmt.Errorf("%v: %v\n%v", ErrChmod, file, err) } case tar.TypeDir: err = os.MkdirAll(file, fileMode) if err != nil { return "", fmt.Errorf("%v: %v\n%v", ErrMkdirAll, file, err) } default: return "", fmt.Errorf("%v: %v", ErrUntar, hdr.Name) } } return dir, nil }
// 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 ValidateACI(aciPath string) error { aciFile, err := os.Open(aciPath) if err != nil { return err } defer aciFile.Close() reader, err := aci.NewCompressedTarReader(aciFile) if err != nil { return err } if err := aci.ValidateArchive(reader); err != nil { return err } return nil }
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 }
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 }
func writeACI(layer io.ReadSeeker, manifest schema.ImageManifest, curPwl []string, output string, compress bool) (*schema.ImageManifest, error) { aciFile, err := os.Create(output) if err != nil { return nil, fmt.Errorf("error creating ACI file: %v", err) } defer aciFile.Close() var w io.WriteCloser = aciFile if compress { w = gzip.NewWriter(aciFile) defer w.Close() } trw := tar.NewWriter(w) defer trw.Close() if err := WriteRootfsDir(trw); err != nil { return nil, fmt.Errorf("error writing rootfs entry: %v", err) } var whiteouts []string convWalker := func(t *tarball.TarFile) error { name := t.Name() if name == "./" { return nil } t.Header.Name = path.Join("rootfs", name) absolutePath := strings.TrimPrefix(t.Header.Name, "rootfs") if strings.Contains(t.Header.Name, "/.wh.") { whiteouts = append(whiteouts, strings.Replace(absolutePath, ".wh.", "", 1)) return nil } if t.Header.Typeflag == tar.TypeLink { t.Header.Linkname = path.Join("rootfs", t.Linkname()) } if err := trw.WriteHeader(t.Header); err != nil { return err } if _, err := io.Copy(trw, t.TarStream); err != nil { return err } if !util.In(curPwl, absolutePath) { curPwl = append(curPwl, absolutePath) } return nil } tr, err := aci.NewCompressedTarReader(layer) if err == nil { defer tr.Close() // write files in rootfs/ if err := tarball.Walk(*tr.Reader, convWalker); err != nil { return nil, err } } else { // ignore errors: empty layers in tars generated by docker save are not // valid tar files so we ignore errors trying to open them. Converted // ACIs will have the manifest and an empty rootfs directory in any // case. } newPwl := subtractWhiteouts(curPwl, whiteouts) manifest.PathWhitelist = newPwl if err := WriteManifest(trw, manifest); err != nil { return nil, fmt.Errorf("error writing manifest: %v", err) } return &manifest, nil }
func runPatchManifest(args []string) (exit int) { var fh *os.File var err error if patchReplace && patchOverwrite { stderr("patch-manifest: Cannot use both --replace and --overwrite") return 1 } if !patchReplace && len(args) != 2 { stderr("patch-manifest: Must provide input and output files (or use --replace)") return 1 } if patchReplace && len(args) != 1 { stderr("patch-manifest: Must provide one file") return 1 } if patchManifestFile != "" && (patchName != "" || patchExec != "" || patchUser != "" || patchGroup != "" || patchCaps != "" || patchMounts != "") { stderr("patch-manifest: --manifest is incompatible with other manifest editing options") return 1 } inputFile = args[0] // Prepare output writer if patchReplace { fh, err = ioutil.TempFile(path.Dir(inputFile), ".actool-tmp."+path.Base(inputFile)+"-") if err != nil { stderr("patch-manifest: Cannot create temporary file: %v", err) return 1 } } else { outputFile = args[1] ext := filepath.Ext(outputFile) if ext != schema.ACIExtension { stderr("patch-manifest: Extension must be %s (given %s)", schema.ACIExtension, ext) return 1 } mode := os.O_CREATE | os.O_WRONLY if patchOverwrite { mode |= os.O_TRUNC } else { mode |= os.O_EXCL } fh, err = os.OpenFile(outputFile, mode, 0644) if err != nil { if os.IsExist(err) { stderr("patch-manifest: Output file exists (try --overwrite)") } else { stderr("patch-manifest: Unable to open output %s: %v", outputFile, err) } return 1 } } var gw *gzip.Writer var w io.WriteCloser = fh if !patchNocompress { gw = gzip.NewWriter(fh) w = gw } tw := tar.NewWriter(w) defer func() { tw.Close() if !patchNocompress { gw.Close() } fh.Close() if exit != 0 && !patchOverwrite { os.Remove(fh.Name()) } }() // Prepare input reader input, err := os.Open(inputFile) if err != nil { stderr("patch-manifest: Cannot open %s: %v", inputFile, err) return 1 } defer input.Close() tr, err := aci.NewCompressedTarReader(input) if err != nil { stderr("patch-manifest: Cannot extract %s: %v", inputFile, err) return 1 } defer tr.Close() var newManifest []byte if patchManifestFile != "" { mr, err := os.Open(patchManifestFile) if err != nil { stderr("patch-manifest: Cannot open %s: %v", patchManifestFile, err) return 1 } defer input.Close() newManifest, err = ioutil.ReadAll(mr) if err != nil { stderr("patch-manifest: Cannot read %s: %v", patchManifestFile, err) return 1 } } err = extractManifest(tr.Reader, tw, false, newManifest) if err != nil { stderr("patch-manifest: Unable to read %s: %v", inputFile, err) return 1 } if patchReplace { err = os.Rename(fh.Name(), inputFile) if err != nil { stderr("patch-manifest: Cannot rename %q to %q: %v", fh.Name, inputFile, err) return 1 } } return }
func writeACI(layer io.ReadSeeker, manifest schema.ImageManifest, curPwl []string, output string, compression common.Compression) (*schema.ImageManifest, error) { aciFile, err := os.Create(output) if err != nil { return nil, fmt.Errorf("error creating ACI file: %v", err) } defer aciFile.Close() var w io.WriteCloser = aciFile if compression == common.GzipCompression { w = gzip.NewWriter(aciFile) defer w.Close() } trw := tar.NewWriter(w) defer trw.Close() if err := WriteRootfsDir(trw); err != nil { return nil, fmt.Errorf("error writing rootfs entry: %v", err) } fileMap := make(map[string]struct{}) var whiteouts []string convWalker := func(t *tarball.TarFile) error { name := t.Name() if name == "./" { return nil } t.Header.Name = path.Join("rootfs", name) absolutePath := strings.TrimPrefix(t.Header.Name, "rootfs") if filepath.Clean(absolutePath) == "/dev" && t.Header.Typeflag != tar.TypeDir { return fmt.Errorf(`invalid layer: "/dev" is not a directory`) } fileMap[absolutePath] = struct{}{} if strings.Contains(t.Header.Name, "/.wh.") { whiteouts = append(whiteouts, strings.Replace(absolutePath, ".wh.", "", 1)) return nil } if t.Header.Typeflag == tar.TypeLink { t.Header.Linkname = path.Join("rootfs", t.Linkname()) } if err := trw.WriteHeader(t.Header); err != nil { return err } if _, err := io.Copy(trw, t.TarStream); err != nil { return err } if !util.In(curPwl, absolutePath) { curPwl = append(curPwl, absolutePath) } return nil } tr, err := aci.NewCompressedTarReader(layer) if err == nil { defer tr.Close() // write files in rootfs/ if err := tarball.Walk(*tr.Reader, convWalker); err != nil { return nil, err } } else { // ignore errors: empty layers in tars generated by docker save are not // valid tar files so we ignore errors trying to open them. Converted // ACIs will have the manifest and an empty rootfs directory in any // case. } newPwl := subtractWhiteouts(curPwl, whiteouts) newPwl, err = writeStdioSymlinks(trw, fileMap, newPwl) if err != nil { return nil, err } // Let's copy the newly generated PathWhitelist to avoid unintended // side-effects manifest.PathWhitelist = make([]string, len(newPwl)) copy(manifest.PathWhitelist, newPwl) if err := WriteManifest(trw, manifest); err != nil { return nil, fmt.Errorf("error writing manifest: %v", err) } return &manifest, nil }