func checkMinimalContainer(t *testing.T, acipath string, expectedManifest schema.ImageManifest) { // Check that there are no files in the rootfs files, err := ioutil.ReadDir(path.Join(acipath, aci.RootfsDir)) if err != nil { t.Errorf("%v", err) } if len(files) != 0 { t.Errorf("rootfs in aci contains files, should be empty") } // Check that the manifest is no bigger than it needs to be manblob, err := ioutil.ReadFile(path.Join(acipath, aci.ManifestFile)) if err != nil { t.Errorf("%v", err) } var man schema.ImageManifest err = man.UnmarshalJSON(manblob) if err != nil { t.Errorf("invalid manifest schema: %v", err) } if str := pretty.Compare(man, expectedManifest); str != "" { t.Errorf("unexpected manifest:\n%s", str) } }
// 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) } 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 }
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 }
// 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 }
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) } }
// ReplaceManifest will replace the manifest in the expanded ACI stored at // a.CurrentACIPath with the new manifest stored at manifestPath func (a *ACBuild) ReplaceManifest(manifestPath string) (err error) { if err = a.lock(); err != nil { return err } defer func() { if err1 := a.unlock(); err == nil { err = err1 } }() finfo, err := os.Stat(manifestPath) switch { case os.IsNotExist(err): return fmt.Errorf("no such file or directory: %s", manifestPath) case err != nil: return err case finfo.IsDir(): return fmt.Errorf("%s is a directory", manifestPath) default: break } manblob, err := ioutil.ReadFile(manifestPath) if err != nil { return err } // Marshal and Unmarshal the manifest to assert that it's valid and to // strip any whitespace var man schema.ImageManifest err = man.UnmarshalJSON(manblob) if err != nil { return err } manblob, err = man.MarshalJSON() if err != nil { return err } return ioutil.WriteFile(path.Join(a.CurrentACIPath, aci.ManifestFile), manblob, 0755) }
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 TestBeginLocalACI(t *testing.T) { wim := schema.ImageManifest{ ACKind: schema.ImageManifestKind, ACVersion: schema.AppContainerVersion, Name: *types.MustACIdentifier("acbuild-begin-test"), Labels: types.Labels{ types.Label{ Name: *types.MustACIdentifier("version"), Value: "9001", }, }, App: &types.App{ Exec: types.Exec{"/bin/nethack4", "-D", "wizard"}, User: "******", Group: "0", Environment: types.Environment{ types.EnvironmentVariable{ Name: "FOO", Value: "BAR", }, }, MountPoints: []types.MountPoint{ types.MountPoint{ Name: *types.MustACName("nethack4-data"), Path: "/root/nethack4-data", ReadOnly: true, }, }, Ports: []types.Port{ types.Port{ Name: *types.MustACName("gopher"), Protocol: "tcp", Port: 70, Count: 1, }, }, }, Annotations: types.Annotations{ types.Annotation{ Name: *types.MustACIdentifier("author"), Value: "the acbuild devs", }, }, Dependencies: types.Dependencies{ types.Dependency{ ImageName: *types.MustACIdentifier("quay.io/gnu/hurd"), }, }, } manblob, err := wim.MarshalJSON() if err != nil { panic(err) } tmpexpandedaci := mustTempDir() defer os.RemoveAll(tmpexpandedaci) err = ioutil.WriteFile(path.Join(tmpexpandedaci, aci.ManifestFile), manblob, 0644) if err != nil { panic(err) } err = os.Mkdir(path.Join(tmpexpandedaci, aci.RootfsDir), 0755) if err != nil { panic(err) } tmpaci, err := ioutil.TempFile("", "acbuild-test") if err != nil { panic(err) } defer os.RemoveAll(tmpaci.Name()) aw := aci.NewImageWriter(wim, tar.NewWriter(tmpaci)) err = filepath.Walk(tmpexpandedaci, aci.BuildWalker(tmpexpandedaci, aw, nil)) aw.Close() if err != nil { panic(err) } tmpaci.Close() tmpdir := mustTempDir() defer cleanUpTest(tmpdir) err = runAcbuild(tmpdir, "begin", tmpaci.Name()) if err != nil { t.Fatalf("%v", err) } checkMinimalContainer(t, path.Join(tmpdir, ".acbuild", "currentaci"), wim) }