// getAppImageID returns the image id to enter // If one was supplied in the flags then it's simply returned // If the PM contains a single image, that image's id is returned // If the PM has multiple images, the ids and names are printed and an error is returned func getAppImageID(p *pod) (*types.Hash, error) { if !flagAppImageID.Empty() { return &flagAppImageID, nil } // figure out the image id, or show a list if multiple are present b, err := ioutil.ReadFile(common.PodManifestPath(p.path())) if err != nil { return nil, fmt.Errorf("error reading pod manifest: %v", err) } m := schema.PodManifest{} if err = m.UnmarshalJSON(b); err != nil { return nil, fmt.Errorf("unable to load manifest: %v", err) } switch len(m.Apps) { case 0: return nil, fmt.Errorf("pod contains zero apps") case 1: return &m.Apps[0].Image.ID, nil default: } stderr("Pod contains multiple apps:") for _, ra := range m.Apps { stderr("\t%s: %s", types.ShortHash(ra.Image.ID.String()), ra.Name.String()) } return nil, fmt.Errorf("specify app using \"rkt enter --imageid ...\"") }
func runFetch(cmd *cobra.Command, args []string) (exit int) { if err := parseApps(&rktApps, args, cmd.Flags(), false); err != nil { stderr("fetch: unable to parse arguments: %v", err) return 1 } if rktApps.Count() < 1 { stderr("fetch: must provide at least one image") return 1 } if flagStoreOnly && flagNoStore { stderr("both --store-only and --no-store specified") return 1 } s, err := store.NewStore(getDataDir()) if err != nil { stderr("fetch: cannot open store: %v", err) return 1 } ks := getKeystore() config, err := getConfig() if err != nil { stderr("fetch: cannot get configuration: %v", err) return 1 } ft := &image.Fetcher{ S: s, Ks: ks, Headers: config.AuthPerHost, DockerAuth: config.DockerCredentialsPerRegistry, InsecureFlags: globalFlags.InsecureFlags, Debug: globalFlags.Debug, TrustKeysFromHTTPS: globalFlags.TrustKeysFromHTTPS, StoreOnly: flagStoreOnly, NoStore: flagNoStore, WithDeps: true, } err = rktApps.Walk(func(app *apps.App) error { hash, err := ft.FetchImage(app.Image, app.Asc, app.ImType) if err != nil { return err } if !flagFullHash { hash = types.ShortHash(hash) } stdout(hash) return nil }) if err != nil { stderr("%v", err) return 1 } return }
func runFetch(cmd *cobra.Command, args []string) (exit int) { if err := parseApps(&rktApps, args, cmd.Flags(), false); err != nil { stderr("fetch: unable to parse arguments: %v", err) return 1 } if rktApps.Count() < 1 { stderr("fetch: must provide at least one image") return 1 } if flagStoreOnly && flagNoStore { stderr("both --store-only and --no-store specified") return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("fetch: cannot open store: %v", err) return 1 } ks := getKeystore() config, err := getConfig() if err != nil { stderr("fetch: cannot get configuration: %v", err) return 1 } ft := &fetcher{ imageActionData: imageActionData{ s: s, ks: ks, headers: config.AuthPerHost, dockerAuth: config.DockerCredentialsPerRegistry, insecureSkipVerify: globalFlags.InsecureSkipVerify, debug: globalFlags.Debug, }, storeOnly: flagStoreOnly, noStore: flagNoStore, withDeps: true, } err = rktApps.Walk(func(app *apps.App) error { hash, err := ft.fetchImage(app.Image, app.Asc) if err != nil { return err } shortHash := types.ShortHash(hash) stdout(shortHash) return nil }) if err != nil { stderr("%v", err) return 1 } return }
func runFetch(args []string) (exit int) { if err := parseApps(&rktApps, args, &fetchFlags, false); err != nil { stderr("fetch: unable to parse arguments: %v", err) return 1 } if rktApps.Count() < 1 { stderr("fetch: must provide at least one image") return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("fetch: cannot open store: %v", err) return 1 } ks := getKeystore() config, err := getConfig() if err != nil { stderr("fetch: cannot get configuration: %v", err) return 1 } ft := &fetcher{ imageActionData: imageActionData{ s: s, ks: ks, headers: config.AuthPerHost, dockerAuth: config.DockerCredentialsPerRegistry, insecureSkipVerify: globalFlags.InsecureSkipVerify, debug: globalFlags.Debug, }, withDeps: true, } err = rktApps.Walk(func(app *apps.App) error { hash, err := ft.fetchImage(app.Image, app.Asc, true) if err != nil { return err } shortHash := types.ShortHash(hash) fmt.Println(shortHash) return nil }) if err != nil { stderr("%v", err) return 1 } return }
func TestResumedFetch(t *testing.T) { image := "rkt-inspect-implicit-fetch.aci" imagePath := patchTestACI(image, "--exec=/inspect") hash := types.ShortHash("sha512-" + getHashOrPanic(imagePath)) defer os.Remove(imagePath) kill := make(chan struct{}) reportkill := make(chan struct{}) shouldInterrupt := &synchronizedBool{} shouldInterrupt.Write(true) server := httptest.NewServer(testServerHandler(t, shouldInterrupt, imagePath, kill, reportkill)) defer server.Close() ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() cmd := fmt.Sprintf("%s --no-store --insecure-skip-verify fetch %s", ctx.Cmd(), server.URL) child := spawnOrFail(t, cmd) <-kill err := child.Close() if err != nil { panic(err) } reportkill <- struct{}{} // rkt has fetched the first half of the image // If it fetches the first half again these channels will be written to. // Closing them to make the test panic if they're written to. close(kill) close(reportkill) child = spawnOrFail(t, cmd) if _, _, err := expectRegexWithOutput(child, ".*"+hash); err != nil { t.Fatalf("hash didn't match: %v", err) } err = child.Wait() if err != nil { t.Errorf("rkt didn't exit cleanly: %v", err) } }
func TestResumedFetchInvalidCache(t *testing.T) { image := "rkt-inspect-implicit-fetch.aci" imagePath := patchTestACI(image, "--exec=/inspect") defer os.Remove(imagePath) hash := types.ShortHash("sha512-" + getHashOrPanic(imagePath)) kill := make(chan struct{}) reportkill := make(chan struct{}) ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() shouldInterrupt := &synchronizedBool{} shouldInterrupt.Write(true) // Fetch the first half of the image, and kill rkt once it reaches halfway. server := httptest.NewServer(testServerHandler(t, shouldInterrupt, imagePath, kill, reportkill)) defer server.Close() cmd := fmt.Sprintf("%s --no-store --insecure-skip-verify fetch %s", ctx.Cmd(), server.URL) child := spawnOrFail(t, cmd) <-kill err := child.Close() if err != nil { panic(err) } reportkill <- struct{}{} // Fetch the image again. The server doesn't support Etags or the // Last-Modified header, so the cached version should be invalidated. If // rkt tries to use the cache, the hash won't check out. shouldInterrupt.Write(false) child = spawnOrFail(t, cmd) if _, s, err := expectRegexWithOutput(child, ".*"+hash); err != nil { t.Fatalf("hash didn't match: %v\nin: %s", err, s) } err = child.Wait() if err != nil { t.Errorf("rkt didn't exit cleanly: %v", err) } }
// Enter enters the pod/app by exec()ing the stage1's /enter similar to /init // /enter can expect to have its CWD set to the app root. // imageID and command are supplied to /enter on argv followed by any arguments. // stage1Path is the path of the stage1 rootfs func Enter(cdir string, podPID int, imageID *types.Hash, stage1Path string, cmdline []string) error { if err := os.Chdir(cdir); err != nil { return fmt.Errorf("error changing to dir: %v", err) } id := types.ShortHash(imageID.String()) ep, err := getStage1Entrypoint(cdir, enterEntrypoint) if err != nil { return fmt.Errorf("error determining entrypoint: %v", err) } argv := []string{filepath.Join(stage1Path, ep)} argv = append(argv, strconv.Itoa(podPID)) argv = append(argv, id) argv = append(argv, cmdline...) if err := syscall.Exec(argv[0], argv, os.Environ()); err != nil { return fmt.Errorf("error execing enter: %v", err) } // never reached return nil }
// RelAppImagePath returns the path of an application image relative to the // stage1 chroot func RelAppImagePath(imageID types.Hash) string { return filepath.Join(stage2Dir, types.ShortHash(imageID.String())) }
// AppImagePath returns the path where an app image (i.e. unpacked ACI) is rooted (i.e. // where its contents are extracted during stage0), based on the app image ID. func AppImagePath(root string, imageID types.Hash) string { return filepath.Join(AppImagesPath(root), types.ShortHash(imageID.String())) }
// CreateCgroups mounts the cgroup controllers hierarchy for the container but // leaves the subcgroup for each app read-write so the systemd inside stage1 // can apply isolators to them func CreateCgroups(root string, subcgroup string, appHashes []types.Hash) error { cgroupsFile, err := os.Open("/proc/cgroups") if err != nil { return err } defer cgroupsFile.Close() cgroups, err := parseCgroups(cgroupsFile) if err != nil { return fmt.Errorf("error parsing /proc/cgroups: %v", err) } controllers := getControllers(cgroups) var flags uintptr // 1. Mount /sys read-only sys := filepath.Join(root, "/sys") if err := os.MkdirAll(sys, 0700); err != nil { return err } flags = syscall.MS_RDONLY | syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV if err := syscall.Mount("sysfs", sys, "sysfs", flags, ""); err != nil { return fmt.Errorf("error mounting %q: %v", sys, err) } // 2. Mount /sys/fs/cgroup cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup") if err := os.MkdirAll(cgroupTmpfs, 0700); err != nil { return err } flags = syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_STRICTATIME if err := syscall.Mount("tmpfs", cgroupTmpfs, "tmpfs", flags, "mode=755"); err != nil { return fmt.Errorf("error mounting %q: %v", cgroupTmpfs, err) } // 3. Mount controllers for _, c := range controllers { // 3a. Mount controller cPath := filepath.Join(root, "/sys/fs/cgroup", c) if err := os.MkdirAll(cPath, 0700); err != nil { return err } flags = syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV if err := syscall.Mount("cgroup", cPath, "cgroup", flags, c); err != nil { return fmt.Errorf("error mounting %q: %v", cPath, err) } // 3b. Check if we're running from a unit to know which subcgroup // directories to mount read-write subcgroupPath := filepath.Join(cPath, subcgroup) // 3c. Create cgroup directories and mount the files we need over // themselves so they stay read-write for _, a := range appHashes { serviceName := types.ShortHash(a.String()) + ".service" appCgroup := filepath.Join(subcgroupPath, serviceName) if err := os.MkdirAll(appCgroup, 0755); err != nil { return err } for _, f := range getControllerRWFiles(c) { cgroupFilePath := filepath.Join(appCgroup, f) // the file may not be there if kernel doesn't support the // feature, skip it in that case if _, err := os.Stat(cgroupFilePath); os.IsNotExist(err) { continue } if err := syscall.Mount(cgroupFilePath, cgroupFilePath, "", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("error bind mounting %q: %v", cgroupFilePath, err) } } } // 3d. Re-mount controller read-only to prevent the container modifying host controllers flags = syscall.MS_BIND | syscall.MS_REMOUNT | syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_RDONLY if err := syscall.Mount(cPath, cPath, "", flags, ""); err != nil { return fmt.Errorf("error remounting RO %q: %v", cPath, err) } } // 4. Create symlinks for combined controllers symlinks := getControllerSymlinks(cgroups) for ln, tgt := range symlinks { lnPath := filepath.Join(cgroupTmpfs, ln) if err := os.Symlink(tgt, lnPath); err != nil { return fmt.Errorf("error creating symlink: %v", err) } } // 5. Create systemd cgroup directory // We're letting systemd-nspawn create the systemd cgroup but later we're // remounting /sys/fs/cgroup read-only so we create the directory here. if err := os.MkdirAll(filepath.Join(cgroupTmpfs, "systemd"), 0700); err != nil { return err } // 6. Bind-mount cgroup filesystem read-only flags = syscall.MS_BIND | syscall.MS_REMOUNT | syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_RDONLY if err := syscall.Mount(cgroupTmpfs, cgroupTmpfs, "", flags, ""); err != nil { return fmt.Errorf("error remounting RO %q: %v", cgroupTmpfs, err) } return nil }
// RelEnvFilePath returns the path to the environment file for the given imageID // relative to the pod's root func RelEnvFilePath(imageID types.Hash) string { return filepath.Join(envDir, types.ShortHash(imageID.String())) }
// ServiceUnitName returns a systemd service unit name for the given imageID func ServiceUnitName(imageID types.Hash) string { return types.ShortHash(imageID.String()) + ".service" }