Skip to content

Commit

Permalink
podman: add support for splitting imagestore
Browse files Browse the repository at this point in the history
Add support for `--imagestore` in podman which allows users to split the filesystem of containers vs image store, imagestore if configured will pull images in image storage instead of the graphRoot while keeping the other parts still in the originally configured graphRoot.

This is an implementation of
containers/storage#1549 in podman.

Signed-off-by: Aditya R <[email protected]>
  • Loading branch information
flouthoc committed Jun 17, 2023
1 parent 719e322 commit 3829fbd
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cmd/podman/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) {
pFlags.StringVar(&podmanConfig.Runroot, runrootFlagName, "", "Path to the 'run directory' where all state information is stored")
_ = cmd.RegisterFlagCompletionFunc(runrootFlagName, completion.AutocompleteDefault)

imageStoreFlagName := "imagestore"
pFlags.StringVar(&podmanConfig.ImageStore, imageStoreFlagName, "", "Path to the `image store`, different from `graph root`, use this to split storing the image into a separate `image store`, see `man containers-storage.conf` for details")
_ = cmd.RegisterFlagCompletionFunc(imageStoreFlagName, completion.AutocompleteDefault)

pFlags.BoolVar(&podmanConfig.TransientStore, "transient-store", false, "Enable transient container storage")

runtimeFlagName := "runtime"
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ Identity value resolution precedence:
- `containers.conf`
Remote connections use local containers.conf for default.

#### **--imagestore**=*path*

Path of the imagestore where images are stored. By default, the storage library stores all the images in the graphroot but if an imagestore is provided, then the storage library will store newly pulled images in the provided imagestore and keep using the graphroot for everything else. If the user is using the overlay driver, then the images which were already part of the graphroot will still be accessible.

This will override *imagestore* option in `containers-storage.conf(5)`, refer to `containers-storage.conf(5)` for more details.

#### **--log-level**=*level*

Log messages at and above specified level: debug, info, warn, error, fatal or panic (default: "warn")
Expand Down
12 changes: 12 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ func WithTransientStore(transientStore bool) RuntimeOption {
}
}

func WithImageStore(imageStore string) RuntimeOption {
return func(rt *Runtime) error {
if rt.valid {
return define.ErrRuntimeFinalized
}

rt.storageConfig.ImageStore = imageStore

return nil
}
}

// WithSignaturePolicy specifies the path of a file which decides how trust is
// managed for images we've pulled.
// If this is not specified, the system default configuration will be used
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type PodmanConfig struct {
URI string // URI to RESTful API Service

Runroot string
ImageStore string
StorageDriver string
StorageOpts []string
SSHMode string
Expand Down
4 changes: 4 additions & 0 deletions pkg/domain/infra/runtime_libpod.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
storageSet = true
storageOpts.RunRoot = cfg.Runroot
}
if fs.Changed("imagestore") {
storageOpts.ImageStore = cfg.ImageStore
options = append(options, libpod.WithImageStore(cfg.ImageStore))
}
if len(storageOpts.RunRoot) > 50 {
return nil, errors.New("the specified runroot is longer than 50 characters")
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/specgenutil/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *conf
"--db-backend", config.Engine.DBBackend,
fmt.Sprintf("--transient-store=%t", storageConfig.TransientStore),
}
if storageConfig.ImageStore != "" {
command = append(command, []string{"--imagestore", storageConfig.ImageStore}...)
}
if config.Engine.OCIRuntime != "" {
command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...)
}
Expand Down
67 changes: 67 additions & 0 deletions test/e2e/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,73 @@ var _ = Describe("Podman pull", func() {
Expect(session).Should(Exit(0))
})

It("podman pull and run on split imagestore", func() {
SkipIfRemote("podman-remote does not support setting external imagestore")
imgName := "splitstoretest"

// Make alpine write-able
session := podmanTest.Podman([]string{"build", "--pull=never", "--tag", imgName, "build/basicalpine"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

tmpDir := filepath.Join(podmanTest.TempDir, "splitstore")
outfile := filepath.Join(podmanTest.TempDir, "image.tar")

save := podmanTest.Podman([]string{"save", "-o", outfile, "--format", "oci-archive", imgName})
save.WaitWithDefaultTimeout()
Expect(save).Should(Exit(0))

rmi := podmanTest.Podman([]string{"rmi", imgName})
rmi.WaitWithDefaultTimeout()
Expect(rmi).Should(Exit(0))

// load to splitstore
result := podmanTest.Podman([]string{"load", "--imagestore", tmpDir, "-q", "-i", outfile})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))

// tag busybox to busybox-test in graphroot since we can delete readonly busybox
session = podmanTest.Podman([]string{"tag", "quay.io/libpod/busybox:latest", "busybox-test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

session = podmanTest.Podman([]string{"images", "--imagestore", tmpDir})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring(imgName))
Expect(session.OutputToString()).To(ContainSubstring("busybox-test"))

// Test deleting image in graphroot even when `--imagestore` is set
session = podmanTest.Podman([]string{"rmi", "--imagestore", tmpDir, "busybox-test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

// Images without --imagestore should not contain alpine
session = podmanTest.Podman([]string{"images"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(Not(ContainSubstring(imgName)))

// Set `imagestore` in `storage.conf` and container should run.
configPath := filepath.Join(podmanTest.TempDir, ".config", "containers", "storage.conf")
os.Setenv("CONTAINERS_STORAGE_CONF", configPath)
defer func() {
os.Unsetenv("CONTAINERS_STORAGE_CONF")
}()

err = os.MkdirAll(filepath.Dir(configPath), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
storageConf := []byte(fmt.Sprintf("[storage]\nimagestore=\"%s\"", tmpDir))
err = os.WriteFile(configPath, storageConf, os.ModePerm)
Expect(err).ToNot(HaveOccurred())

session = podmanTest.Podman([]string{"run", "--name", "test", "--rm",
imgName, "echo", "helloworld"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("helloworld"))
})

It("podman pull by digest", func() {
session := podmanTest.Podman([]string{"pull", "quay.io/libpod/testdigest_v2s2@sha256:755f4d90b3716e2bf57060d249e2cd61c9ac089b1233465c5c2cb2d7ee550fdb"})
session.WaitWithDefaultTimeout()
Expand Down

0 comments on commit 3829fbd

Please sign in to comment.