// findImage will recognize a ACI hash and use that, import a local file, use // discovery or download an ACI directly. func (f *finder) findImage(img string, asc string, discover bool) (*types.Hash, error) { // check if it is a valid hash, if so let it pass through h, err := types.NewHash(img) if err == nil { fullKey, err := f.s.ResolveKey(img) if err != nil { return nil, fmt.Errorf("could not resolve key: %v", err) } h, err = types.NewHash(fullKey) if err != nil { // should never happen panic(err) } return h, nil } // try fetching the image, potentially remotely ft := &fetcher{ imageActionData: f.imageActionData, local: f.local, withDeps: f.withDeps, } key, err := ft.fetchImage(img, asc, discover) if err != nil { return nil, err } h, err = types.NewHash(key) if err != nil { // should never happen panic(err) } return h, nil }
// getAppImageID returns the image id to enter // If one was supplied in the flags then it's simply returned // If the PM contains a single image, that image's id is returned // If the PM has multiple images, the ids and names are printed and an error is returned func getAppImageID(p *pod) (*types.Hash, error) { if flagAppImageID != "" { return types.NewHash(flagAppImageID) } // figure out the image id, or show a list if multiple are present b, err := ioutil.ReadFile(common.PodManifestPath(p.path())) if err != nil { return nil, fmt.Errorf("error reading pod manifest: %v", err) } m := schema.PodManifest{} if err = m.UnmarshalJSON(b); err != nil { return nil, fmt.Errorf("unable to load manifest: %v", err) } switch len(m.Apps) { case 0: return nil, fmt.Errorf("pod contains zero apps") case 1: return &m.Apps[0].Image.ID, nil default: } stderr("Pod contains multiple apps:") for _, ra := range m.Apps { stderr("\t%s: %s", types.ShortHash(ra.Image.ID.String()), ra.Name.String()) } return nil, fmt.Errorf("specify app using \"rkt enter --imageid ...\"") }
func (f *Finder) getHashFromStore(img string) (*types.Hash, error) { h, err := types.NewHash(img) if err != nil { return nil, fmt.Errorf("%q is not a valid hash: %v", img, err) } fullKey, err := f.S.ResolveKey(img) if err != nil { return nil, fmt.Errorf("could not resolve image ID %q: %v", img, err) } h, err = types.NewHash(fullKey) if err != nil { // should never happen panic(fmt.Errorf("got an invalid hash from the store, looks like it is corrupted: %v", err)) } return h, nil }
func runRmImage(cmd *cobra.Command, args []string) (exit int) { if len(args) < 1 { stderr("rkt: Must provide at least one image key") return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("rkt: cannot open store: %v", err) return 1 } //TODO(sgotti) Which return code to use when the removal fails only for some images? done := 0 errors := 0 staleErrors := 0 for _, pkey := range args { errors++ h, err := types.NewHash(pkey) if err != nil { stderr("rkt: wrong imageID %q: %v", pkey, err) continue } key, err := s.ResolveKey(h.String()) if err != nil { stderr("rkt: imageID %q not valid: %v", pkey, err) continue } if key == "" { stderr("rkt: imageID %q doesn't exists", pkey) continue } if err = s.RemoveACI(key); err != nil { if serr, ok := err.(*store.StoreRemovalError); ok { staleErrors++ stderr("rkt: some files cannot be removed for imageID %q: %v", pkey, serr) } else { stderr("rkt: error removing aci for imageID %q: %v", pkey, err) continue } } stdout("rkt: successfully removed aci for imageID: %q", pkey) errors-- done++ } if done > 0 { stderr("rkt: %d image(s) successfully removed", done) } if errors > 0 { stderr("rkt: %d image(s) cannot be removed", errors) } if staleErrors > 0 { stderr("rkt: %d image(s) removed but left some stale files", staleErrors) } return 0 }
func runImageCatManifest(cmd *cobra.Command, args []string) (exit int) { if len(args) != 1 { cmd.Usage() return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("image cat-manifest: cannot open store: %v", err) return 1 } var key string if _, err := types.NewHash(args[0]); err == nil { key, err = s.ResolveKey(args[0]) if err != nil { stderr("image cat-manifest: cannot resolve key: %v", err) return 1 } } else { app, err := discovery.NewAppFromString(args[0]) if err != nil { stderr("image cat-manifest: cannot parse the image name: %v", err) return 1 } labels, err := types.LabelsFromMap(app.Labels) if err != nil { stderr("image cat-manifest: invalid labels in the name: %v", err) return 1 } key, err = s.GetACI(app.Name, labels) if err != nil { stderr("image cat-manifest: cannot find image: %v", err) return 1 } } manifest, err := s.GetImageManifest(key) if err != nil { stderr("image cat-manifest: cannot get image manifest: %v", err) return 1 } var b []byte if flagPrettyPrint { b, err = json.MarshalIndent(manifest, "", "\t") } else { b, err = json.Marshal(manifest) } if err != nil { stderr("image cat-manifest: cannot read the image manifest: %v", err) return 1 } stdout(string(b)) return 0 }
// getStage1Hash returns the hash of the stage1 image used in this pod func (p *pod) getStage1Hash() (*types.Hash, error) { s1IDb, err := p.readFile(common.Stage1IDFilename) if err != nil { return nil, err } s1img, err := types.NewHash(string(s1IDb)) if err != nil { return nil, err } return s1img, nil }
func rmImages(s *store.Store, images []string) error { done := 0 errors := 0 staleErrors := 0 for _, pkey := range images { errors++ h, err := types.NewHash(pkey) if err != nil { stderr("rkt: wrong image ID %q: %v", pkey, err) continue } key, err := s.ResolveKey(h.String()) if err != nil { stderr("rkt: image ID %q not valid: %v", pkey, err) continue } if key == "" { stderr("rkt: image ID %q doesn't exist", pkey) continue } if err = s.RemoveACI(key); err != nil { if serr, ok := err.(*store.StoreRemovalError); ok { staleErrors++ stderr("rkt: some files cannot be removed for image ID %q: %v", pkey, serr) } else { stderr("rkt: error removing aci for image ID %q: %v", pkey, err) continue } } stdout("rkt: successfully removed aci for image ID: %q", pkey) errors-- done++ } if done > 0 { stderr("rkt: %d image(s) successfully removed", done) } // If anything didn't complete, return exit status of 1 if (errors + staleErrors) > 0 { if staleErrors > 0 { stderr("rkt: %d image(s) removed but left some stale files", staleErrors) } if errors > 0 { stderr("rkt: %d image(s) cannot be removed", errors) } return fmt.Errorf("error(s) found while removing images") } return nil }
func getStoreKeyFromAppOrHash(s *store.Store, input string) (string, error) { var key string if _, err := types.NewHash(input); err == nil { key, err = s.ResolveKey(input) if err != nil { return "", fmt.Errorf("cannot resolve key: %v", err) } } else { key, err = getStoreKeyFromApp(s, input) if err != nil { return "", fmt.Errorf("cannot find image: %v", err) } } return key, nil }
func guessImageType(image string) apps.AppImageType { if _, err := types.NewHash(image); err == nil { return apps.AppImageHash } if u, err := url.Parse(image); err == nil && u.Scheme != "" { return apps.AppImageURL } if filepath.IsAbs(image) { return apps.AppImagePath } // Well, at this point is basically heuristics time. The image // parameter can be either a relative path or an image name. // First, let's check if there is a colon in the image // parameter. Colon often serves as a paths separator (like in // the PATH environment variable), so if it exists, then it is // highly unlikely that the image parameter is a path. Colon // in this context is often used for specifying a version of // an image, like in "example.com/reduce-worker:1.0.0". if strings.ContainsRune(image, ':') { return apps.AppImageName } // Second, let's check if there is a dot followed by a slash // (./) - if so, it is likely that the image parameter is path // like ./aci-in-this-dir or ../aci-in-parent-dir if strings.Contains(image, "./") { return apps.AppImagePath } // Third, let's check if the image parameter has an .aci // extension. If so, likely a path like "stage1-coreos.aci". if filepath.Ext(image) == schema.ACIExtension { return apps.AppImagePath } // At this point, if the image parameter is something like // "coreos.com/rkt/stage1-coreos" and you have a directory // tree "coreos.com/rkt" in the current working directory and // you meant the image parameter to point to the file // "stage1-coreos" in this directory tree, then you better be // off prepending the parameter with "./", because I'm gonna // treat this as an image name otherwise. return apps.AppImageName }
// Write renders the ACI with the provided key in the treestore // Write, to avoid having a rendered ACI with old stale files, requires that // the destination directory doesn't exist (usually Remove should be called // before Write) func (ts *TreeStore) Write(key string, s *Store) error { treepath := filepath.Join(ts.path, key) fi, _ := os.Stat(treepath) if fi != nil { return fmt.Errorf("treestore: path %s already exists", treepath) } imageID, err := types.NewHash(key) if err != nil { return fmt.Errorf("treestore: cannot convert key to imageID: %v", err) } if err := os.MkdirAll(treepath, 0755); err != nil { return fmt.Errorf("treestore: cannot create treestore directory %s: %v", treepath, err) } err = aci.RenderACIWithImageID(*imageID, treepath, s) if err != nil { return fmt.Errorf("treestore: cannot render aci: %v", err) } hash, err := ts.Hash(key) if err != nil { return fmt.Errorf("treestore: cannot calculate tree hash: %v", err) } err = ioutil.WriteFile(filepath.Join(treepath, hashfilename), []byte(hash), 0644) if err != nil { return fmt.Errorf("treestore: cannot write hash file: %v", err) } // before creating the "rendered" flag file we need to ensure that all data is fsynced dfd, err := syscall.Open(treepath, syscall.O_RDONLY, 0) if err != nil { return err } defer syscall.Close(dfd) if err := sys.Syncfs(dfd); err != nil { return fmt.Errorf("treestore: failed to sync data: %v", err) } // Create rendered file f, err := os.Create(filepath.Join(treepath, renderedfilename)) if err != nil { return fmt.Errorf("treestore: failed to write rendered file: %v", err) } f.Close() if err := syscall.Fsync(dfd); err != nil { return fmt.Errorf("treestore: failed to sync tree store directory: %v", err) } return nil }
// GetTreeStoreID calculates the treestore ID for the given image key. // The treeStoreID is computed as an hash of the flattened dependency tree // image keys. In this way the ID may change for the same key if the image's // dependencies change. func (s Store) GetTreeStoreID(key string) (string, error) { hash, err := types.NewHash(key) if err != nil { return "", err } images, err := acirenderer.CreateDepListFromImageID(*hash, s) if err != nil { return "", err } keys := []string{} for _, image := range images { keys = append(keys, image.Key) } imagesString := strings.Join(keys, ",") h := sha512.New() h.Write([]byte(imagesString)) return "deps-" + hashToKey(h), nil }
// FindImage tries to get a hash of a passed image, ideally from // store. Otherwise this might involve fetching it from remote with // the Fetcher. func (f *Finder) FindImage(img string, asc string, imgType apps.AppImageType) (*types.Hash, error) { if imgType == apps.AppImageGuess { imgType = guessImageType(img) } if imgType == apps.AppImageHash { return f.getHashFromStore(img) } // urls, names, paths have to be fetched, potentially remotely ft := (*Fetcher)(f) key, err := ft.FetchImage(img, asc, imgType) if err != nil { return nil, err } h, err := types.NewHash(key) if err != nil { // should never happen panic(fmt.Errorf("got an invalid hash from the store, looks like it is corrupted: %v", err)) } return h, nil }
func getKeyFromAppOrHash(s *store.Store, input string) (string, error) { var key string if _, err := types.NewHash(input); err == nil { key, err = s.ResolveKey(input) if err != nil { return "", fmt.Errorf("cannot resolve key: %v", err) } } else { app, err := discovery.NewAppFromString(input) if err != nil { return "", fmt.Errorf("cannot parse the image name: %v", err) } labels, err := types.LabelsFromMap(app.Labels) if err != nil { return "", fmt.Errorf("invalid labels in the name: %v", err) } key, err = s.GetACI(app.Name, labels) if err != nil { return "", fmt.Errorf("cannot find image: %v", err) } } return key, nil }
// Test running pod manifests that contains just one app. // TODO(yifan): Figure out a way to test port mapping on single host. func TestPodManifest(t *testing.T) { ctx := newRktRunCtx() defer ctx.cleanup() tmpdir, err := ioutil.TempDir("", "rkt-tests.") if err != nil { t.Fatalf("Cannot create temporary directory: %v", err) } defer os.RemoveAll(tmpdir) boolFalse, boolTrue := false, true tests := []struct { // [image name]:[image patches] images map[string][]string podManifest *schema.PodManifest shouldSuccess bool expectedResult string cgroup string }{ { // Simple read. map[string][]string{ "rkt-test-run-pod-manifest-read.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, }, }, }, }, }, true, "dir1", "", }, { // Simple read after write with volume mounted. map[string][]string{ "rkt-test-run-pod-manifest-vol-rw.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Simple read after write with read-only mount point, should fail. map[string][]string{ "rkt-test-run-pod-manifest-vol-ro.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", true}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. This should fail as the volume is // read-only in pod manifest, (which will override the mount point in both image/pod manifest). map[string][]string{ "rkt-test-run-pod-manifest-vol-rw-override.aci": {"--mounts=dir1,path=/dir1,readOnly=false"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. map[string][]string{ "rkt-test-run-pod-manifest-vol-rw-override.aci": {"--mounts=dir1,path=/dir1,readOnly=true"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir2/file"}, {"CONTENT", "host:bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir2", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:bar", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. map[string][]string{ "rkt-test-run-pod-manifest-vol-rw-no-app.aci": { "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:baz", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. // This should succeed even the mount point in image manifest is readOnly, // because it is overrided by the volume's readOnly. map[string][]string{ "rkt-test-run-pod-manifest-vol-ro-no-app.aci": { "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:zaz", "--mounts=dir1,path=/dir1,readOnly=true", }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolFalse}, }, }, true, "host:zaz", "", }, { // Simple read after write with read-only volume mounted, no apps in pod manifest. // This should fail as the volume is read-only. map[string][]string{ "rkt-test-run-pod-manifest-vol-ro-no-app.aci": { "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Print CPU quota, which should be overwritten by the pod manifest. map[string][]string{ "rkt-test-run-pod-manifest-cpu-isolator.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-cpuquota"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/cpu", ValueRaw: rawRequestLimit("100", "100"), }, }, }, }, }, }, true, `CPU Quota: 100`, "cpu", }, { // Print memory limit, which should be overwritten by the pod manifest. map[string][]string{ "rkt-test-run-pod-manifest-memory-isolator.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-memorylimit"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/memory", // 4MB. ValueRaw: rawRequestLimit("4194304", "4194304"), }, }, }, }, }, }, true, `Memory Limit: 4194304`, "memory", }, { // Multiple apps in the pod. The first app will read out the content // written by the second app. map[string][]string{ "rkt-test-run-pod-manifest-app1.aci": {"--name=rkt-inspect-readapp"}, "rkt-test-run-pod-manifest-app2.aci": {"--name=rkt-inspect-writeapp"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: "rkt-inspect-readapp", App: &types.App{ Exec: []string{"/inspect", "--pre-sleep=10", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, { Name: "rkt-inspect-writeapp", App: &types.App{ Exec: []string{"/inspect", "--write-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, }, Volumes: []types.Volume{ {"dir", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Pod manifest overwrites the image's capability. map[string][]string{ "rkt-test-run-pod-manifest-cap.aci": {"--capability=CAP_NET_ADMIN"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_ADMIN))}, }, }, }, }, }, true, fmt.Sprintf("%v=disabled", capability.CAP_NET_ADMIN.String()), "", }, { // Pod manifest overwrites the image's capability. map[string][]string{ "rkt-test-run-pod-manifest-cap.aci": {"--capability=CAP_NET_ADMIN"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_BIND_SERVICE))}, }, Isolators: []types.Isolator{ { Name: "os/linux/capabilities-retain-set", ValueRaw: rawValue(fmt.Sprintf(`{"set":["CAP_NET_BIND_SERVICE"]}`)), }, }, }, }, }, }, true, fmt.Sprintf("%v=enabled", capability.CAP_NET_BIND_SERVICE.String()), "", }, } for i, tt := range tests { if tt.cgroup != "" && !cgroup.IsIsolatorSupported(tt.cgroup) { t.Logf("Skip test #%v: cgroup %s not supported", i, tt.cgroup) continue } j := 0 for name, patches := range tt.images { imageFile := patchTestACI(name, patches...) hash := importImageAndFetchHash(t, ctx, imageFile) defer os.Remove(imageFile) imgName := types.MustACIdentifier(name) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } ra := &tt.podManifest.Apps[j] ra.Image.Name = imgName ra.Image.ID = *imgID j++ } tt.podManifest.ACKind = schema.PodManifestKind tt.podManifest.ACVersion = schema.AppContainerVersion manifestFile := generatePodManifestFile(t, tt.podManifest) defer os.Remove(manifestFile) // 1. Test 'rkt run'. runCmd := fmt.Sprintf("%s run --mds-register=false --pod-manifest=%s", ctx.cmd(), manifestFile) t.Logf("Running 'run' test #%v: %v", i, runCmd) child, err := gexpect.Spawn(runCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. cmds := strings.Fields(ctx.cmd()) prepareCmd := exec.Command(cmds[0], cmds[1:]...) prepareArg := fmt.Sprintf("--pod-manifest=%s", manifestFile) prepareCmd.Args = append(prepareCmd.Args, "--insecure-skip-verify", "prepare", prepareArg) output, err := prepareCmd.Output() if err != nil { t.Fatalf("Cannot read the output: %v", err) } podIDStr := strings.TrimSpace(string(output)) podID, err := types.NewUUID(podIDStr) if err != nil { t.Fatalf("%q is not a valid UUID: %v", podIDStr, err) } runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.cmd(), podID.String()) t.Logf("Running 'run' test #%v: %v", i, runPreparedCmd) child, err = gexpect.Spawn(runPreparedCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) } }
// Test an image with 1 dep. The parent image has a pathWhiteList. func TestPWLOnlyParent(t *testing.T) { dir, err := ioutil.TempDir("", tstprefix) if err != nil { t.Fatalf("error creating tempdir: %v", err) } defer os.RemoveAll(dir) ds := NewTestStore() imj := ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test01", "pathWhitelist" : [ "/a/file01.txt", "/a/file02.txt", "/b/link01.txt", "/c/", "/d/" ] } ` entries := []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file01.txt", Size: 5, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file02.txt", Size: 5, }, }, // This should not appear in rendered aci { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file03.txt", Size: 5, }, }, { header: &tar.Header{ Name: "rootfs/b/link01.txt", Linkname: "file01.txt", Typeflag: tar.TypeSymlink, }, }, // The file "rootfs/c/file01.txt" should not appear but a new file "rootfs/c/file02.txt" provided by the upper image should appear. // The directory should be left with its permissions { header: &tar.Header{ Name: "rootfs/c", Typeflag: tar.TypeDir, Mode: 0700, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/c/file01.txt", Size: 5, Mode: 0700, }, }, // The file "rootfs/d/file01.txt" should not appear but the directory should be left and also its permissions { header: &tar.Header{ Name: "rootfs/d", Typeflag: tar.TypeDir, Mode: 0700, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/d/file01.txt", Size: 5, Mode: 0700, }, }, // The file and the directory should not appear { contents: "hello", header: &tar.Header{ Name: "rootfs/e/file01.txt", Size: 5, Mode: 0700, }, }, } key1, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err := createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image1 := Image{Im: im, Key: key1, Level: 1} imj = ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test02" } ` k1, _ := types.NewHash(key1) imj, err = addDependencies(imj, types.Dependency{ ImageName: "example.com/test01", ImageID: k1}, ) entries = []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, { contents: "hellohello", header: &tar.Header{ Name: "rootfs/b/file01.txt", Size: 10, }, }, // New file { contents: "hello", header: &tar.Header{ Name: "rootfs/c/file02.txt", Size: 5, }, }, } expectedFiles := []*fileInfo{ &fileInfo{path: "manifest", typeflag: tar.TypeReg}, &fileInfo{path: "rootfs/a/file01.txt", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "rootfs/a/file02.txt", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "rootfs/b/link01.txt", typeflag: tar.TypeSymlink}, &fileInfo{path: "rootfs/b/file01.txt", typeflag: tar.TypeReg, size: 10}, &fileInfo{path: "rootfs/c", typeflag: tar.TypeDir, mode: 0700}, &fileInfo{path: "rootfs/c/file02.txt", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "rootfs/d", typeflag: tar.TypeDir, mode: 0700}, } key2, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err = createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image2 := Image{Im: im, Key: key2, Level: 0} images := Images{image2, image1} err = checkRenderACIFromList(images, expectedFiles, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } err = checkRenderACI("example.com/test02", expectedFiles, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } }
// Test an image with 1 dep. The upper image overrides a dir provided by a // parent with a non-dir file. func TestFileOvverideDir(t *testing.T) { dir, err := ioutil.TempDir("", tstprefix) if err != nil { t.Fatalf("error creating tempdir: %v", err) } defer os.RemoveAll(dir) ds := NewTestStore() imj := ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test01" } ` entries := []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, { header: &tar.Header{ Name: "rootfs/a/b", Typeflag: tar.TypeDir, Mode: 0700, }, }, { header: &tar.Header{ Name: "rootfs/a/b/c", Typeflag: tar.TypeDir, Mode: 0700, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/a/b/c/file01", Size: 5, }, }, } key1, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err := createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image1 := Image{Im: im, Key: key1, Level: 1} imj = ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test02" } ` k1, _ := types.NewHash(key1) imj, err = addDependencies(imj, types.Dependency{ ImageName: "example.com/test01", ImageID: k1}, ) entries = []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, { contents: "hellohello", header: &tar.Header{ Name: "rootfs/a/b", Size: 10, }, }, } expectedFiles := []*fileInfo{ &fileInfo{path: "manifest", typeflag: tar.TypeReg}, &fileInfo{path: "rootfs/a/b", typeflag: tar.TypeReg, size: 10}, } key2, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err = createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image2 := Image{Im: im, Key: key2, Level: 0} images := Images{image2, image1} err = checkRenderACIFromList(images, expectedFiles, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } err = checkRenderACI("example.com/test02", expectedFiles, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } }
// Test A (pwl) ---- B // \-- C -- D func Test3Deps(t *testing.T) { dir, err := ioutil.TempDir("", tstprefix) if err != nil { t.Fatalf("error creating tempdir: %v", err) } defer os.RemoveAll(dir) ds := NewTestStore() // B imj := ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test01" } ` entries := []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, // It should be overridden by the one provided by the upper image { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file01.txt", Size: 5, }, }, // It should be overridden by the one provided by the next dep { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file02.txt", Size: 5, }, }, // It should remain like this { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file03.txt", Size: 5, }, }, // It should not appear in rendered aci { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file04.txt", Size: 5, }, }, { header: &tar.Header{ Name: "rootfs/b/link01.txt", Linkname: "file01.txt", Typeflag: tar.TypeSymlink, }, }, } key1, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err := createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image1 := Image{Im: im, Key: key1, Level: 1} // D imj = ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test03" } ` entries = []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, // It should be overridden by the one provided by the upper image { contents: "hello", header: &tar.Header{ Name: "rootfs/a/file02.txt", Size: 5, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/b/file01.txt", Size: 5, }, }, // It should not appear in rendered aci { header: &tar.Header{ Name: "rootfs/d", Typeflag: tar.TypeDir, Mode: 0700, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/d/file01.txt", Size: 5, Mode: 0700, }, }, } key2, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err = createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image2 := Image{Im: im, Key: key2, Level: 2} // C imj = ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test02" } ` k2, _ := types.NewHash(key2) imj, err = addDependencies(imj, types.Dependency{ ImageName: "example.com/test03", ImageID: k2}, ) entries = []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, // It should be overridden by the one provided by the upper image { contents: "hellohello", header: &tar.Header{ Name: "rootfs/a/file01.txt", Size: 10, }, }, { contents: "hellohello", header: &tar.Header{ Name: "rootfs/a/file02.txt", Size: 10, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/b/file01.txt", Size: 5, }, }, } key3, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err = createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image3 := Image{Im: im, Key: key3, Level: 1} // A imj = ` { "acKind": "ImageManifest", "acVersion": "0.1.1", "name": "example.com/test04", "pathWhitelist" : [ "/a/file01.txt", "/a/file02.txt", "/a/file03.txt", "/b/link01.txt", "/b/file01.txt", "/b/file02.txt", "/c/file01.txt" ] } ` k1, _ := types.NewHash(key1) k3, _ := types.NewHash(key3) imj, err = addDependencies(imj, types.Dependency{ ImageName: "example.com/test01", ImageID: k1}, types.Dependency{ ImageName: "example.com/test02", ImageID: k3}, ) entries = []*testTarEntry{ { contents: imj, header: &tar.Header{ Name: "manifest", Size: int64(len(imj)), }, }, // Overridden { contents: "hellohellohello", header: &tar.Header{ Name: "rootfs/a/file01.txt", Size: 15, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/b/file02.txt", Size: 5, }, }, { contents: "hello", header: &tar.Header{ Name: "rootfs/c/file01.txt", Size: 5, }, }, } key4, err := newTestACI(entries, dir, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } im, err = createImageManifest(imj) if err != nil { t.Fatalf("unexpected error: %v", err) } image4 := Image{Im: im, Key: key4, Level: 0} expectedFiles := []*fileInfo{ &fileInfo{path: "manifest", typeflag: tar.TypeReg}, &fileInfo{path: "rootfs/a/file01.txt", typeflag: tar.TypeReg, size: 15}, &fileInfo{path: "rootfs/a/file02.txt", typeflag: tar.TypeReg, size: 10}, &fileInfo{path: "rootfs/a/file03.txt", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "rootfs/b/link01.txt", typeflag: tar.TypeSymlink}, &fileInfo{path: "rootfs/b/file01.txt", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "rootfs/b/file02.txt", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "rootfs/c/file01.txt", typeflag: tar.TypeReg, size: 5}, } images := Images{image4, image3, image2, image1} err = checkRenderACIFromList(images, expectedFiles, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } err = checkRenderACI("example.com/test04", expectedFiles, ds) if err != nil { t.Fatalf("unexpected error: %v", err) } }
func runRmImage(args []string) (exit int) { if len(args) < 1 { stderr("rkt: Must provide at least one image key") return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("rkt: cannot open store: %v\n", err) return 1 } referencedImgs, err := getReferencedImgs(s) if err != nil { stderr("rkt: cannot get referenced images: %v\n", err) return 1 } //TODO(sgotti) Which return code to use when the removal fails only for some images? done := 0 errors := 0 staleErrors := 0 for _, pkey := range args { errors++ h, err := types.NewHash(pkey) if err != nil { stderr("rkt: wrong imageID %q: %v\n", pkey, err) continue } key, err := s.ResolveKey(h.String()) if err != nil { stderr("rkt: imageID %q not valid: %v\n", pkey, err) continue } if key == "" { stderr("rkt: imageID %q doesn't exists\n", pkey) continue } err = s.RemoveACI(key) if err != nil { if serr, ok := err.(*store.StoreRemovalError); ok { staleErrors++ stderr("rkt: some files cannot be removed for imageID %q: %v\n", pkey, serr) } else { stderr("rkt: error removing aci for imageID %q: %v\n", pkey, err) } continue } stdout("rkt: successfully removed aci for imageID: %q\n", pkey) // Remove the treestore only if the image isn't referenced by // some containers // TODO(sgotti) there's a windows between getting refenced // images and this check where a new container could be // prepared/runned with this image. To avoid this a global lock // is needed. if _, ok := referencedImgs[key]; ok { stderr("rkt: imageID is referenced by some containers, cannot remove the tree store") continue } else { err = s.RemoveTreeStore(key) if err != nil { staleErrors++ stderr("rkt: error removing treestore for imageID %q: %v\n", pkey, err) continue } } errors-- done++ } if done > 0 { stdout("rkt: %d image(s) successfully removed\n", done) } if errors > 0 { stdout("rkt: %d image(s) cannot be removed\n", errors) } if staleErrors > 0 { stdout("rkt: %d image(s) removed but left some stale files\n", staleErrors) } return 0 }
// Test running pod manifests that contains just one app. // TODO(yifan): Figure out a way to test port mapping on single host. func TestPodManifest(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() tmpdir := createTempDirOrPanic("rkt-tests.") defer os.RemoveAll(tmpdir) boolFalse, boolTrue := false, true tests := []struct { // [image name]:[image patches] images []imagePatch podManifest *schema.PodManifest shouldSucceed bool expectedResult string cgroup string }{ { // Simple read. []imagePatch{ {"rkt-test-run-pod-manifest-read.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, }, }, }, }, }, true, "dir1", "", }, { // Simple read after write with volume mounted. []imagePatch{ {"rkt-test-run-pod-manifest-vol-rw.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Simple read after write with read-only mount point, should fail. []imagePatch{ {"rkt-test-run-pod-manifest-vol-ro.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", true}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. This should fail as the volume is // read-only in pod manifest, (which will override the mount point in both image/pod manifest). []imagePatch{ {"rkt-test-run-pod-manifest-vol-rw-override.aci", []string{"--mounts=dir1,path=/dir1,readOnly=false"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. []imagePatch{ {"rkt-test-run-pod-manifest-vol-rw-override.aci", []string{"--mounts=dir1,path=/dir1,readOnly=true"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir2/file"}, {"CONTENT", "host:bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir2", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:bar", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. []imagePatch{ { "rkt-test-run-pod-manifest-vol-rw-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:baz", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. // This should succeed even the mount point in image manifest is readOnly, // because it is overrided by the volume's readOnly. []imagePatch{ { "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:zaz", "--mounts=dir1,path=/dir1,readOnly=true", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolFalse}, }, }, true, "host:zaz", "", }, { // Simple read after write with read-only volume mounted, no apps in pod manifest. // This should fail as the volume is read-only. []imagePatch{ { "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Print CPU quota, which should be overwritten by the pod manifest. []imagePatch{ {"rkt-test-run-pod-manifest-cpu-isolator.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-cpuquota"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/cpu", ValueRaw: rawRequestLimit("100", "100"), }, }, }, }, }, }, true, `CPU Quota: 100`, "cpu", }, { // Print memory limit, which should be overwritten by the pod manifest. []imagePatch{ {"rkt-test-run-pod-manifest-memory-isolator.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-memorylimit"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/memory", // 4MB. ValueRaw: rawRequestLimit("4194304", "4194304"), }, }, }, }, }, }, true, `Memory Limit: 4194304`, "memory", }, { // Multiple apps (with same images) in the pod. The first app will read out the content // written by the second app. []imagePatch{ {"rkt-test-run-pod-manifest-app.aci", []string{"--name=aci1"}}, {"rkt-test-run-pod-manifest-app.aci", []string{"--name=aci2"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: "rkt-inspect-readapp", App: &types.App{ Exec: []string{"/inspect", "--pre-sleep=10", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, { Name: "rkt-inspect-writeapp", App: &types.App{ Exec: []string{"/inspect", "--write-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, }, Volumes: []types.Volume{ {"dir", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Pod manifest overwrites the image's capability. []imagePatch{ {"rkt-test-run-pod-manifest-cap.aci", []string{"--capability=CAP_NET_ADMIN"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_ADMIN))}, }, }, }, }, }, true, fmt.Sprintf("%v=disabled", capability.CAP_NET_ADMIN.String()), "", }, { // Pod manifest overwrites the image's capability. []imagePatch{ {"rkt-test-run-pod-manifest-cap.aci", []string{"--capability=CAP_NET_BIND_SERVICE"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_ADMIN))}, }, Isolators: []types.Isolator{ { Name: "os/linux/capabilities-retain-set", ValueRaw: rawValue(fmt.Sprintf(`{"set":["CAP_NET_ADMIN"]}`)), }, }, }, }, }, }, true, fmt.Sprintf("%v=enabled", capability.CAP_NET_ADMIN.String()), "", }, } for i, tt := range tests { if tt.cgroup != "" && !cgroup.IsIsolatorSupported(tt.cgroup) { t.Logf("Skip test #%v: cgroup %s not supported", i, tt.cgroup) continue } var hashesToRemove []string for j, v := range tt.images { hash := patchImportAndFetchHash(v.name, v.patches, t, ctx) hashesToRemove = append(hashesToRemove, hash) imgName := types.MustACIdentifier(v.name) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } ra := &tt.podManifest.Apps[j] ra.Image.Name = imgName ra.Image.ID = *imgID } tt.podManifest.ACKind = schema.PodManifestKind tt.podManifest.ACVersion = schema.AppContainerVersion manifestFile := generatePodManifestFile(t, tt.podManifest) defer os.Remove(manifestFile) // 1. Test 'rkt run'. runCmd := fmt.Sprintf("%s run --mds-register=false --pod-manifest=%s", ctx.Cmd(), manifestFile) t.Logf("Running 'run' test #%v", i) child := spawnOrFail(t, runCmd) if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSucceed { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. rktCmd := fmt.Sprintf("%s --insecure-skip-verify prepare --pod-manifest=%s", ctx.Cmd(), manifestFile) uuid := runRktAndGetUUID(t, rktCmd) runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.Cmd(), uuid) t.Logf("Running 'run-prepared' test #%v", i) child = spawnOrFail(t, runPreparedCmd) if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSucceed { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // we run the garbage collector and remove the imported images to save // space runGC(t, ctx) for _, h := range hashesToRemove { removeFromCas(t, ctx, h) } } }
// Write renders the ACI with the provided key in the treestore. id references // that specific tree store rendered image. // Write, to avoid having a rendered ACI with old stale files, requires that // the destination directory doesn't exist (usually Remove should be called // before Write) func (ts *TreeStore) Write(id string, key string, s *Store) (string, error) { treepath := ts.GetPath(id) fi, _ := os.Stat(treepath) if fi != nil { return "", fmt.Errorf("treestore: path %s already exists", treepath) } imageID, err := types.NewHash(key) if err != nil { return "", fmt.Errorf("treestore: cannot convert key to imageID: %v", err) } if err := os.MkdirAll(treepath, 0755); err != nil { return "", fmt.Errorf("treestore: cannot create treestore directory %s: %v", treepath, err) } err = aci.RenderACIWithImageID(*imageID, treepath, s, uid.NewBlankUidRange()) if err != nil { return "", fmt.Errorf("treestore: cannot render aci: %v", err) } hash, err := ts.Hash(id) if err != nil { return "", fmt.Errorf("treestore: cannot calculate tree hash: %v", err) } err = ioutil.WriteFile(filepath.Join(treepath, hashfilename), []byte(hash), 0644) if err != nil { return "", fmt.Errorf("treestore: cannot write hash file: %v", err) } // before creating the "rendered" flag file we need to ensure that all data is fsynced dfd, err := syscall.Open(treepath, syscall.O_RDONLY, 0) if err != nil { return "", err } defer syscall.Close(dfd) if err := sys.Syncfs(dfd); err != nil { return "", fmt.Errorf("treestore: failed to sync data: %v", err) } // Create rendered file f, err := os.Create(filepath.Join(treepath, renderedfilename)) if err != nil { return "", fmt.Errorf("treestore: failed to write rendered file: %v", err) } f.Close() // Write the hash of the image that will use this tree store err = ioutil.WriteFile(filepath.Join(treepath, imagefilename), []byte(key), 0644) if err != nil { return "", fmt.Errorf("treestore: cannot write image file: %v", err) } if err := syscall.Fsync(dfd); err != nil { return "", fmt.Errorf("treestore: failed to sync tree store directory: %v", err) } treeSize, err := ts.Size(id) if err != nil { return "", err } if err := s.UpdateTreeStoreSize(key, treeSize); err != nil { return "", err } return string(hash), nil }
func rmImages(s *store.Store, images []string) error { done := 0 errors := 0 staleErrors := 0 imageMap := make(map[string]string) imageCounter := make(map[string]int) for _, pkey := range images { errors++ h, err := types.NewHash(pkey) if err != nil { var found bool keys, found, err := s.ResolveName(pkey) if len(keys) > 0 { errors += len(keys) - 1 } if err != nil { stderr("rkt: %v", err) continue } if !found { stderr("rkt: image name %q not found", pkey) continue } for _, key := range keys { imageMap[key] = pkey imageCounter[key]++ } } else { key, err := s.ResolveKey(h.String()) if err != nil { stderr("rkt: image ID %q not valid: %v", pkey, err) continue } if key == "" { stderr("rkt: image ID %q doesn't exist", pkey) continue } aciinfo, err := s.GetACIInfoWithBlobKey(key) if err != nil { stderr("rkt: error retrieving aci infos for image %q: %v", key, err) continue } imageMap[key] = aciinfo.Name imageCounter[key]++ } } // Adjust the error count by subtracting duplicate IDs from it, // therefore allowing only one error per ID. for _, c := range imageCounter { if c > 1 { errors -= c - 1 } } for key, name := range imageMap { if err := s.RemoveACI(key); err != nil { if serr, ok := err.(*store.StoreRemovalError); ok { staleErrors++ stderr("rkt: some files cannot be removed for image %q (%q): %v", key, name, serr) } else { stderr("rkt: error removing aci for image %q (%q): %v", key, name, err) continue } } stdout("rkt: successfully removed aci for image: %q (%q)", key, name) errors-- done++ } if done > 0 { stderr("rkt: %d image(s) successfully removed", done) } // If anything didn't complete, return exit status of 1 if (errors + staleErrors) > 0 { if staleErrors > 0 { stderr("rkt: %d image(s) removed but left some stale files", staleErrors) } if errors > 0 { stderr("rkt: %d image(s) cannot be removed", errors) } return fmt.Errorf("error(s) found while removing images") } return nil }
func rmImages(s *store.Store, images []string) error { done := 0 errors := 0 staleErrors := 0 for _, pkey := range images { var ( keys []string name string ) errors++ h, err := types.NewHash(pkey) if err != nil { var found bool keys, found, err = s.ResolveName(pkey) if len(keys) > 0 { errors += len(keys) - 1 } if err != nil { stderr("rkt: %v", err) continue } if !found { stderr("rkt: image name %q not found", pkey) continue } name = pkey } else { key, err := s.ResolveKey(h.String()) if err != nil { stderr("rkt: image ID %q not valid: %v", pkey, err) continue } if key == "" { stderr("rkt: image ID %q doesn't exist", pkey) continue } aciinfo, err := s.GetACIInfoWithBlobKey(key) if err != nil { stderr("rkt: error retrieving aci infos for image %q: %v", key, err) continue } name = aciinfo.Name keys = append(keys, key) } for _, key := range keys { if err = s.RemoveACI(key); err != nil { if serr, ok := err.(*store.StoreRemovalError); ok { staleErrors++ stderr("rkt: some files cannot be removed for image %q (%q): %v", key, name, serr) } else { stderr("rkt: error removing aci for image %q (%q): %v", key, name, err) continue } } stdout("rkt: successfully removed aci for image: %q (%q)", key, name) errors-- done++ } } if done > 0 { stderr("rkt: %d image(s) successfully removed", done) } // If anything didn't complete, return exit status of 1 if (errors + staleErrors) > 0 { if staleErrors > 0 { stderr("rkt: %d image(s) removed but left some stale files", staleErrors) } if errors > 0 { stderr("rkt: %d image(s) cannot be removed", errors) } return fmt.Errorf("error(s) found while removing images") } return nil }
// Test running pod manifests that contains just one app. // TODO(yifan): Add more tests for port mappings. and multiple apps. func TestPodManifest(t *testing.T) { ctx := newRktRunCtx() defer ctx.cleanup() tmpdir, err := ioutil.TempDir("", "rkt-tests.") if err != nil { t.Fatalf("Cannot create temporary directory: %v", err) } defer os.RemoveAll(tmpdir) boolFalse, boolTrue := false, true tests := []struct { imageName string imagePatches []string podManifest *schema.PodManifest shouldSuccess bool expectedResult string }{ { // Simple read. "rkt-test-run-pod-manifest-read.aci", []string{}, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, }, }, }, }, }, true, "dir1", }, { // Simple read after write with volume mounted. "rkt-test-run-pod-manifest-vol-rw.aci", []string{}, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:foo", }, { // Simple read after write with read-only mount point, should fail. "rkt-test-run-pod-manifest-vol-ro.aci", []string{}, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", true}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, }, { // Simple read after write with volume mounted. // Override the image's mount point spec. This should fail as the volume is // read-only in pod manifest, (which will override the mount point in both image/pod manifest). "rkt-test-run-pod-manifest-vol-rw-override.aci", []string{ "--mounts=dir1,path=/dir1,readOnly=false", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, }, { // Simple read after write with volume mounted. // Override the image's mount point spec. "rkt-test-run-pod-manifest-vol-rw-override.aci", []string{ "--mounts=dir1,path=/dir1,readOnly=true", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir2/file"}, {"CONTENT", "host:bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir2", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:bar", }, { // Simple read after write with volume mounted, no apps in pod manifest. "rkt-test-run-pod-manifest-vol-rw-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:baz", "--mounts=dir1,path=/dir1,readOnly=false", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:baz", }, { // Simple read after write with volume mounted, no apps in pod manifest. // This should succeed even the mount point in image manifest is readOnly, // because it is overrided by the volume's readOnly. "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:zaz", "--mounts=dir1,path=/dir1,readOnly=true", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolFalse}, }, }, true, "host:zaz", }, { // Simple read after write with read-only volume mounted, no apps in pod manifest. // This should fail as the volume is read-only. "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=baz", "--mounts=dir1,path=/dir1,readOnly=false", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, }, } for i, tt := range tests { patchTestACI(tt.imageName, tt.imagePatches...) hash := importImageAndFetchHash(t, ctx, tt.imageName) defer os.Remove(tt.imageName) tt.podManifest.ACKind = schema.PodManifestKind tt.podManifest.ACVersion = schema.AppContainerVersion imgName := types.MustACIdentifier(tt.imageName) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } for i := range tt.podManifest.Apps { ra := &tt.podManifest.Apps[i] ra.Image.Name = imgName ra.Image.ID = *imgID } manifestFile := generatePodManifestFile(t, tt.podManifest) defer os.Remove(manifestFile) // 1. Test 'rkt run'. runCmd := fmt.Sprintf("%s run --pod-manifest=%s", ctx.cmd(), manifestFile) t.Logf("Running 'run' test #%v: %v", i, runCmd) child, err := gexpect.Spawn(runCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := child.Expect(tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. cmds := strings.Fields(ctx.cmd()) prepareCmd := exec.Command(cmds[0], cmds[1:]...) prepareArg := fmt.Sprintf("--pod-manifest=%s", manifestFile) prepareCmd.Args = append(prepareCmd.Args, "--insecure-skip-verify", "prepare", prepareArg) output, err := prepareCmd.Output() if err != nil { t.Fatalf("Cannot read the output: %v", err) } podIDStr := strings.TrimSpace(string(output)) podID, err := types.NewUUID(podIDStr) if err != nil { t.Fatalf("%q is not a valid UUID: %v", podIDStr, err) } runPreparedCmd := fmt.Sprintf("%s run-prepared %s", ctx.cmd(), podID.String()) t.Logf("Running 'run' test #%v: %v", i, runPreparedCmd) child, err = gexpect.Spawn(runPreparedCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := child.Expect(tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) } }
func TestAPIServiceListInspectPods(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() svc := startAPIService(t, ctx) defer stopAPIService(t, svc) c, conn := newAPIClientOrFail(t, "localhost:15441") defer conn.Close() resp, err := c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) != 0 { t.Errorf("Unexpected result: %v, should see zero pods", resp.Pods) } patches := []string{"--exec=/inspect --print-msg=HELLO_API --exit-code=42"} patchImportAndFetchHash("rkt-inspect-print.aci", patches, t, ctx) // TODO(derekparker) There is a bug where `patchImportAndFetchHash` does not // return the full hash, which causes `InspectPod` to fail later in this test. // So instead we list images to get the full, correct hash. iresp, err := c.ListImages(context.Background(), &v1alpha.ListImagesRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(iresp.Images) == 0 { t.Fatal("Expected non-zero images") } imgID, err := types.NewHash(iresp.Images[0].Id) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", iresp.Images[0].Id, err) } pm := schema.BlankPodManifest() pm.Apps = []schema.RuntimeApp{ schema.RuntimeApp{ Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, Annotations: []types.Annotation{types.Annotation{Name: types.ACIdentifier("app-test"), Value: "app-test"}}, }, } pm.Annotations = []types.Annotation{types.Annotation{Name: types.ACIdentifier("test"), Value: "test"}} manifestFile := generatePodManifestFile(t, pm) defer os.Remove(manifestFile) runCmd := fmt.Sprintf("%s run --pod-manifest=%s", ctx.Cmd(), manifestFile) esp := spawnOrFail(t, runCmd) waitOrFail(t, esp, true) // ListPods(detail=false). resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) == 0 { t.Errorf("Unexpected result: %v, should see non-zero pods", resp.Pods) } for _, p := range resp.Pods { checkPodBasics(t, ctx, p) // Test InspectPod(). inspectResp, err := c.InspectPod(context.Background(), &v1alpha.InspectPodRequest{Id: p.Id}) if err != nil { t.Fatalf("Unexpected error: %v", err) } checkPodDetails(t, ctx, inspectResp.Pod) // Test Apps. for i, app := range p.Apps { checkAnnotations(t, pm.Apps[i].Annotations, app.Annotations) } } // ListPods(detail=true). resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{Detail: true}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) == 0 { t.Errorf("Unexpected result: %v, should see non-zero pods", resp.Pods) } for _, p := range resp.Pods { checkPodDetails(t, ctx, p) } }