func mergeManifests(manifests []schema.ImageManifest) schema.ImageManifest { // FIXME(iaguis) we take app layer's manifest as the final manifest for now manifest := manifests[0] manifest.Dependencies = nil layerIndex := -1 for i, l := range manifest.Labels { if l.Name.String() == "layer" { layerIndex = i } } if layerIndex != -1 { manifest.Labels = append(manifest.Labels[:layerIndex], manifest.Labels[layerIndex+1:]...) } nameWithoutLayerID := appctypes.MustACIdentifier(stripLayerID(manifest.Name.String())) manifest.Name = *nameWithoutLayerID // once the image is squashed, we don't need a pathWhitelist manifest.PathWhitelist = nil return manifest }
func NewTestVolumeMount(volumeMountTestCases [][]volumeMountTestCase) testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() deferredFuncs := prepareTmpDirWithRecursiveMountsAndFiles(t) defer executeFuncsReverse(deferredFuncs) for _, testCases := range volumeMountTestCases { for i, tt := range testCases { var hashesToRemove []string for j, v := range tt.images { hash, err := patchImportAndFetchHash(v.name, v.patches, t, ctx) if err != nil { t.Fatalf("error running patchImportAndFetchHash: %v", err) } hashesToRemove = append(hashesToRemove, hash) if tt.podManifest != nil { imgName := types.MustACIdentifier(v.name) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } tt.podManifest.Apps[j].Image.Name = imgName tt.podManifest.Apps[j].Image.ID = *imgID } } manifestFile := "" if tt.podManifest != nil { 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", ctx.Cmd()) if manifestFile != "" { runCmd += fmt.Sprintf(" --pod-manifest=%s", manifestFile) } else { // TODO: run the tests for more than just the first image runCmd += fmt.Sprintf(" %s %s", tt.cmdArgs, hashesToRemove[0]) } t.Logf("Running 'run' test #%v: %q", i, tt.description) child := spawnOrFail(t, runCmd) ctx.RegisterChild(child) if tt.expectedResult != "" { if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) } } child.Wait() verifyHostFile(t, volDir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. prepareCmd := fmt.Sprintf("%s prepare", ctx.Cmd()) if manifestFile != "" { prepareCmd += fmt.Sprintf(" --pod-manifest=%s", manifestFile) } else { // TODO: run the tests for more than just the first image prepareCmd += fmt.Sprintf(" %s %s", tt.cmdArgs, hashesToRemove[0]) } uuid := runRktAndGetUUID(t, prepareCmd) runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.Cmd(), uuid) t.Logf("Running 'run-prepared' test #%v: %q", i, tt.description) child = spawnOrFail(t, runPreparedCmd) if tt.expectedResult != "" { if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) } } child.Wait() verifyHostFile(t, volDir, "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) } } } }) }
// 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 expectedExit int expectedResult string cgroup string }{ { // Special characters []imagePatch{ {"rkt-test-run-pod-manifest-special-characters.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-msg=\n'\"$"}, User: "******", Group: "0", }, }, }, }, 0, `'"[$]`, "", }, { // Working directory. []imagePatch{ {"rkt-test-run-pod-manifest-working-directory.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-cwd"}, User: "******", Group: "0", WorkingDirectory: "/dir1", }, }, }, }, 0, "cwd: /dir1", "", }, { // 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"}, }, }, }, }, }, 0, "dir1", "", }, { // Simple read from read-only rootfs. []imagePatch{ {"rkt-test-run-read-only-rootfs-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"}, }, }, ReadOnlyRootFS: true, }, }, }, 0, "dir1", "", }, { // Simple read after write with *empty* volume mounted. []imagePatch{ {"rkt-test-run-pod-manifest-empty-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", "empty:foo"}, }, MountPoints: []types.MountPoint{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "empty", Mode: stringP("0755"), UID: intP(0), GID: intP(0), }, }, }, 0, "empty:foo", "", }, { // Simple read from read-only rootfs after write with *empty* volume mounted. []imagePatch{ {"rkt-test-run-pod-manifest-read-only-rootfs-empty-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", "empty:foo"}, }, MountPoints: []types.MountPoint{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "empty", Mode: stringP("0755"), UID: intP(0), GID: intP(0), }, }, }, 0, "empty:foo", "", }, { // Stat directory in a *empty* volume mounted. []imagePatch{ {"rkt-test-run-pod-manifest-empty-vol-stat.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--stat-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1"}, }, MountPoints: []types.MountPoint{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "empty", Mode: stringP("0123"), UID: intP(9991), GID: intP(9992), }, }, }, 0, "(?s)/dir1: mode: d--x-w--wx.*" + "/dir1: user: 9991.*" + "/dir1: group: 9992", "", }, { // Stat directory in a *empty* volume mounted using a read-only rootfs. []imagePatch{ {"rkt-test-run-pod-manifest-read-only-rootfs-empty-vol-stat.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--stat-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1"}, }, MountPoints: []types.MountPoint{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "empty", Mode: stringP("0123"), UID: intP(9991), GID: intP(9992), }, }, }, 0, "(?s)/dir1: mode: d--x-w--wx.*" + "/dir1: user: 9991.*" + "/dir1: group: 9992", "", }, { // 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{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "host:foo", "", }, { // Simple read after write with volume mounted in a read-only rootfs. []imagePatch{ {"rkt-test-run-pod-manifest-read-only-rootfs-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{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "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{ { Name: "dir1", Path: "/dir1", ReadOnly: true, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 254, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with read-only mount point in a read-only rootfs, should fail. []imagePatch{ {"rkt-test-run-pod-manifest-read-only-rootfs-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{ { Name: "dir1", Path: "/dir1", ReadOnly: true, }, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 254, `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{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: &boolTrue, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 254, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted in a read-only rootfs. // 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{ { Name: "dir1", Path: "/dir1", ReadOnly: false, }, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: &boolTrue, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 254, `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{ { Name: "dir1", Path: "/dir2", ReadOnly: false, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "host:bar", "", }, { // Simple read after write with volume mounted in a read-only rootfs. // Override the image's mount point spec. []imagePatch{ {"rkt-test-run-pod-manifest-read-only-rootfs-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{ { Name: "dir1", Path: "/dir2", ReadOnly: false, }, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "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:baw", "--mounts=dir1,path=/dir1,readOnly=false", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "host:baw", "", }, { // Simple read after write with volume mounted in a read-only rootfs, no apps in pod manifest. []imagePatch{ { "rkt-test-run-pod-manifest-read-only-rootfs-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, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "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 overridden 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{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: &boolFalse, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "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{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: &boolTrue, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 254, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write in read-only rootfs 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-read-only-rootfs-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, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ { Name: "dir1", Kind: "host", Source: tmpdir, ReadOnly: &boolTrue, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 254, `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{ mustNewIsolator(`{ "name": "resource/cpu", "value": { "request": "100m", "limit": "100m"} }`), mustNewIsolator(`{ "name": "os/linux/capabilities-retain-set", "value": { "set": ["CAP_SYS_PTRACE"] } }`), }, }, }, }, }, 0, `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{ // 4MB. mustNewIsolator(`{ "name": "resource/memory", "value": { "request": "4194304", "limit": "4194304"} }`), mustNewIsolator(`{ "name": "os/linux/capabilities-retain-set", "value": { "set": ["CAP_SYS_PTRACE"] } }`), }, }, }, }, }, 0, `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{ { Name: "dir", Path: "/dir", ReadOnly: 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{ { Name: "dir", Path: "/dir", ReadOnly: false, }, }, }, }, }, Volumes: []types.Volume{ { Name: "dir", Kind: "host", Source: tmpdir, ReadOnly: nil, Recursive: nil, Mode: nil, UID: nil, GID: nil, }, }, }, 0, "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))}, }, }, }, }, }, 0, 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{ mustNewIsolator(`{ "name": "os/linux/capabilities-retain-set", "value": { "set": ["CAP_NET_ADMIN"] } }`), }, }, }, }, }, 0, fmt.Sprintf("%v=enabled", capability.CAP_NET_ADMIN.String()), "", }, { // Set valid numerical app user and group. []imagePatch{ {"rkt-test-run-pod-manifest-valid-numerical-user-group.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "100", }, }, }, }, 0, "User: uid=1000 euid=1000 gid=100 egid=100", "", }, { // Set valid non-numerical app user and group. []imagePatch{ {"rkt-test-run-pod-manifest-valid-user-group.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "group1", }, }, }, }, 0, "User: uid=1000 euid=1000 gid=100 egid=100", "", }, { // Set "root", it should work without it being present in // /etc/{passwd,group} []imagePatch{ {"rkt-test-run-pod-manifest-root-user-group.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "root", }, }, }, }, 0, "User: uid=0 euid=0 gid=0 egid=0", "", }, { // Set invalid non-numerical app user. []imagePatch{ {"rkt-test-run-pod-manifest-invalid-user.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "0", }, }, }, }, 254, `"user2" user not found`, "", }, { // Set invalid non-numerical app group. []imagePatch{ {"rkt-test-run-pod-manifest-invalid-group.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "group2", }, }, }, }, 254, `"group2" group not found`, "", }, { // Set valid path-like app user and group. []imagePatch{ {"rkt-test-run-pod-manifest-valid-path-user-group.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "/etc/group", }, }, }, }, 0, "User: uid=0 euid=0 gid=0 egid=0", "", }, { // Set invalid path-like app user. []imagePatch{ {"rkt-test-run-pod-manifest-invalid-path-user.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "0", }, }, }, }, 254, `no such file or directory`, "", }, { // Set invalid path-like app group. []imagePatch{ {"rkt-test-run-pod-manifest-invalid-path-group.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-user"}, User: "******", Group: "/etc/nofile", }, }, }, }, 254, `no such file or directory`, "", }, } for i, tt := range tests { if tt.cgroup != "" { ok, err := cgroup.IsIsolatorSupported(tt.cgroup) if err != nil { t.Fatalf("Error checking memory isolator support: %v", err) } if !ok { t.Logf("Skip test #%v: cgroup %s not supported", i, tt.cgroup) continue } } var hashesToRemove []string for j, v := range tt.images { hash, err := patchImportAndFetchHash(v.name, v.patches, t, ctx) if err != nil { t.Fatalf("%v", err) } 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 _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Errorf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) continue } } waitOrFail(t, child, tt.expectedExit) verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. rktCmd := fmt.Sprintf("%s --insecure-options=image 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 _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Errorf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) continue } } waitOrFail(t, child, tt.expectedExit) 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) } } }
func NewAPIServiceListInspectPodsTest() testutils.Test { return testutils.TestFunc(func(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=0"} imageHash, err := patchImportAndFetchHash("rkt-inspect-print.aci", patches, t, ctx) if err != nil { t.Fatalf("%v", err) } imgID, err := types.NewHash(imageHash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", imageHash, err) } podManifests := []struct { mfst schema.PodManifest net string expectedExitCode int }{ { // 1, Good pod. schema.PodManifest{ ACKind: schema.PodManifestKind, ACVersion: schema.AppContainerVersion, Apps: []schema.RuntimeApp{ { Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, Annotations: []types.Annotation{{Name: types.ACIdentifier("app-test"), Value: "app-test"}}, }, }, Annotations: []types.Annotation{ {Name: types.ACIdentifier("test"), Value: "test"}, }, }, "default", 0, }, { // 2, Bad pod, won't be launched correctly. schema.PodManifest{ ACKind: schema.PodManifestKind, ACVersion: schema.AppContainerVersion, Apps: []schema.RuntimeApp{ { Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, }, }, }, "non-existent-network", 254, }, } // Launch the pods. for _, entry := range podManifests { manifestFile := generatePodManifestFile(t, &entry.mfst) defer os.Remove(manifestFile) runCmd := fmt.Sprintf("%s run --net=%s --pod-manifest=%s", ctx.Cmd(), entry.net, manifestFile) waitOrFail(t, spawnOrFail(t, runCmd), entry.expectedExitCode) } time.Sleep(delta) gcCmd := fmt.Sprintf("%s gc --mark-only=true", ctx.Cmd()) waitOrFail(t, spawnOrFail(t, gcCmd), 0) gcTime := time.Now() // ListPods(detail=false). resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) != len(podManifests) { t.Errorf("Unexpected result: %v, should see %v pods", len(resp.Pods), len(podManifests)) } for _, p := range resp.Pods { checkPodBasicsWithGCTime(t, ctx, p, gcTime) // 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) } // 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) != len(podManifests) { t.Errorf("Unexpected result: %v, should see %v pods", len(resp.Pods), len(podManifests)) } for _, p := range resp.Pods { checkPodDetails(t, ctx, p) } // ListPods with corrupt pod directory // Note that we don't checkPodDetails here, the failure this is testing is // the api server panicing, which results in a list call hanging for ages // and then failing. // TODO: do further validation on the partial pods returned for _, p := range resp.Pods { numRemoved := 0 podDir := getPodDir(t, ctx, p.Id) filepath.Walk(filepath.Join(podDir, "appsinfo"), filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.Name() == "manifest" { os.Remove(path) numRemoved++ } return nil })) if numRemoved == 0 { t.Fatalf("Expected to remove at least one app manifest for pod %v", p) } } // 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) != len(podManifests) { t.Fatalf("Expected %v pods, got %v pods", len(podManifests), len(resp.Pods)) } }) }
func GenerateManifest(layerData types.DockerImageData, dockerURL *types.ParsedDockerURL) (*schema.ImageManifest, error) { dockerConfig := layerData.Config genManifest := &schema.ImageManifest{} appURL := "" // omit docker hub index URL in app name if dockerURL.IndexURL != defaultIndex { appURL = dockerURL.IndexURL + "/" } appURL += dockerURL.ImageName + "-" + layerData.ID appURL, err := appctypes.SanitizeACIdentifier(appURL) if err != nil { return nil, err } name := appctypes.MustACIdentifier(appURL) genManifest.Name = *name acVersion, err := appctypes.NewSemVer(schemaVersion) if err != nil { panic("invalid appc spec version") } genManifest.ACVersion = *acVersion genManifest.ACKind = appctypes.ACKind(schema.ImageManifestKind) var ( labels appctypes.Labels parentLabels appctypes.Labels annotations appctypes.Annotations ) layer := appctypes.MustACIdentifier("layer") labels = append(labels, appctypes.Label{Name: *layer, Value: layerData.ID}) tag := dockerURL.Tag version := appctypes.MustACIdentifier("version") labels = append(labels, appctypes.Label{Name: *version, Value: tag}) if layerData.OS != "" { os := appctypes.MustACIdentifier("os") labels = append(labels, appctypes.Label{Name: *os, Value: layerData.OS}) parentLabels = append(parentLabels, appctypes.Label{Name: *os, Value: layerData.OS}) if layerData.Architecture != "" { arch := appctypes.MustACIdentifier("arch") labels = append(labels, appctypes.Label{Name: *arch, Value: layerData.Architecture}) parentLabels = append(parentLabels, appctypes.Label{Name: *arch, Value: layerData.Architecture}) } } if layerData.Author != "" { authorsKey := appctypes.MustACIdentifier("authors") annotations = append(annotations, appctypes.Annotation{Name: *authorsKey, Value: layerData.Author}) } epoch := time.Unix(0, 0) if !layerData.Created.Equal(epoch) { createdKey := appctypes.MustACIdentifier("created") annotations = append(annotations, appctypes.Annotation{Name: *createdKey, Value: layerData.Created.Format(time.RFC3339)}) } if layerData.Comment != "" { commentKey := appctypes.MustACIdentifier("docker-comment") annotations = append(annotations, appctypes.Annotation{Name: *commentKey, Value: layerData.Comment}) } annotations = append(annotations, appctypes.Annotation{Name: *appctypes.MustACIdentifier(appcDockerV1RegistryURL), Value: dockerURL.IndexURL}) annotations = append(annotations, appctypes.Annotation{Name: *appctypes.MustACIdentifier(appcDockerV1Repository), Value: dockerURL.ImageName}) annotations = append(annotations, appctypes.Annotation{Name: *appctypes.MustACIdentifier(appcDockerV1ImageID), Value: layerData.ID}) annotations = append(annotations, appctypes.Annotation{Name: *appctypes.MustACIdentifier(appcDockerV1ParentImageID), Value: layerData.Parent}) genManifest.Labels = labels genManifest.Annotations = annotations if dockerConfig != nil { exec := getExecCommand(dockerConfig.Entrypoint, dockerConfig.Cmd) if exec != nil { user, group := parseDockerUser(dockerConfig.User) var env appctypes.Environment for _, v := range dockerConfig.Env { parts := strings.SplitN(v, "=", 2) env.Set(parts[0], parts[1]) } app := &appctypes.App{ Exec: exec, User: user, Group: group, Environment: env, WorkingDirectory: dockerConfig.WorkingDir, } app.MountPoints, err = convertVolumesToMPs(dockerConfig.Volumes) if err != nil { return nil, err } app.Ports, err = convertPorts(dockerConfig.ExposedPorts, dockerConfig.PortSpecs) if err != nil { return nil, err } genManifest.App = app } } if layerData.Parent != "" { indexPrefix := "" // omit docker hub index URL in app name if dockerURL.IndexURL != defaultIndex { indexPrefix = dockerURL.IndexURL + "/" } parentImageNameString := indexPrefix + dockerURL.ImageName + "-" + layerData.Parent parentImageNameString, err := appctypes.SanitizeACIdentifier(parentImageNameString) if err != nil { return nil, err } parentImageName := appctypes.MustACIdentifier(parentImageNameString) genManifest.Dependencies = append(genManifest.Dependencies, appctypes.Dependency{ImageName: *parentImageName, Labels: parentLabels}) annotations = append(annotations, appctypes.Annotation{Name: *appctypes.MustACIdentifier(appcDockerV1Tag), Value: dockerURL.Tag}) } return genManifest, nil }
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=0"} imageHash := patchImportAndFetchHash("rkt-inspect-print.aci", patches, t, ctx) imgID, err := types.NewHash(imageHash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", imageHash, err) } pm := schema.BlankPodManifest() pm.Apps = []schema.RuntimeApp{ { Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, Annotations: []types.Annotation{{Name: types.ACIdentifier("app-test"), Value: "app-test"}}, }, } pm.Annotations = []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) waitOrFail(t, spawnOrFail(t, runCmd), 0) gcCmd := fmt.Sprintf("%s gc --mark-only=true", ctx.Cmd()) waitOrFail(t, spawnOrFail(t, gcCmd), 0) gcTime := time.Now() // 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 { checkPodBasicsWithGCTime(t, ctx, p, gcTime) // 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) } }
// GenerateManifest converts the docker manifest format to an appc // ImageManifest. func GenerateManifest(layerData types.DockerImageData, dockerURL *types.ParsedDockerURL) (*schema.ImageManifest, error) { dockerConfig := layerData.Config genManifest := &schema.ImageManifest{} appURL := "" appURL = dockerURL.IndexURL + "/" appURL += dockerURL.ImageName + "-" + layerData.ID appURL, err := appctypes.SanitizeACIdentifier(appURL) if err != nil { return nil, err } name := appctypes.MustACIdentifier(appURL) genManifest.Name = *name acVersion, err := appctypes.NewSemVer(schema.AppContainerVersion.String()) if err != nil { panic("invalid appc spec version") } genManifest.ACVersion = *acVersion genManifest.ACKind = appctypes.ACKind(schema.ImageManifestKind) var annotations appctypes.Annotations labels := make(map[appctypes.ACIdentifier]string) parentLabels := make(map[appctypes.ACIdentifier]string) addLabel := func(key, val string) { if key != "" && val != "" { labels[*appctypes.MustACIdentifier(key)] = val } } addParentLabel := func(key, val string) { if key != "" && val != "" { parentLabels[*appctypes.MustACIdentifier(key)] = val } } addAnno := func(key, val string) { if key != "" && val != "" { annotations.Set(*appctypes.MustACIdentifier(key), val) } } addLabel("layer", layerData.ID) addLabel("version", dockerURL.Tag) addLabel("os", layerData.OS) addParentLabel("os", layerData.OS) addLabel("arch", layerData.Architecture) addParentLabel("arch", layerData.OS) addAnno("authors", layerData.Author) epoch := time.Unix(0, 0) if !layerData.Created.Equal(epoch) { addAnno("created", layerData.Created.Format(time.RFC3339)) } addAnno("docker-comment", layerData.Comment) addAnno(common.AppcDockerRegistryURL, dockerURL.IndexURL) addAnno(common.AppcDockerRepository, dockerURL.ImageName) addAnno(common.AppcDockerImageID, layerData.ID) addAnno(common.AppcDockerParentImageID, layerData.Parent) if dockerConfig != nil { exec := getExecCommand(dockerConfig.Entrypoint, dockerConfig.Cmd) if exec != nil { user, group := parseDockerUser(dockerConfig.User) var env appctypes.Environment for _, v := range dockerConfig.Env { parts := strings.SplitN(v, "=", 2) env.Set(parts[0], parts[1]) } app := &appctypes.App{ Exec: exec, User: user, Group: group, Environment: env, WorkingDirectory: dockerConfig.WorkingDir, } app.MountPoints, err = convertVolumesToMPs(dockerConfig.Volumes) if err != nil { return nil, err } app.Ports, err = convertPorts(dockerConfig.ExposedPorts, dockerConfig.PortSpecs) if err != nil { return nil, err } ep, cmd, err := generateEPCmdAnnotation(dockerConfig.Entrypoint, dockerConfig.Cmd) if err != nil { return nil, err } if len(ep) > 0 { addAnno(common.AppcDockerEntrypoint, ep) } if len(cmd) > 0 { addAnno(common.AppcDockerCmd, cmd) } genManifest.App = app } } if layerData.Parent != "" { indexPrefix := "" // omit docker hub index URL in app name indexPrefix = dockerURL.IndexURL + "/" parentImageNameString := indexPrefix + dockerURL.ImageName + "-" + layerData.Parent parentImageNameString, err := appctypes.SanitizeACIdentifier(parentImageNameString) if err != nil { return nil, err } parentImageName := appctypes.MustACIdentifier(parentImageNameString) plbl, err := appctypes.LabelsFromMap(labels) if err != nil { return nil, err } genManifest.Dependencies = append(genManifest.Dependencies, appctypes.Dependency{ImageName: *parentImageName, Labels: plbl}) addAnno(common.AppcDockerTag, dockerURL.Tag) } genManifest.Labels, err = appctypes.LabelsFromMap(labels) if err != nil { return nil, err } genManifest.Annotations = annotations return genManifest, nil }
func TestFlyMountPodManifest(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() tmpdir := createTempDirOrPanic("rkt-tests.") defer os.RemoveAll(tmpdir) tests := []struct { // [image name]:[image patches] images []imagePatch podManifest *schema.PodManifest expectedExit int expectedResult string }{ { // Simple read after write with volume mounted in a read-only rootfs. []imagePatch{ {"rkt-test-run-pod-manifest-read-only-rootfs-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}, }, }, ReadOnlyRootFS: true, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil, nil, nil, nil}, }, }, 0, "host:foo", }, } for i, tt := range tests { var hashesToRemove []string for j, v := range tt.images { hash, err := patchImportAndFetchHash(v.name, v.patches, t, ctx) if err != nil { t.Fatalf("%v", err) } 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) ctx.RegisterChild(child) if tt.expectedResult != "" { if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Errorf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) continue } } waitOrFail(t, child, tt.expectedExit) verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. rktCmd := fmt.Sprintf("%s --insecure-options=image 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 _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Errorf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) continue } } waitOrFail(t, child, tt.expectedExit) 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) } } }