diff options
-rw-r--r-- | cmd/podman/commit.go | 93 | ||||
-rw-r--r-- | cmd/podman/import.go | 2 | ||||
-rw-r--r-- | cmd/podman/load.go | 2 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | completions/bash/podman | 28 | ||||
-rw-r--r-- | docs/podman-commit.1.md | 96 | ||||
-rw-r--r-- | libpod/container.go | 36 | ||||
-rw-r--r-- | libpod/image_inspect.go | 2 | ||||
-rw-r--r-- | test/podman_commit.bats | 122 |
9 files changed, 377 insertions, 5 deletions
diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go new file mode 100644 index 000000000..5d37458ea --- /dev/null +++ b/cmd/podman/commit.go @@ -0,0 +1,93 @@ +package main + +import ( + "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod" + "github.com/urfave/cli" +) + +var ( + commitFlags = []cli.Flag{ + cli.StringSliceFlag{ + Name: "change, c", + Usage: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR", + }, + cli.StringFlag{ + Name: "message, m", + Usage: "Set commit message for imported image", + }, + cli.StringFlag{ + Name: "author, a", + Usage: "Set the author for the image comitted", + }, + cli.BoolTFlag{ + Name: "pause, p", + Usage: "Pause container during commit", + }, + } + commitDescription = `Create an image from a container's changes. + Optionally tag the image created, set the author with the --author flag, + set the commit message with the --message flag, + and make changes to the instructions with the --change flag.` + commitCommand = cli.Command{ + Name: "commit", + Usage: "Create new image based on the changed container", + Description: commitDescription, + Flags: commitFlags, + Action: commitCmd, + ArgsUsage: "CONTAINER [REPOSITORY[:TAG]]", + } +) + +func commitCmd(c *cli.Context) error { + if err := validateFlags(c, commitFlags); err != nil { + return err + } + + runtime, err := getRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + var opts libpod.CopyOptions + var container string + args := c.Args() + switch len(args) { + case 0: + return errors.Errorf("need to give container name or id") + case 1: + container = args[0] + case 2: + container = args[0] + opts.Reference = args[1] + default: + return errors.Errorf("too many arguments. Usage CONTAINER [REFERENCE]") + } + + changes := v1.ImageConfig{} + if c.IsSet("change") { + changes, err = getImageConfig(c.StringSlice("change")) + if err != nil { + return errors.Wrapf(err, "error adding config changes to image %q", container) + } + } + + history := []v1.History{ + {Comment: c.String("message")}, + } + + config := v1.Image{ + Config: changes, + History: history, + Author: c.String("author"), + } + opts.ImageConfig = config + + ctr, err := runtime.LookupContainer(container) + if err != nil { + return errors.Wrapf(err, "error looking up container %q", container) + } + return ctr.Commit(c.BoolT("pause"), opts) +} diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 2e8702c3d..7b380b500 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -28,7 +28,7 @@ var ( } importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). Note remote tar balls can be specified, via web address. - Optionally tag the image. You can specify the Dockerfile instructions using the --change option. + Optionally tag the image. You can specify the instructions using the --change option. ` importCommand = cli.Command{ Name: "import", diff --git a/cmd/podman/load.go b/cmd/podman/load.go index 2f3d9c56d..ee8a79756 100644 --- a/cmd/podman/load.go +++ b/cmd/podman/load.go @@ -73,8 +73,8 @@ func loadCmd(c *cli.Context) error { if err != nil { return errors.Errorf("error creating file %v", err) } - defer outFile.Close() defer os.Remove(outFile.Name()) + defer outFile.Close() inFile, err := os.OpenFile(input, 0, 0666) if err != nil { diff --git a/cmd/podman/main.go b/cmd/podman/main.go index cc6d26992..d3cb86798 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -35,6 +35,7 @@ func main() { app.Commands = []cli.Command{ attachCommand, + commitCommand, createCommand, diffCommand, execCommand, diff --git a/completions/bash/podman b/completions/bash/podman index ecf752de9..fd7fa99e2 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -657,6 +657,33 @@ _podman_attach() { _complete_ "$options_with_args" "$boolean_options" } +_podman_commit() { + local options_with_args=" + --author + -a + --change + -c + --message + -m + " + local boolean_options=" + --help + -h + --pause + -p + " + _complete_ "$options_with_args" "$boolean_options" + + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_list_images + ;; + esac +} + _podman_diff() { local options_with_args=" --format @@ -1454,6 +1481,7 @@ _podman_podman() { " commands=" attach + commit create diff exec diff --git a/docs/podman-commit.1.md b/docs/podman-commit.1.md new file mode 100644 index 000000000..10680fcb8 --- /dev/null +++ b/docs/podman-commit.1.md @@ -0,0 +1,96 @@ +% podman(1) podman-commit - Tool to create new image based on the changed container +% Urvashi Mohnani +# podman-commit "1" "December 2017" "podman" + +## NAME +podman-commit - Create new image based on the changed container + +## SYNOPSIS +**podman commit** +**TARBALL** +[**--author**|**-a**] +[**--change**|**-c**] +[**--message**|**-m**] +[**--help**|**-h**] + +## DESCRIPTION +**podman commit** creates an image based on a changed container. The author of the +image can be set using the **--author** flag. Various image instructions can be +configured with the **--change** flag and a commit message can be set using the +**--message** flag. The container and its processes are paused while the image is +committed. This minimizes the likelihood of data corruption when creating the new +image. If this is not desired, the **--pause** flag can be set to false. + +**podman [GLOBAL OPTIONS]** + +**podman commit [GLOBAL OPTIONS]** + +**podman commit [OPTIONS] CONTAINER** + +## OPTIONS + +**--author, -a** +Set the author for the committed image + +**--change, -c** +Apply the following possible instructions to the created image: +**CMD** | **ENTRYPOINT** | **ENV** | **EXPOSE** | **LABEL** | **STOPSIGNAL** | **USER** | **VOLUME** | **WORKDIR** +Can be set multiple times + +**--message, -m** +Set commit message for committed image + +**--pause, -p** +Pause the container when creating an image + +## EXAMPLES + +``` +# podman commit --change CMD=/bin/bash --change ENTRYPOINT=/bin/sh --change LABEL=blue=image reverent_golick image-commited +Getting image source signatures +Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86 + 25.80 MB / 25.80 MB [======================================================] 0s +Copying config sha256:c16a6d30f3782288ec4e7521c754acc29d37155629cb39149756f486dae2d4cd + 448 B / 448 B [============================================================] 0s +Writing manifest to image destination +Storing signatures +``` + +``` +# podman commit --message "commiting container to image" reverent_golick image-commited +Getting image source signatures +Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86 + 25.80 MB / 25.80 MB [======================================================] 0s +Copying config sha256:af376cdda5c0ac1d9592bf56567253d203f8de6a8edf356c683a645d75221540 + 376 B / 376 B [============================================================] 0s +Writing manifest to image destination +Storing signatures +``` + +``` +# podman commit --author "firstName lastName" reverent_golick +Getting image source signatures +Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86 + 25.80 MB / 25.80 MB [======================================================] 0s +Copying config sha256:d61387b4d5edf65edee5353e2340783703074ffeaaac529cde97a8357eea7645 + 378 B / 378 B [============================================================] 0s +Writing manifest to image destination +Storing signatures +``` + +``` +# podman commit --pause=false reverent_golick image-commited +Getting image source signatures +Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86 + 25.80 MB / 25.80 MB [======================================================] 0s +Copying config sha256:5813fe8a3b18696089fd09957a12e88bda43dc1745b5240879ffffe93240d29a + 419 B / 419 B [============================================================] 0s +Writing manifest to image destination +Storing signatures +``` + +## SEE ALSO +podman(1), podman-run(1), podman-create(1) + +## HISTORY +December 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com> diff --git a/libpod/container.go b/libpod/container.go index 15ad1f49a..dc22c9c61 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -890,6 +890,10 @@ func (c *Container) Export(path string) error { return err } + return c.export(path) +} + +func (c *Container) export(path string) error { mountPoint := c.state.Mountpoint if !c.state.Mounted { mount, err := c.runtime.store.Mount(c.ID(), c.config.MountLabel) @@ -965,8 +969,36 @@ func (c *Container) Inspect(size bool) (*ContainerInspectData, error) { // Commit commits the changes between a container and its image, creating a new // image -func (c *Container) Commit() (*storage.Image, error) { - return nil, ErrNotImplemented +func (c *Container) Commit(pause bool, options CopyOptions) error { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + + if c.state.State == ContainerStateRunning && pause { + if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { + return errors.Wrapf(err, "error pausing container %q", c.ID()) + } + defer func() { + if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { + logrus.Errorf("error unpausing container %q: %v", c.ID(), err) + } + }() + } + + tempFile, err := ioutil.TempFile(c.runtime.config.TmpDir, "podman-commit") + if err != nil { + return errors.Wrapf(err, "error creating temp file") + } + defer os.Remove(tempFile.Name()) + defer tempFile.Close() + + if err := c.export(tempFile.Name()); err != nil { + return err + } + return c.runtime.ImportImage(tempFile.Name(), options) } // Wait blocks on a container to exit and returns its exit code diff --git a/libpod/image_inspect.go b/libpod/image_inspect.go index a08665434..3d904e64b 100644 --- a/libpod/image_inspect.go +++ b/libpod/image_inspect.go @@ -47,7 +47,7 @@ func getImageData(img storage.Image, imgRef types.Image, size int64, driver *dri RepoDigests: repoDigests, Comment: ociv1Img.History[0].Comment, Created: ociv1Img.Created, - Author: ociv1Img.History[0].Author, + Author: ociv1Img.Author, Architecture: ociv1Img.Architecture, Os: ociv1Img.OS, Config: &ociv1Img.Config, diff --git a/test/podman_commit.bats b/test/podman_commit.bats new file mode 100644 index 000000000..46eaf32a4 --- /dev/null +++ b/test/podman_commit.bats @@ -0,0 +1,122 @@ +#!/usr/bin/env bats + +load helpers + +IMAGE="redis:alpine" + +function teardown() { + cleanup_test +} + +function setup() { + prepare_network_conf + copy_images +} + +@test "podman commit default" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit my_ctr image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} images | grep image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "podman commit with message flag" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --message testing-commit my_ctr image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect image-committed | grep testing-commit" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "podman commit with author flag" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --author author-name my_ctr image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect image-committed | grep author-name" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "podman commit with change flag" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --change LABEL=image=blue my_ctr image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect image-committed | grep blue" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "podman commit with pause flag" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --pause=false my_ctr image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} images | grep image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "podman commit non-running container" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} create --name my_ctr ${FEDORA_MINIMAL} ls" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit my_ctr image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} images | grep image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm my_ctr" + echo "$output" + [ "$status" -eq 0 ] +} |