// 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) (*types.Hash, error) { ensureLogger(f.Debug) // Check if it's an hash if _, err := types.NewHash(img); err == nil { h, err := f.getHashFromStore(img) if err != nil { return nil, err } return h, nil } d, err := DistFromImageString(img) if err != nil { return nil, err } // urls, names, paths have to be fetched, potentially remotely ft := (*Fetcher)(f) h, err := ft.FetchImage(d, img, asc) if err != nil { return nil, err } return h, nil }
func (f *Finder) getHashFromStore(img string) (*types.Hash, error) { h, err := types.NewHash(img) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("%q is not a valid hash", img), err) } fullKey, err := f.S.ResolveKey(img) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("could not resolve image %q", img), err) } h, err = types.NewHash(fullKey) if err != nil { // should never happen log.PanicE("got an invalid hash from the store, looks like it is corrupted", err) } return h, nil }
func getImage(name string, localOnly bool) (*jetpack.Image, error) { if h, err := types.NewHash(name); err == nil { if len(name) < hashSize { // Short hash. Iterate over images, return first prefix match. // FIXME: what about multiple matches? name = strings.ToLower(name) if imgs, err := Host.Images(); err != nil { return nil, errors.Trace(err) } else { for _, img := range imgs { if strings.HasPrefix(img.Hash.String(), name) { return img, nil } } return nil, jetpack.ErrNotFound } } return Host.GetImage(*h, "", nil) } if name, labels, err := parseImageName(name); err != nil { return nil, errors.Trace(err) } else if localOnly { return Host.GetLocalImage(types.Hash{}, name, labels) } else { return Host.GetImage(types.Hash{}, name, labels) } }
func parseApp(args []string) ([]string, *schema.RuntimeApp, error) { if len(args) == 0 { return nil, nil, nil } rtapp := schema.RuntimeApp{} // Parse first argument (image name) if h, err := types.NewHash(args[0]); err == nil { rtapp.Image.ID = *h rtapp.Name.Set(h.String()) // won't err } else if dapp, err := discovery.NewAppFromString(args[0]); err == nil { rtapp.Image.Name = &dapp.Name rtapp.Name.Set(path.Base(dapp.Name.String())) // won't err here if ll, err := types.LabelsFromMap(dapp.Labels); err != nil { return args, nil, err } else { rtapp.Image.Labels = ll } } else { return args, nil, err } fl := flag.NewFlagSet(args[0], flag.ExitOnError) fl.Var(&rtapp.Name, "name", "App name") fl.Var((*AnnotationsFlag)(&rtapp.Annotations), "a", "Add annotation (NAME=VALUE)") fl.Var((*MountsFlag)(&rtapp.Mounts), "m", "Mount volume (VOLUME[:MOUNTPOINT])") // TODO: app override fl.Parse(args[1:]) return fl.Args(), &rtapp, nil }
// ExtractLayerInfo extracts the image name and ID from a path to an ACI func ExtractLayerInfo(store *store.Store, in string) (types.Dependency, error) { im, err := GetManifestFromImage(in) if err != nil { return types.Dependency{}, fmt.Errorf("error getting manifest from image (%v): %v", in, err) } inFile, err := os.Open(in) if err != nil { return types.Dependency{}, fmt.Errorf("error opening ACI: %v", err) } defer inFile.Close() inImageID, err := store.WriteACI(inFile, false) if err != nil { return types.Dependency{}, fmt.Errorf("error writing ACI into the tree store: %v", err) } hash, err := types.NewHash(inImageID) if err != nil { return types.Dependency{}, fmt.Errorf("error creating hash from an image ID (%s): %v", hash, err) } return types.Dependency{ ImageName: im.Name, ImageID: hash, }, nil }
//rktDirectory string, ociConfig string, ociRuntimeConfig string, ociDirectory string func (this *oci2rkt) makePod() (err error) { //1.covert to pod this.podManifest.ACVersion = types.SemVer{ Major: 0, Minor: 8, Patch: 0, PreRelease: "", Metadata: "", } this.podManifest.ACKind = "PodManifest" /* this.podManifest.Apps = schema.AppList{ { Name: "oci", Image: schema.RuntimeImage{ ID: types.Hash{ Val: "", }, }, }, } */ runTimeImage := schema.RuntimeImage{} hash, err := types.NewHash("sha512-ca0bee4ecb888d10cf0816ebe7e16499230ab349bd3126976ab60b9b1db2e120") if err != nil { return err } runTimeImage.ID = *hash runTimeApp := schema.RuntimeApp{ Name: "oci", } runTimeApp.Image = runTimeImage this.podManifest.Apps = append(this.podManifest.Apps, runTimeApp) this.podManifest.Volumes = nil this.podManifest.Isolators = nil this.podManifest.Annotations = nil this.podManifest.Ports = nil //2.marshal podBuf, err := json.Marshal(this.podManifest) if err != nil { return err } //3.wirte pod file pod := this.RktBundlePath + "/pod" err = ioutil.WriteFile(pod, podBuf, 0755) if err != nil { return err } return nil }
func mustRktHash(hash string) *appctypes.Hash { h, err := appctypes.NewHash(hash) if err != nil { panic(err) } return h }
// FetchImage will take an image as either a path, a URL or a name // string and import it into the store if found. If ascPath is not "", // it must exist as a local file and will be used as the signature // file for verification, unless verification is disabled. If // f.WithDeps is true also image dependencies are fetched. func (f *Fetcher) FetchImage(d dist.Distribution, image, ascPath string) (*types.Hash, error) { ensureLogger(f.Debug) db := &distBundle{ dist: d, image: image, } a := f.getAsc(ascPath) hash, err := f.fetchSingleImage(db, a) if err != nil { return nil, err } if f.WithDeps { err = f.fetchImageDeps(hash) if err != nil { return nil, err } } // we need to be able to do a chroot and access to the tree store // directories, we need to // 1) check if the system supports OverlayFS // 2) check if we're root if common.SupportsOverlay() == nil && os.Geteuid() == 0 { if _, _, err := f.Ts.Render(hash, false); err != nil { return nil, errwrap.Wrap(errors.New("error rendering tree store"), err) } } h, err := types.NewHash(hash) if err != nil { // should never happen log.PanicE("invalid hash", err) } return h, nil }
func (p *Pod) processAci(e common.RuntimeApp) (*schema.RuntimeApp, error) { aci, err := p.buildAci(e) if err != nil { return nil, err } name, err := types.NewACName(e.Name) if err != nil { return nil, errs.WithEF(err, p.fields.WithField("name", e.Name), "Invalid name format") } sum, err := Sha512sum(aci.target + pathImageAci) if err != nil { return nil, errs.WithEF(err, p.fields.WithField("file", aci.target+pathImageAci), "Failed to calculate sha512 of aci") } tmp, _ := types.NewHash("sha512-" + sum) labels := types.Labels{} labels = append(labels, types.Label{Name: "version", Value: aci.manifest.NameAndVersion.Version()}) identifier, _ := types.NewACIdentifier(aci.manifest.NameAndVersion.Name()) ttmp := schema.RuntimeImage{Name: identifier, ID: *tmp, Labels: labels} e.App.Group = aci.manifest.Aci.App.Group e.App.User = aci.manifest.Aci.App.User if e.App.User == "" { e.App.User = "******" } if e.App.Group == "" { e.App.Group = "0" } isolators, err := common.ToAppcIsolators(e.App.Isolators) if err != nil { return nil, errs.WithEF(err, p.fields, "Failed to prepare isolators") } return &schema.RuntimeApp{ Name: *name, Image: ttmp, App: &types.App{ Exec: e.App.Exec, User: e.App.User, Group: e.App.Group, WorkingDirectory: e.App.WorkingDirectory, SupplementaryGIDs: e.App.SupplementaryGIDs, Environment: e.App.Environment, MountPoints: e.App.MountPoints, Ports: e.App.Ports, Isolators: isolators, }, Mounts: e.Mounts, Annotations: e.Annotations}, 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 try to stat whatever file the URL would specify. If it // exists, that's probably what the user wanted. f, err := os.Stat(image) if err == nil && f.Mode().IsRegular() { return apps.AppImagePath } // Second, 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 } // Third, 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 } // Fourth, 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 }
func runAddDep(cmd *cobra.Command, args []string) (exit int) { if len(args) == 0 { cmd.Usage() return 1 } if len(args) != 1 { stderr("dependency add: incorrect number of arguments") return 1 } if debug { stderr("Adding dependency %q", args[0]) } app, err := discovery.NewAppFromString(args[0]) if err != nil { stderr("dependency add: couldn't parse dependency name: %v", err) return 1 } appcLabels := types.Labels(labels) for name, value := range app.Labels { if _, ok := appcLabels.Get(string(name)); ok { stderr("multiple %s labels specified", name) return 1 } appcLabels = append(appcLabels, types.Label{ Name: name, Value: value, }) } var hash *types.Hash if imageId != "" { var err error hash, err = types.NewHash(imageId) if err != nil { stderr("dependency add: couldn't parse image ID: %v", err) return 1 } } err = newACBuild().AddDependency(app.Name, hash, appcLabels, size) if err != nil { stderr("dependency add: %v", err) return getErrorCode(err) } return 0 }
func (p *Pod) processAci() []schema.RuntimeApp { apps := []schema.RuntimeApp{} for _, e := range p.manifest.Pod.Apps { aciName := p.buildAciIfNeeded(e) // TODO: support not FS override by only storing info pod manifest // if aciName == nil { // aciName = &e.Image // } name, _ := types.NewACName(e.Name) sum, err := utils.Sha512sum(p.path + "/" + e.Name + "/target/image.aci") if err != nil { log.Get().Panic(err) } tmp, _ := types.NewHash("sha512-" + sum) labels := types.Labels{} labels = append(labels, types.Label{Name: "version", Value: aciName.Version()}) identifier, _ := types.NewACIdentifier(aciName.Name()) ttmp := schema.RuntimeImage{Name: identifier, ID: *tmp, Labels: labels} if e.App.User == "" { e.App.User = "******" } if e.App.Group == "" { e.App.Group = "0" } apps = append(apps, schema.RuntimeApp{ Name: *name, Image: ttmp, App: &types.App{ Exec: e.App.Exec, EventHandlers: e.App.EventHandlers, User: e.App.User, Group: e.App.Group, WorkingDirectory: e.App.WorkingDirectory, Environment: e.App.Environment, MountPoints: e.App.MountPoints, Ports: e.App.Ports, Isolators: e.App.Isolators, }, Mounts: e.Mounts, Annotations: e.Annotations}) } return apps }
func TestDockerVolumeSemanticsPodManifest(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() for i, tt := range volDockerTests { t.Logf("Running test #%v on directory %s", i, tt.dir) hash, err := patchImportAndFetchHash(fmt.Sprintf("rkt-volume-image-pm-%d.aci", i), []string{fmt.Sprintf("--mounts=mydir,path=%s,readOnly=false", tt.dir)}, t, ctx) if err != nil { t.Fatalf("%v", err) } imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } pm := &schema.PodManifest{ ACKind: schema.PodManifestKind, ACVersion: schema.AppContainerVersion, Apps: []schema.RuntimeApp{ { Name: "rkt-volume-image", App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", fmt.Sprintf("%s/file", tt.dir)}, }, MountPoints: []types.MountPoint{ {"mydir", tt.dir, false}, }, }, Image: schema.RuntimeImage{ ID: *imgID, }, }, }, } manifestFile := generatePodManifestFile(t, pm) defer os.Remove(manifestFile) cmd := fmt.Sprintf("%s --debug --insecure-options=image run --pod-manifest=%s", ctx.Cmd(), manifestFile) expected := fmt.Sprintf("<<<%s>>>", tt.expectedContent) runRktAndCheckOutput(t, cmd, expected, false) } }
func (r *Runtime) newAppcRuntimeApp(pod *api.Pod, c api.Container, pullSecrets []api.Secret) (*appcschema.RuntimeApp, []kubecontainer.PortMapping, error) { if err, _ := r.imagePuller.PullImage(pod, &c, pullSecrets); err != nil { return nil, nil, err } imgManifest, err := r.getImageManifest(c.Image) if err != nil { return nil, nil, err } if imgManifest.App == nil { imgManifest.App = new(appctypes.App) } imageID, err := r.getImageID(c.Image) if err != nil { return nil, nil, err } hash, err := appctypes.NewHash(imageID) if err != nil { return nil, nil, err } opts, err := r.generator.GenerateRunContainerOptions(pod, &c) if err != nil { return nil, nil, err } if err := setApp(imgManifest.App, &c, opts); err != nil { return nil, nil, err } name, err := appctypes.SanitizeACName(c.Name) if err != nil { return nil, nil, err } appName := appctypes.MustACName(name) kubehash := kubecontainer.HashContainer(&c) return &appcschema.RuntimeApp{ Name: *appName, Image: appcschema.RuntimeImage{ID: *hash}, App: imgManifest.App, Annotations: []appctypes.Annotation{ { Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno), Value: strconv.FormatUint(kubehash, 10), }, }, }, opts.PortMappings, nil }
func getStoreKeyFromAppOrHash(s *imagestore.Store, input string) (string, error) { var key string if _, err := types.NewHash(input); err == nil { key, err = s.ResolveKey(input) if err != nil { return "", errwrap.Wrap(errors.New("cannot resolve image ID"), err) } } else { key, err = getStoreKeyFromApp(s, input) if err != nil { return "", errwrap.Wrap(errors.New("cannot find image"), err) } } return key, nil }
func TestAdd2Dependencies(t *testing.T) { workingDir := setUpTest(t) defer cleanUpTest(workingDir) err := runACBuildNoHist(workingDir, "dependency", "add", depName, "--image-id", depImageID, "--label", newLabel(depLabel1Key, depLabel1Val), "--label", newLabel(depLabel2Key, depLabel2Val), "--size", strconv.Itoa(int(depSize))) if err != nil { t.Fatalf("%v\n", err) } err = runACBuildNoHist(workingDir, "dependency", "add", depName2) if err != nil { t.Fatalf("%v\n", err) } hash, err := types.NewHash(depImageID) if err != nil { panic(err) } deps := types.Dependencies{ types.Dependency{ ImageName: *types.MustACIdentifier(depName), ImageID: hash, Labels: types.Labels{ types.Label{ Name: *types.MustACIdentifier(depLabel1Key), Value: depLabel1Val, }, types.Label{ Name: *types.MustACIdentifier(depLabel2Key), Value: depLabel2Val, }, }, Size: depSize, }, types.Dependency{ ImageName: *types.MustACIdentifier(depName2), }, } checkManifest(t, workingDir, manWithDeps(deps)) checkEmptyRootfs(t, workingDir) }
func (p *Pod) processAci() []schema.RuntimeApp { apps := []schema.RuntimeApp{} for _, e := range p.manifest.Pod.Apps { aci := p.buildAci(e) name, _ := types.NewACName(e.Name) sum, err := utils.Sha512sum(aci.target + "/image.aci") if err != nil { panic(err) } tmp, _ := types.NewHash("sha512-" + sum) labels := types.Labels{} labels = append(labels, types.Label{Name: "version", Value: aci.manifest.NameAndVersion.Version()}) identifier, _ := types.NewACIdentifier(aci.manifest.NameAndVersion.Name()) ttmp := schema.RuntimeImage{Name: identifier, ID: *tmp, Labels: labels} if e.App.User == "" { e.App.User = "******" } if e.App.Group == "" { e.App.Group = "0" } apps = append(apps, schema.RuntimeApp{ Name: *name, Image: ttmp, App: &types.App{ Exec: e.App.Exec, User: e.App.User, Group: e.App.Group, WorkingDirectory: e.App.WorkingDirectory, Environment: e.App.Environment, MountPoints: e.App.MountPoints, Ports: e.App.Ports, Isolators: e.App.Isolators, }, Mounts: e.Mounts, Annotations: e.Annotations}) } return apps }
// 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 }
// GetID 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 (ts *Store) GetID(key string) (string, error) { hash, err := types.NewHash(key) if err != nil { return "", err } images, err := acirenderer.CreateDepListFromImageID(*hash, ts.store) if err != nil { return "", err } var 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 log.PanicE("got an invalid hash from the store, looks like it is corrupted", err) } return h, nil }
func TestAddDependencyWithImageID(t *testing.T) { workingDir := setUpTest(t) defer cleanUpTest(workingDir) err := runACBuildNoHist(workingDir, "dependency", "add", depName, "--image-id", depImageID) if err != nil { t.Fatalf("%v\n", err) } hash, err := types.NewHash(depImageID) if err != nil { panic(err) } deps := types.Dependencies{ types.Dependency{ ImageName: *types.MustACIdentifier(depName), ImageID: hash, }, } checkManifest(t, workingDir, manWithDeps(deps)) checkEmptyRootfs(t, workingDir) }
// makePodManifest transforms a kubelet pod spec to the rkt pod manifest. // TODO(yifan): Use the RunContainerOptions generated by GenerateRunContainerOptions(). func (r *runtime) makePodManifest(pod *api.Pod) (*appcschema.PodManifest, error) { var globalPortMappings []kubecontainer.PortMapping manifest := appcschema.BlankPodManifest() for _, c := range pod.Spec.Containers { imgManifest, err := r.getImageManifest(c.Image) if err != nil { return nil, err } if imgManifest.App == nil { return nil, fmt.Errorf("no app section in image manifest for image: %q", c.Image) } img, err := r.getImageByName(c.Image) if err != nil { return nil, err } hash, err := appctypes.NewHash(img.id) if err != nil { return nil, err } opts, err := r.generator.GenerateRunContainerOptions(pod, &c) if err != nil { return nil, err } globalPortMappings = append(globalPortMappings, opts.PortMappings...) if err := setApp(imgManifest.App, &c, opts); err != nil { return nil, err } manifest.Apps = append(manifest.Apps, appcschema.RuntimeApp{ // TODO(yifan): We should allow app name to be different with // image name. See https://github.com/coreos/rkt/pull/640. Name: imgManifest.Name, Image: appcschema.RuntimeImage{ID: *hash}, App: imgManifest.App, }) } volumeMap, ok := r.volumeGetter.GetVolumes(pod.UID) if !ok { return nil, fmt.Errorf("cannot get the volumes for pod %q", kubecontainer.GetPodFullName(pod)) } // Set global volumes. for name, volume := range volumeMap { volName, err := appctypes.NewACName(name) if err != nil { return nil, fmt.Errorf("cannot use the volume's name %q as ACName: %v", name, err) } manifest.Volumes = append(manifest.Volumes, appctypes.Volume{ Name: *volName, Kind: "host", Source: volume.GetPath(), }) } // Set global ports. for _, port := range globalPortMappings { name, err := appctypes.SanitizeACName(port.Name) if err != nil { return nil, fmt.Errorf("cannot use the port's name %q as ACName: %v", port.Name, err) } portName := appctypes.MustACName(name) manifest.Ports = append(manifest.Ports, appctypes.ExposedPort{ Name: *portName, HostPort: uint(port.HostPort), }) } // TODO(yifan): Set pod-level isolators once it's supported in kubernetes. return manifest, nil }
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) } } }
// makePodManifest transforms a kubelet pod spec to the rkt pod manifest. func (r *runtime) makePodManifest(pod *api.Pod, pullSecrets []api.Secret) (*appcschema.PodManifest, error) { var globalPortMappings []kubecontainer.PortMapping manifest := appcschema.BlankPodManifest() for _, c := range pod.Spec.Containers { if err := r.imagePuller.PullImage(pod, &c, pullSecrets); err != nil { return nil, err } imgManifest, err := r.getImageManifest(c.Image) if err != nil { return nil, err } if imgManifest.App == nil { imgManifest.App = new(appctypes.App) } img, err := r.getImageByName(c.Image) if err != nil { return nil, err } hash, err := appctypes.NewHash(img.ID) if err != nil { return nil, err } opts, err := r.generator.GenerateRunContainerOptions(pod, &c) if err != nil { return nil, err } globalPortMappings = append(globalPortMappings, opts.PortMappings...) if err := setApp(imgManifest.App, &c, opts); err != nil { return nil, err } name, err := appctypes.SanitizeACName(c.Name) if err != nil { return nil, err } appName := appctypes.MustACName(name) manifest.Apps = append(manifest.Apps, appcschema.RuntimeApp{ Name: *appName, Image: appcschema.RuntimeImage{ID: *hash}, App: imgManifest.App, }) } volumeMap, ok := r.volumeGetter.GetVolumes(pod.UID) if !ok { return nil, fmt.Errorf("cannot get the volumes for pod %q", kubeletUtil.FormatPodName(pod)) } // Set global volumes. for name, volume := range volumeMap { volName, err := appctypes.NewACName(name) if err != nil { return nil, fmt.Errorf("cannot use the volume's name %q as ACName: %v", name, err) } manifest.Volumes = append(manifest.Volumes, appctypes.Volume{ Name: *volName, Kind: "host", Source: volume.GetPath(), }) } // Set global ports. for _, port := range globalPortMappings { name, err := appctypes.SanitizeACName(port.Name) if err != nil { return nil, fmt.Errorf("cannot use the port's name %q as ACName: %v", port.Name, err) } portName := appctypes.MustACName(name) manifest.Ports = append(manifest.Ports, appctypes.ExposedPort{ Name: *portName, HostPort: uint(port.HostPort), }) } // TODO(yifan): Set pod-level isolators once it's supported in kubernetes. return manifest, nil }
// 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("path %s already exists", treepath) } imageID, err := types.NewHash(key) if err != nil { return "", errwrap.Wrap(errors.New("cannot convert key to imageID"), err) } if err := os.MkdirAll(treepath, 0755); err != nil { return "", errwrap.Wrap(fmt.Errorf("cannot create treestore directory %s", treepath), err) } err = aci.RenderACIWithImageID(*imageID, treepath, s, uid.NewBlankUidRange()) if err != nil { return "", errwrap.Wrap(errors.New("cannot render aci"), err) } hash, err := ts.Hash(id) if err != nil { return "", errwrap.Wrap(errors.New("cannot calculate tree hash"), err) } err = ioutil.WriteFile(filepath.Join(treepath, hashfilename), []byte(hash), 0644) if err != nil { return "", errwrap.Wrap(errors.New("cannot write hash file"), 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 "", errwrap.Wrap(errors.New("failed to sync data"), err) } // Create rendered file f, err := os.Create(filepath.Join(treepath, renderedfilename)) if err != nil { return "", errwrap.Wrap(errors.New("failed to write rendered file"), 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 "", errwrap.Wrap(errors.New("cannot write image file"), err) } if err := syscall.Fsync(dfd); err != nil { return "", errwrap.Wrap(errors.New("failed to sync tree store directory"), 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 (r *Runtime) newAppcRuntimeApp(pod *api.Pod, c api.Container, pullSecrets []api.Secret, manifest *appcschema.PodManifest) error { if err, _ := r.imagePuller.PullImage(pod, &c, pullSecrets); err != nil { return nil } imgManifest, err := r.getImageManifest(c.Image) if err != nil { return err } if imgManifest.App == nil { imgManifest.App = new(appctypes.App) } imageID, err := r.getImageID(c.Image) if err != nil { return err } hash, err := appctypes.NewHash(imageID) if err != nil { return err } opts, err := r.runtimeHelper.GenerateRunContainerOptions(pod, &c) if err != nil { return err } // create the container log file and make a mount pair. mnt, err := makeContainerLogMount(opts, &c) if err != nil { return err } ctx := securitycontext.DetermineEffectiveSecurityContext(pod, &c) if err := setApp(imgManifest.App, &c, opts, ctx, pod.Spec.SecurityContext); err != nil { return err } ra := appcschema.RuntimeApp{ Name: convertToACName(c.Name), Image: appcschema.RuntimeImage{ID: *hash}, App: imgManifest.App, Annotations: []appctypes.Annotation{ { Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno), Value: strconv.FormatUint(kubecontainer.HashContainer(&c), 10), }, }, } if mnt != nil { ra.Annotations = append(ra.Annotations, appctypes.Annotation{ Name: *appctypes.MustACIdentifier(k8sRktTerminationMessagePathAnno), Value: mnt.HostPath, }) manifest.Volumes = append(manifest.Volumes, appctypes.Volume{ Name: convertToACName(mnt.Name), Kind: "host", Source: mnt.HostPath, }) } manifest.Apps = append(manifest.Apps, ra) // Set global ports. for _, port := range opts.PortMappings { manifest.Ports = append(manifest.Ports, appctypes.ExposedPort{ Name: convertToACName(port.Name), HostPort: uint(port.HostPort), }) } return nil }
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 rmImages(s *imagestore.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.Error(err) continue } if !found { stderr.Printf("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.PrintE(fmt.Sprintf("image ID %q not valid", pkey), err) continue } if key == "" { stderr.Printf("image ID %q doesn't exist", pkey) continue } aciinfo, err := s.GetACIInfoWithBlobKey(key) if err != nil { stderr.PrintE(fmt.Sprintf("error retrieving aci infos for image %q", 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.(*imagestore.StoreRemovalError); ok { staleErrors++ stderr.PrintE(fmt.Sprintf("some files cannot be removed for image %q (%q)", key, name), serr) } else { stderr.PrintE(fmt.Sprintf("error removing aci for image %q (%q)", key, name), err) continue } } stdout.Printf("successfully removed aci for image: %q", key) errors-- done++ } if done > 0 { stderr.Printf("%d image(s) successfully removed", done) } // If anything didn't complete, return exit status of 1 if (errors + staleErrors) > 0 { if staleErrors > 0 { stderr.Printf("%d image(s) removed but left some stale files", staleErrors) } if errors > 0 { stderr.Printf("%d image(s) cannot be removed", errors) } return fmt.Errorf("error(s) found while removing images") } return nil }
// 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) } }