// 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 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) } for _, f := range files { if !strings.HasPrefix(f, "rootfs") { return fmt.Errorf("unrecognized file path in layout: %q", f) } } return nil }
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 }
// 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, 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 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 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 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 LinuxSpec err = json.Unmarshal(config, &spec) if err != nil { if debugEnabled { log.Printf("Unmarshal config.json failed: %v", err) } return nil } var runSpec 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 = "ImageManifest" // 2. Assemble "acVersion" field m.ACVersion = schema.AppContainerVersion // 3. Assemble "name" field m.Name = "oci" // 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 // 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...) } 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...) } 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 = true 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 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 }
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 globalFlags.Debug { stderr("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)) 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 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 } reader, err := aci.NewCompressedTarReader(layer) if err == nil { // write files in rootfs/ if err := tarball.Walk(*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.NewACName(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 } if patchCaps != "" { app := im.App if app == nil { return fmt.Errorf("no app in the manifest") } 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 err } app.Isolators = append(app.Isolators, *isolator) } if patchMounts != "" { app := im.App if app == nil { return fmt.Errorf("no app in the manifest") } mounts := strings.Split(patchMounts, ":") for _, m := range mounts { mountPoint, err := types.MountPointFromString(m) if err != nil { return fmt.Errorf("cannot parse mount point %q", m) } app.MountPoints = append(app.MountPoints, *mountPoint) } } return 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 }