diff options
-rw-r--r-- | docs/source/markdown/podman-create.1.md | 1 | ||||
-rw-r--r-- | docs/source/markdown/podman-run.1.md | 9 | ||||
-rw-r--r-- | libpod/container.go | 4 | ||||
-rw-r--r-- | libpod/options.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 73 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 6 | ||||
-rw-r--r-- | pkg/bindings/images/images.go | 3 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 89 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 4 | ||||
-rw-r--r-- | test/e2e/create_test.go | 14 |
10 files changed, 150 insertions, 55 deletions
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 1da9d72e6..03ac8642f 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -676,6 +676,7 @@ Valid values are: - `no` : Do not restart containers on exit - `on-failure[:max_retries]` : Restart containers when they exit with a non-0 exit code, retrying indefinitely or until the optional max_retries count is hit - `always` : Restart containers when they exit, regardless of status, retrying indefinitely +- `unless-stopped` : Identical to **always** Please note that restart will not restart containers after a system reboot. If this functionality is required in your environment, you can invoke Podman from a systemd unit file, or create an init script for whichever init system is in use. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 3e1ade047..539e62819 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -682,11 +682,10 @@ Restart policy will not take effect if a container is stopped via the **podman k Valid _policy_ values are: -- **no**: Do not restart containers on exit; -- **on-failure**[:*max_retries*]: Restart containers when they exit -with a non-zero exit code, retrying indefinitely or until the optional -*max_retries* count is hit; -- **always**: Restart containers when they exit, regardless of status, retrying indefinitely. +- `no` : Do not restart containers on exit +- `on-failure[:max_retries]` : Restart containers when they exit with a non-zero exit code, retrying indefinitely or until the optional *max_retries* count is hit +- `always` : Restart containers when they exit, regardless of status, retrying indefinitely +- `unless-stopped` : Identical to **always** Please note that restart will not restart containers after a system reboot. If this functionality is required in your environment, you can invoke Podman from a **systemd.unit**(5) file, or create an init script for whichever init system is in use. diff --git a/libpod/container.go b/libpod/container.go index d4a779b13..20702903e 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -97,6 +97,10 @@ const ( // RestartPolicyOnFailure restarts the container on non-0 exit code, // with an optional maximum number of retries. RestartPolicyOnFailure = "on-failure" + // RestartPolicyUnlessStopped unconditionally restarts unless stopped + // by the user. It is identical to Always except with respect to + // handling of system restart, which Podman does not yet support. + RestartPolicyUnlessStopped = "unless-stopped" ) // Container is a single OCI container. diff --git a/libpod/options.go b/libpod/options.go index 5a0f60093..e1c4ddf06 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1285,7 +1285,7 @@ func WithRestartPolicy(policy string) CtrCreateOption { } switch policy { - case RestartPolicyNone, RestartPolicyNo, RestartPolicyOnFailure, RestartPolicyAlways: + case RestartPolicyNone, RestartPolicyNo, RestartPolicyOnFailure, RestartPolicyAlways, RestartPolicyUnlessStopped: ctr.config.RestartPolicy = policy default: return errors.Wrapf(define.ErrInvalidArg, "%q is not a valid restart policy", policy) diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 913994f46..f967acf32 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -48,34 +48,34 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { defer os.RemoveAll(anchorDir) query := struct { - Dockerfile string `schema:"dockerfile"` - Tag string `schema:"t"` - ExtraHosts string `schema:"extrahosts"` - Remote string `schema:"remote"` - Quiet bool `schema:"q"` - NoCache bool `schema:"nocache"` - CacheFrom string `schema:"cachefrom"` - Pull bool `schema:"pull"` - Rm bool `schema:"rm"` - ForceRm bool `schema:"forcerm"` - Memory int64 `schema:"memory"` - MemSwap int64 `schema:"memswap"` - CpuShares uint64 `schema:"cpushares"` //nolint - CpuSetCpus string `schema:"cpusetcpus"` //nolint - CpuPeriod uint64 `schema:"cpuperiod"` //nolint - CpuQuota int64 `schema:"cpuquota"` //nolint - BuildArgs string `schema:"buildargs"` - ShmSize int `schema:"shmsize"` - Squash bool `schema:"squash"` - Labels string `schema:"labels"` - NetworkMode string `schema:"networkmode"` - Platform string `schema:"platform"` - Target string `schema:"target"` - Outputs string `schema:"outputs"` - Registry string `schema:"registry"` + Dockerfile string `schema:"dockerfile"` + Tag []string `schema:"t"` + ExtraHosts string `schema:"extrahosts"` + Remote string `schema:"remote"` + Quiet bool `schema:"q"` + NoCache bool `schema:"nocache"` + CacheFrom string `schema:"cachefrom"` + Pull bool `schema:"pull"` + Rm bool `schema:"rm"` + ForceRm bool `schema:"forcerm"` + Memory int64 `schema:"memory"` + MemSwap int64 `schema:"memswap"` + CpuShares uint64 `schema:"cpushares"` //nolint + CpuSetCpus string `schema:"cpusetcpus"` //nolint + CpuPeriod uint64 `schema:"cpuperiod"` //nolint + CpuQuota int64 `schema:"cpuquota"` //nolint + BuildArgs string `schema:"buildargs"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + Labels string `schema:"labels"` + NetworkMode string `schema:"networkmode"` + Platform string `schema:"platform"` + Target string `schema:"target"` + Outputs string `schema:"outputs"` + Registry string `schema:"registry"` }{ Dockerfile: "Dockerfile", - Tag: "", + Tag: []string{}, ExtraHosts: "", Remote: "", Quiet: false, @@ -107,20 +107,19 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } var ( - // Tag is the name with optional tag... - name = query.Tag - tag = "latest" + output string + additionalNames []string ) - if strings.Contains(query.Tag, ":") { - tokens := strings.SplitN(query.Tag, ":", 2) - name = tokens[0] - tag = tokens[1] + if len(query.Tag) > 0 { + output = query.Tag[0] + } + if len(query.Tag) > 1 { + additionalNames = query.Tag[1:] } if _, found := r.URL.Query()["target"]; found { - name = query.Target + output = query.Target } - var buildArgs = map[string]string{} if _, found := r.URL.Query()["buildargs"]; found { if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { @@ -168,8 +167,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { TransientMounts: nil, Compression: archive.Gzip, Args: buildArgs, - Output: name, - AdditionalTags: []string{tag}, + Output: output, + AdditionalTags: additionalNames, Log: func(format string, args ...interface{}) { buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) }, diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 83584d0f3..754cb1b75 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -410,7 +410,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // swagger:operation POST /build compat buildImage // --- // tags: - // - images + // - images (compat) // summary: Create image // description: Build an image from the given Dockerfile(s) // parameters: @@ -425,7 +425,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: t // type: string // default: latest - // description: A name and optional tag to apply to the image in the `name:tag` format. + // description: A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default latest value is assumed. You can provide several t parameters. // - in: query // name: extrahosts // type: string @@ -1211,7 +1211,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: t // type: string // default: latest - // description: A name and optional tag to apply to the image in the `name:tag` format. + // description: A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default latest value is assumed. You can provide several t parameters. // - in: query // name: extrahosts // type: string diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 9cb6a0ac5..bc2d116f3 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -229,6 +229,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if t := options.Output; len(t) > 0 { params.Set("t", t) } + for _, tag := range options.AdditionalTags { + params.Add("t", tag) + } // TODO Remote, Quiet if options.NoCache { params.Set("nocache", "1") diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 9ddc5f1a9..35189cb0a 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -1,10 +1,13 @@ package tunnel import ( + "archive/tar" + "bytes" "context" + "io" "io/ioutil" "os" - "path" + "path/filepath" "strings" "github.com/containers/common/pkg/config" @@ -16,6 +19,7 @@ import ( utils2 "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) { @@ -276,14 +280,27 @@ func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { } func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) { - if len(containerFiles) > 1 { - return nil, errors.New("something") + var tarReader io.Reader + tarfile, err := archive.Tar(opts.ContextDirectory, 0) + if err != nil { + return nil, err } - tarfile, err := archive.Tar(path.Base(containerFiles[0]), 0) + tarReader = tarfile + cwd, err := os.Getwd() if err != nil { return nil, err } - return images.Build(ir.ClientCxt, containerFiles, opts, tarfile) + if cwd != opts.ContextDirectory { + fn := func(h *tar.Header, r io.Reader) (data []byte, update bool, skip bool, err error) { + h.Name = filepath.Join(filepath.Base(opts.ContextDirectory), h.Name) + return nil, false, false, nil + } + tarReader, err = transformArchive(tarfile, false, fn) + if err != nil { + return nil, err + } + } + return images.Build(ir.ClientCxt, containerFiles, opts, tarReader) } func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { @@ -297,3 +314,65 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { return nil, errors.New("not implemented yet") } + +// Sourced from openshift image builder + +// TransformFileFunc is given a chance to transform an arbitrary input file. +type TransformFileFunc func(h *tar.Header, r io.Reader) (data []byte, update bool, skip bool, err error) + +// filterArchive transforms the provided input archive to a new archive, +// giving the fn a chance to transform arbitrary files. +func filterArchive(r io.Reader, w io.Writer, fn TransformFileFunc) error { + tr := tar.NewReader(r) + tw := tar.NewWriter(w) + + var body io.Reader = tr + + for { + h, err := tr.Next() + if err == io.EOF { + return tw.Close() + } + if err != nil { + return err + } + + name := h.Name + data, ok, skip, err := fn(h, tr) + logrus.Debugf("Transform %q -> %q: data=%t ok=%t skip=%t err=%v", name, h.Name, data != nil, ok, skip, err) + if err != nil { + return err + } + if skip { + continue + } + if ok { + h.Size = int64(len(data)) + body = bytes.NewBuffer(data) + } + if err := tw.WriteHeader(h); err != nil { + return err + } + if _, err := io.Copy(tw, body); err != nil { + return err + } + } +} + +func transformArchive(r io.Reader, compressed bool, fn TransformFileFunc) (io.Reader, error) { + var cwe error + pr, pw := io.Pipe() + go func() { + if compressed { + in, err := archive.DecompressStream(r) + if err != nil { + cwe = pw.CloseWithError(err) + return + } + r = in + } + err := filterArchive(r, pw, fn) + cwe = pw.CloseWithError(err) + }() + return pr, cwe +} diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 869601e93..2f7100e7e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -7,7 +7,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" @@ -251,9 +250,6 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. // Default used if not overridden on command line if s.RestartPolicy != "" { - if s.RestartPolicy == "unless-stopped" { - return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") - } if s.RestartRetries != nil { options = append(options, libpod.WithRestartRetries(*s.RestartRetries)) } diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 822e470f2..52ce0b46a 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -401,6 +401,20 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Not(Equal(0))) }) + It("podman create with --restart-policy unless-stopped", func() { + ctrName := "testctr" + unlessStopped := "unless-stopped" + session := podmanTest.Podman([]string{"create", "-t", "--restart", unlessStopped, "--name", ctrName, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect.WaitWithDefaultTimeout() + data := inspect.InspectContainerToJSON() + Expect(len(data)).To(Equal(1)) + Expect(data[0].HostConfig.RestartPolicy.Name).To(Equal(unlessStopped)) + }) + It("podman create with -m 1000000 sets swap to 2000000", func() { numMem := 1000000 ctrName := "testCtr" |