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 }
// PrintManifest will print the given manifest to stdout, optionally inserting // whitespace to make it more human readable. func PrintManifest(man *schema.ImageManifest, prettyPrint bool) error { var manblob []byte var err error if prettyPrint { manblob, err = json.MarshalIndent(man, "", " ") } else { manblob, err = man.MarshalJSON() } if err != nil { return err } fmt.Println(string(manblob)) return 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) } } }
// PrepareACIDir takes a manifest and a path to rootfs and lay them out in a // temp directory that conforms to the layout of ACI image. func PrepareACIDir(manifest *schema.ImageManifest, rootfs string) (string, error) { // Create a temp directory to hold the manifest and rootfs tmpDir, err := ioutil.TempDir("", "") if err != nil { return "", fmt.Errorf("error creating temp directory: %v", err) } // Write the manifest file tmpManifest, err := os.Create(filepath.Join(tmpDir, aci.ManifestFile)) if err != nil { return "", fmt.Errorf("error creating temporary manifest: %v", err) } defer tmpManifest.Close() manifestBytes, err := manifest.MarshalJSON() if err != nil { return "", fmt.Errorf("error marshalling manifest: %v", err) } _, err = tmpManifest.Write(manifestBytes) if err != nil { return "", fmt.Errorf("error writing manifest to temp file: %v", err) } if err := tmpManifest.Sync(); err != nil { return "", fmt.Errorf("error syncing manifest file: %v", err) } if rootfs == "" { // Create an (empty) rootfs if err := os.Mkdir(filepath.Join(tmpDir, aci.RootfsDir), 0755); err != nil { return "", fmt.Errorf("error making an empty rootfs directory: %v", err) } } else { if err := shutil.CopyTree(rootfs, filepath.Join(tmpDir, aci.RootfsDir), &shutil.CopyTreeOptions{ Symlinks: true, IgnoreDanglingSymlinks: true, CopyFunction: shutil.Copy, }); err != nil { return "", fmt.Errorf("Unable to copy rootfs to a temporary directory: %s", err) } } return tmpDir, nil }
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 createACI(dir string, fs map[string]*deb, image string, m *schema.ImageManifest) error { idir, err := ioutil.TempDir(dir, "image") if err != nil { return errorf(err.Error()) } rootfs := filepath.Join(idir, "rootfs") os.MkdirAll(rootfs, 0755) for _, d := range fs { err := run(exec.Command("cp", "-a", d.Path+"/.", rootfs)) if err != nil { return err } i, err := types.SanitizeACIdentifier( fmt.Sprintf("debian.org/deb/%v", d.Name)) if err != nil { return errorf(err.Error()) } a, err := types.NewACIdentifier(i) if err != nil { return errorf(err.Error()) } m.Annotations.Set( *a, fmt.Sprintf("%v/%v", d.Arch, d.Version)) } bytes, err := m.MarshalJSON() if err != nil { return errorf(err.Error()) } if err := ioutil.WriteFile(filepath.Join(idir, "manifest"), bytes, 0644); err != nil { return errorf(err.Error()) } if err := run(exec.Command("actool", "build", "-overwrite", idir, image)); err != nil { return err } return 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) }
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 }
func patchManifest(im *schema.ImageManifest) error { if patchName != "" { name, err := types.NewACIdentifier(patchName) if err != nil { return err } im.Name = *name } if patchExec != "" { im.App.Exec = strings.Split(patchExec, " ") } if patchUser != "" { im.App.User = patchUser } if patchGroup != "" { im.App.Group = patchGroup } var app *types.App if patchCaps != "" || patchMounts != "" || patchPorts != "" || patchIsolators != "" { app = im.App if app == nil { return fmt.Errorf("no app in the manifest") } } if patchCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRetainSetName) if isolator != nil { return fmt.Errorf("isolator already exists") } // Instantiate a Isolator with the content specified by the --capability // parameter. // TODO: Instead of creating a JSON and then unmarshalling it, the isolator // should be instantiated directory. But it requires a constructor, see: // https://github.com/appc/spec/issues/268 capsList := strings.Split(patchCaps, ",") caps := fmt.Sprintf(`"set": ["%s"]`, strings.Join(capsList, `", "`)) isolatorStr := getIsolatorStr(types.LinuxCapabilitiesRetainSetName, caps) isolator = &types.Isolator{} err := isolator.UnmarshalJSON([]byte(isolatorStr)) if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchCaps, err) } app.Isolators = append(app.Isolators, *isolator) } if patchMounts != "" { mounts := strings.Split(patchMounts, ":") for _, m := range mounts { mountPoint, err := types.MountPointFromString(m) if err != nil { return fmt.Errorf("cannot parse mount point %q: %v", m, err) } app.MountPoints = append(app.MountPoints, *mountPoint) } } if patchPorts != "" { ports := strings.Split(patchPorts, ":") for _, p := range ports { port, err := types.PortFromString(p) if err != nil { return fmt.Errorf("cannot parse port %q: %v", p, err) } app.Ports = append(app.Ports, *port) } } if patchIsolators != "" { isolators := strings.Split(patchIsolators, ":") for _, is := range isolators { name, isolatorStr, err := isolatorStrFromString(is) if err != nil { return fmt.Errorf("cannot parse isolator %q: %v", is, err) } if _, ok := types.ResourceIsolatorNames[name]; !ok { return fmt.Errorf("isolator %s is not supported for patching", name) } isolator := &types.Isolator{} if err := isolator.UnmarshalJSON([]byte(isolatorStr)); err != nil { return fmt.Errorf("cannot unmarshal isolator %v: %v", isolatorStr, err) } app.Isolators = append(app.Isolators, *isolator) } } return nil }
func genManifest(path string) *schema.ImageManifest { // Get runtime.json and config.json runtimePath := path + "/runtime.json" configPath := path + "/config.json" runtime, err := ioutil.ReadFile(runtimePath) if err != nil { if debugEnabled { log.Printf("Open file runtime.json failed: %v", err) } return nil } config, err := ioutil.ReadFile(configPath) if err != nil { if debugEnabled { log.Printf("Open file config.json failed: %v", err) } return nil } var spec specs.LinuxSpec err = json.Unmarshal(config, &spec) if err != nil { if debugEnabled { log.Printf("Unmarshal config.json failed: %v", err) } return nil } var runSpec specs.LinuxRuntimeSpec err = json.Unmarshal(runtime, &runSpec) if err != nil { if debugEnabled { log.Printf("Unmarshal runtime.json failed: %v", err) } return nil } // Begin to convert runtime.json/config.json to manifest m := new(schema.ImageManifest) // 1. Assemble "acKind" field m.ACKind = schema.ImageManifestKind // 2. Assemble "acVersion" field m.ACVersion = schema.AppContainerVersion // 3. Assemble "name" field m.Name = types.ACIdentifier(manifestName) // 4. Assemble "labels" field // 4.1 "version" label := new(types.Label) label.Name = types.ACIdentifier("version") label.Value = spec.Version m.Labels = append(m.Labels, *label) // 4.2 "os" label = new(types.Label) label.Name = types.ACIdentifier("os") label.Value = spec.Platform.OS m.Labels = append(m.Labels, *label) // 4.3 "arch" label = new(types.Label) label.Name = types.ACIdentifier("arch") label.Value = spec.Platform.Arch m.Labels = append(m.Labels, *label) // 5. Assemble "app" field app := new(types.App) // 5.1 "exec" app.Exec = spec.Process.Args prefixDir := "" //var exeStr string if app.Exec == nil { app.Exec = append(app.Exec, "/bin/sh") } else { if !filepath.IsAbs(app.Exec[0]) { if spec.Process.Cwd == "" { prefixDir = "/" } else { prefixDir = spec.Process.Cwd } } app.Exec[0] = prefixDir + app.Exec[0] } // 5.2 "user" app.User = fmt.Sprintf("%d", spec.Process.User.UID) // 5.3 "group" app.Group = fmt.Sprintf("%d", spec.Process.User.GID) // 5.4 "eventHandlers" event := new(types.EventHandler) event.Name = "pre-start" for index := range runSpec.Hooks.Prestart { event.Exec = append(event.Exec, runSpec.Hooks.Prestart[index].Path) event.Exec = append(event.Exec, runSpec.Hooks.Prestart[index].Args...) event.Exec = append(event.Exec, runSpec.Hooks.Prestart[index].Env...) } if len(event.Exec) == 0 { event.Exec = append(event.Exec, "/bin/echo") event.Exec = append(event.Exec, "-n") } app.EventHandlers = append(app.EventHandlers, *event) event = new(types.EventHandler) event.Name = "post-stop" for index := range runSpec.Hooks.Poststop { event.Exec = append(event.Exec, runSpec.Hooks.Poststop[index].Path) event.Exec = append(event.Exec, runSpec.Hooks.Poststop[index].Args...) event.Exec = append(event.Exec, runSpec.Hooks.Poststop[index].Env...) } if len(event.Exec) == 0 { event.Exec = append(event.Exec, "/bin/echo") event.Exec = append(event.Exec, "-n") } app.EventHandlers = append(app.EventHandlers, *event) // 5.5 "workingDirectory" app.WorkingDirectory = spec.Process.Cwd // 5.6 "environment" env := new(types.EnvironmentVariable) for index := range spec.Process.Env { s := strings.Split(spec.Process.Env[index], "=") env.Name = s[0] env.Value = s[1] app.Environment = append(app.Environment, *env) } // 5.7 "mountPoints" for index := range spec.Mounts { mount := new(types.MountPoint) mount.Name = types.ACName(spec.Mounts[index].Name) mount.Path = spec.Mounts[index].Path mount.ReadOnly = false app.MountPoints = append(app.MountPoints, *mount) } // 5.8 "ports" // 5.9 "isolators" if runSpec.Linux.Resources != nil { if runSpec.Linux.Resources.CPU.Quota != 0 { cpuLimt := new(ResourceCPU) cpuLimt.Limit = fmt.Sprintf("%dm", runSpec.Linux.Resources.CPU.Quota) isolator := new(types.Isolator) isolator.Name = types.ACIdentifier("resource/cpu") bytes, _ := json.Marshal(cpuLimt) valueRaw := json.RawMessage(bytes) isolator.ValueRaw = &valueRaw app.Isolators = append(app.Isolators, *isolator) } if runSpec.Linux.Resources.Memory.Limit != 0 { memLimt := new(ResourceMem) memLimt.Limit = fmt.Sprintf("%dG", runSpec.Linux.Resources.Memory.Limit/(1024*1024*1024)) isolator := new(types.Isolator) isolator.Name = types.ACIdentifier("resource/memory") bytes, _ := json.Marshal(memLimt) valueRaw := json.RawMessage(bytes) isolator.ValueRaw = &valueRaw app.Isolators = append(app.Isolators, *isolator) } } if len(spec.Linux.Capabilities) != 0 { isolatorCapSet := new(IsolatorCapSet) isolatorCapSet.Sets = append(isolatorCapSet.Sets, spec.Linux.Capabilities...) isolator := new(types.Isolator) isolator.Name = types.ACIdentifier(types.LinuxCapabilitiesRetainSetName) bytes, _ := json.Marshal(isolatorCapSet) valueRaw := json.RawMessage(bytes) isolator.ValueRaw = &valueRaw app.Isolators = append(app.Isolators, *isolator) } // 6. "annotations" // 7. "dependencies" // 8. "pathWhitelist" m.App = app return m }
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 patchManifest(im *schema.ImageManifest) error { if patchName != "" { name, err := types.NewACIdentifier(patchName) if err != nil { return err } im.Name = *name } var app *types.App = im.App if patchExec != "" { if app == nil { // if the original manifest was missing an app and // patchExec is set let's assume the user is trying to // inject one... im.App = &types.App{} app = im.App } app.Exec = strings.Split(patchExec, " ") } if patchUser != "" || patchGroup != "" || patchSupplementaryGIDs != "" || patchCaps != "" || patchRevokeCaps != "" || patchMounts != "" || patchPorts != "" || patchIsolators != "" { // ...but if we still don't have an app and the user is trying // to patch one of its other parameters, it's an error if app == nil { return fmt.Errorf("no app in the supplied manifest and no exec command provided") } } if patchUser != "" { app.User = patchUser } if patchGroup != "" { app.Group = patchGroup } if patchSupplementaryGIDs != "" { app.SupplementaryGIDs = []int{} gids := strings.Split(patchSupplementaryGIDs, ",") for _, g := range gids { gid, err := strconv.Atoi(g) if err != nil { return fmt.Errorf("invalid supplementary group %q: %v", g, err) } app.SupplementaryGIDs = append(app.SupplementaryGIDs, gid) } } if patchCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRetainSetName) if isolator != nil { return fmt.Errorf("isolator already exists (os/linux/capabilities-retain-set)") } // Instantiate a Isolator with the content specified by the --capability // parameter. caps, err := types.NewLinuxCapabilitiesRetainSet(strings.Split(patchCaps, ",")...) if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchCaps, err) } app.Isolators = append(app.Isolators, caps.AsIsolator()) } if patchRevokeCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRevokeSetName) if isolator != nil { return fmt.Errorf("isolator already exists (os/linux/capabilities-remove-set)") } // Instantiate a Isolator with the content specified by the --revoke-capability // parameter. caps, err := types.NewLinuxCapabilitiesRevokeSet(strings.Split(patchRevokeCaps, ",")...) if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchRevokeCaps, err) } app.Isolators = append(app.Isolators, caps.AsIsolator()) } if patchMounts != "" { mounts := strings.Split(patchMounts, ":") for _, m := range mounts { mountPoint, err := types.MountPointFromString(m) if err != nil { return fmt.Errorf("cannot parse mount point %q: %v", m, err) } app.MountPoints = append(app.MountPoints, *mountPoint) } } if patchPorts != "" { ports := strings.Split(patchPorts, ":") for _, p := range ports { port, err := types.PortFromString(p) if err != nil { return fmt.Errorf("cannot parse port %q: %v", p, err) } app.Ports = append(app.Ports, *port) } } if patchIsolators != "" { isolators := strings.Split(patchIsolators, ":") for _, is := range isolators { name, isolatorStr, err := isolatorStrFromString(is) if err != nil { return fmt.Errorf("cannot parse isolator %q: %v", is, err) } if _, ok := types.ResourceIsolatorNames[name]; !ok { return fmt.Errorf("isolator %s is not supported for patching", name) } isolator := &types.Isolator{} if err := isolator.UnmarshalJSON([]byte(isolatorStr)); err != nil { return fmt.Errorf("cannot unmarshal isolator %v: %v", isolatorStr, err) } app.Isolators = append(app.Isolators, *isolator) } } return nil }
func patchManifest(im *schema.ImageManifest) error { if patchName != "" { name, err := types.NewACIdentifier(patchName) if err != nil { return err } im.Name = *name } var app *types.App = im.App if patchExec != "" { if app == nil { // if the original manifest was missing an app and // patchExec is set let's assume the user is trying to // inject one... im.App = &types.App{} app = im.App } app.Exec = strings.Split(patchExec, " ") } if patchUser != "" || patchGroup != "" || patchSupplementaryGIDs != "" || patchCaps != "" || patchMounts != "" || patchPorts != "" || patchIsolators != "" { // ...but if we still don't have an app and the user is trying // to patch one of its other parameters, it's an error if app == nil { return fmt.Errorf("no app in the supplied manifest and no exec command provided") } } if patchUser != "" { app.User = patchUser } if patchGroup != "" { app.Group = patchGroup } if patchSupplementaryGIDs != "" { app.SupplementaryGIDs = []int{} gids := strings.Split(patchSupplementaryGIDs, ",") for _, g := range gids { gid, err := strconv.Atoi(g) if err != nil { return fmt.Errorf("invalid supplementary group %q: %v", g, err) } app.SupplementaryGIDs = append(app.SupplementaryGIDs, gid) } } if patchCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRetainSetName) if isolator != nil { return fmt.Errorf("isolator already exists") } // Instantiate a Isolator with the content specified by the --capability // parameter. // TODO: Instead of creating a JSON and then unmarshalling it, the isolator // should be instantiated directory. But it requires a constructor, see: // https://github.com/appc/spec/issues/268 capsList := strings.Split(patchCaps, ",") caps := fmt.Sprintf(`"set": ["%s"]`, strings.Join(capsList, `", "`)) isolatorStr := getIsolatorStr(types.LinuxCapabilitiesRetainSetName, caps) isolator = &types.Isolator{} err := isolator.UnmarshalJSON([]byte(isolatorStr)) if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchCaps, err) } app.Isolators = append(app.Isolators, *isolator) } if patchMounts != "" { mounts := strings.Split(patchMounts, ":") for _, m := range mounts { mountPoint, err := types.MountPointFromString(m) if err != nil { return fmt.Errorf("cannot parse mount point %q: %v", m, err) } app.MountPoints = append(app.MountPoints, *mountPoint) } } if patchPorts != "" { ports := strings.Split(patchPorts, ":") for _, p := range ports { port, err := types.PortFromString(p) if err != nil { return fmt.Errorf("cannot parse port %q: %v", p, err) } app.Ports = append(app.Ports, *port) } } if patchIsolators != "" { isolators := strings.Split(patchIsolators, ":") for _, is := range isolators { name, isolatorStr, err := isolatorStrFromString(is) if err != nil { return fmt.Errorf("cannot parse isolator %q: %v", is, err) } if _, ok := types.ResourceIsolatorNames[name]; !ok { return fmt.Errorf("isolator %s is not supported for patching", name) } isolator := &types.Isolator{} if err := isolator.UnmarshalJSON([]byte(isolatorStr)); err != nil { return fmt.Errorf("cannot unmarshal isolator %v: %v", isolatorStr, err) } app.Isolators = append(app.Isolators, *isolator) } } return nil }
func patchManifest(im *schema.ImageManifest) error { if patchName != "" { name, err := types.NewACIdentifier(patchName) if err != nil { return err } im.Name = *name } var app *types.App = im.App if patchExec != "" { if app == nil { // if the original manifest was missing an app and // patchExec is set let's assume the user is trying to // inject one... im.App = &types.App{} app = im.App } app.Exec = strings.Split(patchExec, " ") } if patchUser != "" || patchGroup != "" || patchSupplementaryGIDs != "" || patchCaps != "" || patchRevokeCaps != "" || patchMounts != "" || patchPorts != "" || patchIsolators != "" { // ...but if we still don't have an app and the user is trying // to patch one of its other parameters, it's an error if app == nil { return fmt.Errorf("no app in the supplied manifest and no exec command provided") } } if patchUser != "" { app.User = patchUser } if patchGroup != "" { app.Group = patchGroup } if patchSupplementaryGIDs != "" { app.SupplementaryGIDs = []int{} gids := strings.Split(patchSupplementaryGIDs, ",") for _, g := range gids { gid, err := strconv.Atoi(g) if err != nil { return fmt.Errorf("invalid supplementary group %q: %v", g, err) } app.SupplementaryGIDs = append(app.SupplementaryGIDs, gid) } } if patchCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRetainSetName) if isolator != nil { return fmt.Errorf("isolator already exists (os/linux/capabilities-retain-set)") } // Instantiate a Isolator with the content specified by the --capability // parameter. caps, err := types.NewLinuxCapabilitiesRetainSet(strings.Split(patchCaps, ",")...) if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchCaps, err) } isolator, err = caps.AsIsolator() if err != nil { return err } app.Isolators = append(app.Isolators, *isolator) } if patchRevokeCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRevokeSetName) if isolator != nil { return fmt.Errorf("isolator already exists (os/linux/capabilities-remove-set)") } // Instantiate a Isolator with the content specified by the --revoke-capability // parameter. caps, err := types.NewLinuxCapabilitiesRevokeSet(strings.Split(patchRevokeCaps, ",")...) if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchRevokeCaps, err) } isolator, err = caps.AsIsolator() if err != nil { return err } app.Isolators = append(app.Isolators, *isolator) } if patchMounts != "" { mounts := strings.Split(patchMounts, ":") for _, m := range mounts { mountPoint, err := types.MountPointFromString(m) if err != nil { return fmt.Errorf("cannot parse mount point %q: %v", m, err) } app.MountPoints = append(app.MountPoints, *mountPoint) } } if patchPorts != "" { ports := strings.Split(patchPorts, ":") for _, p := range ports { port, err := types.PortFromString(p) if err != nil { return fmt.Errorf("cannot parse port %q: %v", p, err) } app.Ports = append(app.Ports, *port) } } // Parse seccomp args and override existing seccomp isolators if patchSeccompMode != "" { seccompIsolator, err := parseSeccompArgs(patchSeccompMode, patchSeccompSet) if err != nil { return err } seccompReps := []types.ACIdentifier{types.LinuxSeccompRemoveSetName, types.LinuxSeccompRetainSetName} app.Isolators.ReplaceIsolatorsByName(*seccompIsolator, seccompReps) } else if patchSeccompSet != "" { return fmt.Errorf("--seccomp-set specified without --seccomp-mode") } if patchIsolators != "" { isolators := strings.Split(patchIsolators, ":") for _, is := range isolators { name, isolatorStr, err := isolatorStrFromString(is) if err != nil { return fmt.Errorf("cannot parse isolator %q: %v", is, err) } _, ok := types.ResourceIsolatorNames[name] switch name { case types.LinuxNoNewPrivilegesName: ok = true kv := strings.Split(is, ",") if len(kv) != 2 { return fmt.Errorf("isolator %s: invalid format", name) } isolatorStr = fmt.Sprintf(`{ "name": "%s", "value": %s }`, name, kv[1]) case types.LinuxSeccompRemoveSetName, types.LinuxSeccompRetainSetName: ok = false } if !ok { return fmt.Errorf("isolator %s is not supported for patching", name) } isolator := &types.Isolator{} if err := isolator.UnmarshalJSON([]byte(isolatorStr)); err != nil { return fmt.Errorf("cannot unmarshal isolator %v: %v", isolatorStr, err) } app.Isolators = append(app.Isolators, *isolator) } } return nil }
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 }
// 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 }
// 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 (aci *Aci) prepareStage1aci() (string, error) { ImportInternalBuilderIfNeeded(aci.manifest) if len(aci.manifest.Builder.Dependencies) == 0 { return "", nil } logs.WithFields(aci.fields).Debug("Preparing stage1") if err := os.MkdirAll(aci.target+pathStage1+common.PathRootfs, 0777); err != nil { return "", errs.WithEF(err, aci.fields.WithField("path", aci.target+pathBuilder), "Failed to create stage1 aci path") } Home.Rkt.Fetch(aci.manifest.Builder.Image.String()) manifestStr, err := Home.Rkt.CatManifest(aci.manifest.Builder.Image.String()) if err != nil { return "", errs.WithEF(err, aci.fields, "Failed to read stage1 image manifest") } manifest := schema.ImageManifest{} if err := json.Unmarshal([]byte(manifestStr), &manifest); err != nil { return "", errs.WithEF(err, aci.fields.WithField("content", manifestStr), "Failed to unmarshal stage1 manifest received from rkt") } manifest.Dependencies = types.Dependencies{} dep, err := common.ToAppcDependencies(aci.manifest.Builder.Dependencies) if err != nil { return "", errs.WithEF(err, aci.fields, "Invalid dependency on stage1 for rkt") } manifest.Dependencies = append(manifest.Dependencies, dep...) stage1Image, err := common.ToAppcDependencies([]common.ACFullname{aci.manifest.Builder.Image}) if err != nil { return "", errs.WithEF(err, aci.fields, "Invalid image on stage1 for rkt") } manifest.Dependencies = append(manifest.Dependencies, stage1Image...) name, err := types.NewACIdentifier(prefixBuilderStage1 + aci.manifest.NameAndVersion.Name()) if err != nil { return "", errs.WithEF(err, aci.fields.WithField("name", prefixBuilderStage1+aci.manifest.NameAndVersion.Name()), "aci name is not a valid identifier for rkt") } manifest.Name = *name content, err := json.MarshalIndent(&manifest, "", " ") if err != nil { return "", errs.WithEF(err, aci.fields, "Failed to marshal builder's stage1 manifest") } if err := ioutil.WriteFile(aci.target+pathStage1+common.PathManifest, content, 0644); err != nil { return "", errs.WithEF(err, aci.fields.WithField("path", aci.target+pathStage1+common.PathManifest), "Failed to write builder's stage1 manifest to file") } if err := aci.tarAci(aci.target + pathStage1); err != nil { return "", err } logs.WithF(aci.fields.WithField("path", aci.target+pathStage1+pathImageAci)).Info("Importing builder's stage1") hash, err := Home.Rkt.FetchInsecure(aci.target + pathStage1 + pathImageAci) if err != nil { return "", errs.WithEF(err, aci.fields, "fetch of builder's stage1 aci failed") } return hash, 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 }