diff options
34 files changed, 633 insertions, 92 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index e79c5c20b..921cd5a71 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -373,6 +373,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "read-only-tmpfs", true, "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", ) + createFlags.BoolVar( + &cf.Replace, + "replace", false, + `If a container with the same name exists, replace it`, + ) createFlags.StringVar( &cf.Restart, "restart", "", diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 98dc6744c..49052704e 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -76,6 +76,7 @@ type ContainerCLIOpts struct { ReadOnly bool ReadOnlyTmpFS bool Restart string + Replace bool Rm bool RootFS bool SecurityOpt []string diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index ed09585ba..6269ec781 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -122,6 +122,12 @@ func create(cmd *cobra.Command, args []string) error { return err } + if cliVals.Replace { + if err := replaceContainer(cliVals.Name); err != nil { + return err + } + } + report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s) if err != nil { return err @@ -138,6 +144,17 @@ func create(cmd *cobra.Command, args []string) error { return nil } +func replaceContainer(name string) error { + if len(name) == 0 { + return errors.New("cannot replace container without --name being set") + } + rmOptions := entities.RmOptions{ + Force: true, // force stop & removal + Ignore: true, // ignore errors when a container doesn't exit + } + return removeContainers([]string{name}, rmOptions, false) +} + func createInit(c *cobra.Command) error { if c.Flag("privileged").Changed && c.Flag("security-opt").Changed { logrus.Warn("setting security options with --privileged has no effect") diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index b25473a8d..22d6d59b4 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -87,6 +87,14 @@ func init() { } func rm(cmd *cobra.Command, args []string) error { + return removeContainers(args, rmOptions, true) +} + +// removeContainers will remove the specified containers (names or IDs). +// Allows for sharing removal logic across commands. If setExit is set, +// removeContainers will set the exit code according to the `podman-rm` man +// page. +func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit bool) error { var ( errs utils.OutputErrors ) @@ -96,9 +104,9 @@ func rm(cmd *cobra.Command, args []string) error { return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile") } } - responses, err := registry.ContainerEngine().ContainerRm(context.Background(), args, rmOptions) + responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions) if err != nil { - if len(args) < 2 { + if setExit && len(namesOrIDs) < 2 { setExitCode(err) } return err @@ -109,7 +117,9 @@ func rm(cmd *cobra.Command, args []string) error { if errors.Cause(err) == define.ErrWillDeadlock { logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve") } - setExitCode(r.Err) + if setExit { + setExitCode(r.Err) + } errs = append(errs, r.Err) } else { fmt.Println(r.Id) diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 8a02c63c0..b9c196b64 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -129,6 +129,12 @@ func run(cmd *cobra.Command, args []string) error { } } + if cliVals.Replace { + if err := replaceContainer(cliVals.Name); err != nil { + return err + } + } + // If -i is not set, clear stdin if !cliVals.Interactive { runOpts.InputStream = nil diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 51b7a7d52..835a62359 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -39,6 +39,7 @@ var ( createOptions entities.PodCreateOptions labels, labelFile []string podIDFile string + replace bool share string ) @@ -61,6 +62,7 @@ func init() { flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") + flags.BoolVar(&replace, "replace", false, "If a pod with the same exists, replace it") flags.StringVar(&share, "share", specgen.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") flags.SetNormalizeFunc(aliasNetworkFlag) } @@ -147,6 +149,12 @@ func create(cmd *cobra.Command, args []string) error { } } + if replace { + if err := replacePod(createOptions.Name); err != nil { + return err + } + } + response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions) if err != nil { return err @@ -159,3 +167,14 @@ func create(cmd *cobra.Command, args []string) error { fmt.Println(response.Id) return nil } + +func replacePod(name string) error { + if len(name) == 0 { + return errors.New("cannot replace pod without --name being set") + } + rmOptions := entities.PodRmOptions{ + Force: true, // stop and remove pod + Ignore: true, // ignore if pod doesn't exist + } + return removePods([]string{name}, rmOptions, false) +} diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index 8de0bce9e..ec8dae1d1 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -58,24 +58,30 @@ func init() { } func rm(cmd *cobra.Command, args []string) error { - var ( - errs utils.OutputErrors - ) - ids, err := common.ReadPodIDFiles(rmOptions.PodIDFiles) if err != nil { return err } args = append(args, ids...) + return removePods(args, rmOptions.PodRmOptions, true) +} - responses, err := registry.ContainerEngine().PodRm(context.Background(), args, rmOptions.PodRmOptions) +// removePods removes the specified pods (names or IDs). Allows for sharing +// pod-removal logic across commands. +func removePods(namesOrIDs []string, rmOptions entities.PodRmOptions, printIDs bool) error { + var errs utils.OutputErrors + + responses, err := registry.ContainerEngine().PodRm(context.Background(), namesOrIDs, rmOptions) if err != nil { return err } + // in the cli, first we print out all the successful attempts for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + if printIDs { + fmt.Println(r.Id) + } } else { errs = append(errs, r.Err) } diff --git a/completions/bash/podman b/completions/bash/podman index 6dbe645fe..5e990ec41 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -3118,6 +3118,7 @@ _podman_pod_create() { --help -h --infra + --replace " _complete_ "$options_with_args" "$boolean_options" } diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index dbc835920..1da9d72e6 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -662,6 +662,10 @@ its root filesystem mounted as read only prohibiting any writes. If container is running in --read-only mode, then mount a read-write tmpfs on /run, /tmp, and /var/tmp. The default is *true* +**--replace**=**true**|**false** + +If another container with the same name already exists, replace and remove it. The default is **false**. + **--restart**=*policy* Restart policy to follow when containers exit. diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index de6b600f0..1401400bb 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -102,6 +102,10 @@ Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPO NOTE: This cannot be modified once the pod is created. +**--replace**=**true**|**false** + +If another pod with the same name already exists, replace and remove it. The default is **false**. + **--share**=*namespace* A comma delimited list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 22f7cae09..3e1ade047 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -671,6 +671,10 @@ its root filesystem mounted as read only prohibiting any writes. If container is running in **--read-only** mode, then mount a read-write tmpfs on _/run_, _/tmp_, and _/var/tmp_. The default is **true**. +**--replace**=**true**|**false** + +If another container with the same name already exists, replace and remove it. The default is **false**. + **--restart**=*policy* Restart policy to follow when containers exit. diff --git a/libpod/container_log.go b/libpod/container_log.go index c3a84d048..39c395fe6 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -2,6 +2,7 @@ package libpod import ( "os" + "time" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" @@ -72,5 +73,27 @@ func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *l } options.WaitGroup.Done() }() + // Check if container is still running or paused + go func() { + if options.Follow { + for { + state, err := c.State() + if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { + logrus.Error(err) + break + } else if err != nil { + break + } + if state != define.ContainerStateRunning && state != define.ContainerStatePaused { + err := t.Stop() + if err != nil { + logrus.Error(err) + } + break + } + time.Sleep(1 * time.Second) + } + } + }() return nil } diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 6cc766a38..399c104e9 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -36,6 +36,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { if hdr[0] != "application/x-tar" { utils.BadRequest(w, "Content-Type", hdr[0], fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) + return } } @@ -260,8 +261,7 @@ func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) { r.Body.Close() if err != nil { - utils.InternalServerError(w, - fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)) + return "", fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI) } _, _ = tarBall.Seek(0, 0) diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 79aeff2f8..c1e84ab5a 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -247,6 +247,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI if err != nil { return nil, err } + // TODO the rest of these still need wiring! config := dockerContainer.Config{ // Hostname: "", @@ -261,17 +262,17 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI // StdinOnce: false, Env: info.Config.Env, Cmd: info.Config.Cmd, - // Healthcheck: nil, + //Healthcheck: l.ImageData.HealthCheck, // ArgsEscaped: false, // Image: "", - // Volumes: nil, - // WorkingDir: "", - // Entrypoint: nil, + Volumes: info.Config.Volumes, + WorkingDir: info.Config.WorkingDir, + Entrypoint: info.Config.Entrypoint, // NetworkDisabled: false, // MacAddress: "", - // OnBuild: nil, - Labels: info.Labels, - // StopSignal: "", + //OnBuild: info.Config.OnBuild, + Labels: info.Labels, + StopSignal: info.Config.StopSignal, // StopTimeout: nil, // Shell: nil, } @@ -285,7 +286,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Comment: info.Comment, Config: &config, Created: l.Created().Format(time.RFC3339Nano), - DockerVersion: "", + DockerVersion: info.Version, GraphDriver: docker.GraphDriverData{}, ID: fmt.Sprintf("sha256:%s", l.ID()), Metadata: docker.ImageMetadata{}, diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 3b94b10eb..b987f0442 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -739,4 +739,23 @@ var _ = Describe("Podman containers ", func() { //Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) }) + It("List containers with filters", func() { + var name = "top" + var name2 = "top2" + cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil) + Expect(err).To(BeNil()) + _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil) + Expect(err).To(BeNil()) + s := specgen.NewSpecGenerator(alpine.name, false) + s.Terminal = true + s.Command = []string{"date", "-R"} + _, err = containers.CreateWithSpec(bt.conn, s) + Expect(err).To(BeNil()) + // Validate list container with id filter + filters := make(map[string][]string) + filters["id"] = []string{cid} + c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil, nil) + Expect(err).To(BeNil()) + Expect(len(c)).To(Equal(1)) + }) }) diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go index 7567d5a70..82b07e2e1 100644 --- a/pkg/domain/infra/abi/cp.go +++ b/pkg/domain/infra/abi/cp.go @@ -260,7 +260,19 @@ func containerCopy(srcPath, destPath, src, dest string, idMappingOpts storage.ID if srcfi.IsDir() { logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) { - destPath = filepath.Join(destPath, filepath.Base(srcPath)) + srcPathBase := filepath.Base(srcPath) + if !isFromHostToCtr { + pathArr := strings.SplitN(src, ":", 2) + if len(pathArr) != 2 { + return errors.Errorf("invalid arguments %s, you must specify source path", src) + } + if pathArr[1] == "/" { + // If `srcPath` is the root directory of the container, + // `srcPath` will be `.../${sha256_ID}/merged/`, so do not join it + srcPathBase = "" + } + } + destPath = filepath.Join(destPath, srcPathBase) } if err = copyWithTar(srcPath, destPath); err != nil { return errors.Wrapf(err, "error copying %q to %q", srcPath, dest) diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index dced1a3da..4180022cb 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -207,24 +207,42 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst info.CreateCommand = filterPodFlags(info.CreateCommand) } - // Enforce detaching - // - // since we use systemd `Type=forking` service - // @see https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type= - // when we generated systemd service file with the --new param, - // `ExecStart` will have `/usr/bin/podman run ...` - // if `info.CreateCommand` has no `-d` or `--detach` param, - // podman will run the container in default attached mode, - // as a result, `systemd start` will wait the `podman run` command exit until failed with timeout error. + // Presence check for certain flags/options. hasDetachParam := false + hasNameParam := false + hasReplaceParam := false for _, p := range info.CreateCommand[index:] { - if p == "--detach" || p == "-d" { + switch p { + case "--detach", "-d": hasDetachParam = true + case "--name": + hasNameParam = true + case "--replace": + hasReplaceParam = true } } + if !hasDetachParam { + // Enforce detaching + // + // since we use systemd `Type=forking` service @see + // https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type= + // when we generated systemd service file with the + // --new param, `ExecStart` will have `/usr/bin/podman + // run ...` if `info.CreateCommand` has no `-d` or + // `--detach` param, podman will run the container in + // default attached mode, as a result, `systemd start` + // will wait the `podman run` command exit until failed + // with timeout error. startCommand = append(startCommand, "-d") } + if hasNameParam && !hasReplaceParam { + // Enforce --replace for named containers. This will + // make systemd units more robuts as it allows them to + // start after system crashes (see + // github.com/containers/libpod/issues/5485). + startCommand = append(startCommand, "--replace") + } startCommand = append(startCommand, info.CreateCommand[index:]...) info.ExecStartPre = "/usr/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}" diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 8365ecd7a..8d3ea1ca0 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -103,7 +103,7 @@ Type=forking [Install] WantedBy=multi-user.target default.target` - goodNameNew := `# jadda-jadda.service + goodWithNameAndGeneric := `# jadda-jadda.service # autogenerated by Podman CI [Unit] @@ -116,7 +116,30 @@ After=network-online.target Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id -ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +KillMode=none +Type=forking + +[Install] +WantedBy=multi-user.target default.target` + + goodWithExplicitShortDetachParam := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id PIDFile=%t/jadda-jadda.pid @@ -139,7 +162,7 @@ After=network-online.target Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id -ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --pod-id-file /tmp/pod-foobar.pod-id-file -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --pod-id-file /tmp/pod-foobar.pod-id-file --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id PIDFile=%t/jadda-jadda.pid @@ -148,6 +171,7 @@ Type=forking [Install] WantedBy=multi-user.target default.target` + goodNameNewDetach := `# jadda-jadda.service # autogenerated by Podman CI @@ -161,7 +185,7 @@ After=network-online.target Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id -ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --replace --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id PIDFile=%t/jadda-jadda.pid @@ -274,7 +298,7 @@ WantedBy=multi-user.target default.target` CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, EnvVariable: EnvVariable, }, - goodNameNew, + goodWithNameAndGeneric, true, false, }, @@ -290,7 +314,7 @@ WantedBy=multi-user.target default.target` CreateCommand: []string{"I'll get stripped", "container", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, EnvVariable: EnvVariable, }, - goodNameNew, + goodWithExplicitShortDetachParam, true, false, }, diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index 5cfd5ab0a..367b8381f 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -265,7 +265,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) if podCreateIndex == 0 { return "", errors.Errorf("pod does not appear to be created via `podman pod create`: %v", info.CreateCommand) } - podRootArgs = info.CreateCommand[1 : podCreateIndex-2] + podRootArgs = info.CreateCommand[0 : podCreateIndex-2] podCreateArgs = filterPodFlags(info.CreateCommand[podCreateIndex+1:]) } // We're hard-coding the first five arguments and append the @@ -277,6 +277,21 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) "--infra-conmon-pidfile", "{{.PIDFile}}", "--pod-id-file", "{{.PodIDFile}}"}...) + // Presence check for certain flags/options. + hasNameParam := false + hasReplaceParam := false + for _, p := range podCreateArgs { + switch p { + case "--name": + hasNameParam = true + case "--replace": + hasReplaceParam = true + } + } + if hasNameParam && !hasReplaceParam { + podCreateArgs = append(podCreateArgs, "--replace") + } + startCommand = append(startCommand, podCreateArgs...) info.ExecStartPre1 = "/usr/bin/rm -f {{.PIDFile}} {{.PodIDFile}}" diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index f6e225c35..f7ce33a30 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -36,7 +36,7 @@ func TestValidateRestartPolicyPod(t *testing.T) { } func TestCreatePodSystemdUnit(t *testing.T) { - podGoodName := `# pod-123abc.service + podGood := `# pod-123abc.service # autogenerated by Podman CI [Unit] @@ -59,10 +59,37 @@ Type=forking [Install] WantedBy=multi-user.target default.target` + podGoodNamedNew := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +ExecStartPre=/usr/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace +ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id +ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 +ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id +PIDFile=%t/pod-123abc.pid +KillMode=none +Type=forking + +[Install] +WantedBy=multi-user.target default.target` + tests := []struct { name string info podInfo want string + new bool wantErr bool }{ {"pod", @@ -76,7 +103,24 @@ WantedBy=multi-user.target default.target` PodmanVersion: "CI", RequiredServices: []string{"container-1", "container-2"}, }, - podGoodName, + podGood, + false, + false, + }, + {"pod --new", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + RestartPolicy: "on-failure", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "pod", "create", "--name", "foo"}, + }, + podGoodNamedNew, + true, false, }, } @@ -86,6 +130,7 @@ WantedBy=multi-user.target default.target` t.Run(tt.name, func(t *testing.T) { opts := entities.GenerateSystemdOptions{ Files: false, + New: test.new, } got, err := executePodTemplate(&test.info, opts) if (err != nil) != test.wantErr { diff --git a/test/dockerpy/containers.py b/test/dockerpy/containers.py deleted file mode 100644 index d70ec932c..000000000 --- a/test/dockerpy/containers.py +++ /dev/null @@ -1,46 +0,0 @@ - -import unittest -import docker -import requests -import os -from docker import Client -from . import constant -from . import common - -client = common.get_client() - -class TestContainers(unittest.TestCase): - - podman = None - - def setUp(self): - super().setUp() - common.run_top_container() - - def tearDown(self): - common.remove_all_containers() - common.remove_all_images() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - common.enable_sock(cls) - - @classmethod - def tearDownClass(cls): - common.terminate_connection(cls) - return super().tearDownClass() - - def test_inspect_container(self): - # Inspect bogus container - with self.assertRaises(requests.HTTPError): - client.inspect_container("dummy") - # Inspect valid container - container = client.inspect_container(constant.TOP) - self.assertIn(constant.TOP , container["Name"]) - - -if __name__ == '__main__': - # Setup temporary space - unittest.main() diff --git a/test/e2e/build/Dockerfile.test-cp-root-dir b/test/e2e/build/Dockerfile.test-cp-root-dir new file mode 100644 index 000000000..9f7de7c32 --- /dev/null +++ b/test/e2e/build/Dockerfile.test-cp-root-dir @@ -0,0 +1,2 @@ +FROM scratch +COPY Dockerfile.test-cp-root-dir / diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index 6ae54ba34..3f9b12e0a 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -296,4 +296,42 @@ var _ = Describe("Podman cp", func() { os.Remove("testfile1") }) + It("podman cp the root directory from the ctr to an existing directory on the host ", func() { + imgName := "test-cp-root-dir:latest" + DockerfileName := "Dockerfile.test-cp-root-dir" + ctrName := "test-container-cp-root" + + session := podmanTest.PodmanNoCache([]string{"build", "-f", "build/" + DockerfileName, "-t", imgName, "build/"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + testDirPath := filepath.Join(podmanTest.RunRoot, "TestDirForCp") + + session = podmanTest.Podman([]string{"create", "--name", ctrName, imgName, "dummy"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + err := os.Mkdir(testDirPath, 0755) + Expect(err).To(BeNil()) + defer os.RemoveAll(testDirPath) + + // Copy the root directory of the container to an existing directory + session = podmanTest.Podman([]string{"cp", ctrName + ":/", testDirPath}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // The file should be in the directory, + // not one layer too much of the directory called merged + checkFile := filepath.Join(testDirPath, DockerfileName) + _, err = os.Stat(checkFile) + Expect(err).To(BeNil()) + + session = podmanTest.Podman([]string{"container", "rm", ctrName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.PodmanNoCache([]string{"rmi", "-f", imgName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index b9a1ff83d..822e470f2 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -429,4 +429,19 @@ var _ = Describe("Podman create", func() { Expect(len(data)).To(Equal(1)) Expect(data[0].HostConfig.NanoCpus).To(Equal(int64(nanoCPUs))) }) + + It("podman create --replace", func() { + // Make sure we error out with --name. + session := podmanTest.Podman([]string{"create", "--replace", ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + // Create and replace 5 times in a row the "same" container. + ctrName := "testCtr" + for i := 0; i < 5; i++ { + session = podmanTest.Podman([]string{"create", "--replace", "--name", ctrName, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + } + }) }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index f36163ccc..f9446e0c6 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -288,4 +288,16 @@ var _ = Describe("Podman logs", func() { logc.WaitWithDefaultTimeout() Expect(logc).To(Exit(0)) }) + + It("follow output stopped container", func() { + containerName := "logs-f" + + logc := podmanTest.Podman([]string{"run", "--name", containerName, "-d", ALPINE}) + logc.WaitWithDefaultTimeout() + Expect(logc).To(Exit(0)) + + results := podmanTest.Podman([]string{"logs", "-f", containerName}) + results.WaitWithDefaultTimeout() + Expect(results).To(Exit(0)) + }) }) diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index a7d5783cb..8d07f6290 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -305,4 +305,19 @@ var _ = Describe("Podman pod create", func() { data := check.InspectPodToJSON() Expect(data.ID).To(Equal(string(id))) }) + + It("podman pod create --replace", func() { + // Make sure we error out with --name. + session := podmanTest.Podman([]string{"pod", "create", "--replace", ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + // Create and replace 5 times in a row the "same" pod. + podName := "testCtr" + for i := 0; i < 5; i++ { + session = podmanTest.Podman([]string{"pod", "create", "--replace", "--name", podName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + } + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 59215c7e5..76944b3db 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -931,4 +931,19 @@ USER mail` session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) + + It("podman run --replace", func() { + // Make sure we error out with --name. + session := podmanTest.Podman([]string{"create", "--replace", ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + // Run and replace 5 times in a row the "same" container. + ctrName := "testCtr" + for i := 0; i < 5; i++ { + session := podmanTest.Podman([]string{"run", "--detach", "--replace", "--name", ctrName, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + } + }) }) diff --git a/test/dockerpy/README.md b/test/test_dockerpy/README.md index 32e426d58..32e426d58 100644 --- a/test/dockerpy/README.md +++ b/test/test_dockerpy/README.md diff --git a/test/dockerpy/__init__.py b/test/test_dockerpy/__init__.py index e69de29bb..e69de29bb 100644 --- a/test/dockerpy/__init__.py +++ b/test/test_dockerpy/__init__.py diff --git a/test/dockerpy/common.py b/test/test_dockerpy/common.py index fdacb49be..975b13dc6 100644 --- a/test/dockerpy/common.py +++ b/test/test_dockerpy/common.py @@ -23,13 +23,30 @@ def podman(): binary = "bin/podman" return binary -def restore_image_from_cache(): - client.load_image(constant.ImageCacheDir+alpineDict["tarballName"]) +def restore_image_from_cache(TestClass): + alpineImage = os.path.join(constant.ImageCacheDir , alpineDict["tarballName"]) + if not os.path.exists(alpineImage): + os.makedirs(constant.ImageCacheDir) + client.pull(constant.ALPINE) + response = client.get_image(constant.ALPINE) + image_tar = open(alpineImage,mode="wb") + image_tar.write(response.data) + image_tar.close() + else : + TestClass.podman = subprocess.run( + [ + podman(), "load", "-i", alpineImage + ], + shell=False, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) def run_top_container(): - client.pull(constant.ALPINE) - c = client.create_container(constant.ALPINE,name=constant.TOP) + c = client.create_container(image=constant.ALPINE,command='/bin/sleep 5',name=constant.TOP) client.start(container=c.get("Id")) + return c.get("Id") def enable_sock(TestClass): TestClass.podman = subprocess.Popen( diff --git a/test/dockerpy/constant.py b/test/test_dockerpy/constant.py index 8a3f1d984..8a3f1d984 100644 --- a/test/dockerpy/constant.py +++ b/test/test_dockerpy/constant.py diff --git a/test/test_dockerpy/test_containers.py b/test/test_dockerpy/test_containers.py new file mode 100644 index 000000000..34fe82c18 --- /dev/null +++ b/test/test_dockerpy/test_containers.py @@ -0,0 +1,194 @@ + +import unittest +import docker +import requests +import os +from docker import Client +from . import constant +from . import common +import time + +client = common.get_client() + +class TestContainers(unittest.TestCase): + + podman = None + topContainerId = "" + + def setUp(self): + super().setUp() + common.restore_image_from_cache(self) + TestContainers.topContainerId = common.run_top_container() + + def tearDown(self): + common.remove_all_containers() + common.remove_all_images() + return super().tearDown() + + @classmethod + def setUpClass(cls): + super().setUpClass() + common.enable_sock(cls) + + @classmethod + def tearDownClass(cls): + common.terminate_connection(cls) + return super().tearDownClass() + + def test_inspect_container(self): + # Inspect bogus container + with self.assertRaises(requests.HTTPError) as error: + client.inspect_container("dummy") + self.assertEqual(error.exception.response.status_code, 404) + # Inspect valid container by name + container = client.inspect_container(constant.TOP) + self.assertIn(TestContainers.topContainerId , container["Id"]) + # Inspect valid container by Id + container = client.inspect_container(TestContainers.topContainerId) + self.assertIn(constant.TOP , container["Name"]) + + def test_create_container(self): + # Run a container with detach mode + container = client.create_container(image="alpine", detach=True) + self.assertEqual(len(container),2) + + + def test_start_container(self): + # Start bogus container + with self.assertRaises(requests.HTTPError) as error: + client.start("dummy") + self.assertEqual(error.exception.response.status_code, 404) + + # Podman docs says it should give a 304 but returns with no response + # # Start a already started container should return 304 + # response = client.start(container=TestContainers.topContainerId) + # self.assertEqual(error.exception.response.status_code, 304) + + # Create a new container and validate the count + client.create_container(image=constant.ALPINE,name="container2") + containers = client.containers(quiet=True,all=True) + self.assertEqual(len(containers),2) + + def test_stop_container(self): + # Stop bogus container + with self.assertRaises(requests.HTTPError) as error: + client.stop("dummy") + self.assertEqual(error.exception.response.status_code, 404) + + # Validate the container state + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "running") + + # Stop a running container and validate the state + client.stop(TestContainers.topContainerId) + container = client.inspect_container(constant.TOP) + self.assertIn(container["State"]["Status"],"stopped exited",) + + def test_restart_container(self): + # Restart bogus container + with self.assertRaises(requests.HTTPError) as error: + client.restart("dummy") + self.assertEqual(error.exception.response.status_code, 404) + + # Validate the container state + client.stop(TestContainers.topContainerId) + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "stopped") + + # restart a running container and validate the state + client.restart(TestContainers.topContainerId) + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "running") + + def test_remove_container(self): + # Remove bogus container + with self.assertRaises(requests.HTTPError) as error: + client.remove_container("dummy") + self.assertEqual(error.exception.response.status_code, 404) + + # Remove container by ID with force + client.remove_container(TestContainers.topContainerId, force=True) + containers = client.containers() + self.assertEqual(len(containers),0) + + def test_remove_container_without_force(self): + # Validate current container count + containers = client.containers() + self.assertTrue(len(containers),1) + + # Remove running container should throw error + with self.assertRaises(requests.HTTPError) as error: + client.remove_container(TestContainers.topContainerId) + self.assertEqual(error.exception.response.status_code, 500) + + # Remove container by ID with force + client.stop(TestContainers.topContainerId) + client.remove_container(TestContainers.topContainerId) + containers = client.containers() + self.assertEqual(len(containers),0) + + def test_pause_container(self): + # Pause bogus container + with self.assertRaises(requests.HTTPError) as error: + client.pause("dummy") + self.assertEqual(error.exception.response.status_code, 404) + + # Validate the container state + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "running") + + # Pause a running container and validate the state + client.pause(container) + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "paused") + + def test_pause_stoped_container(self): + # Stop the container + client.stop(TestContainers.topContainerId) + + # Pause exited container should trow error + with self.assertRaises(requests.HTTPError) as error: + client.pause(TestContainers.topContainerId) + self.assertEqual(error.exception.response.status_code, 500) + + + def test_unpause_container(self): + # Unpause bogus container + with self.assertRaises(requests.HTTPError) as error: + client.unpause("dummy") + self.assertEqual(error.exception.response.status_code, 404) + + # Validate the container state + client.pause(TestContainers.topContainerId) + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "paused") + + # Pause a running container and validate the state + client.unpause(TestContainers.topContainerId) + container = client.inspect_container(constant.TOP) + self.assertEqual(container["State"]["Status"], "running") + + def test_list_container(self): + + # Add container and validate the count + client.create_container(image="alpine", detach=True) + containers = client.containers(all=True) + self.assertEqual(len(containers),2) + + # Not working for now......checking + # # List container with filter by id + # filters = {'id':TestContainers.topContainerId} + # filteredContainers = client.containers(all=True,filters = filters) + # self.assertEqual(len(filteredContainers) , 1) + + # # List container with filter by name + # filters = {'name':constant.TOP} + # filteredContainers = client.containers(all=True,filters = filters) + # self.assertEqual(len(filteredContainers) , 1) + + @unittest.skip("Not Supported yet") + def test_rename_container(self): + # rename bogus container + with self.assertRaises(requests.HTTPError) as error: + client.rename(container="dummy", name="newname") + self.assertEqual(error.exception.response.status_code, 404) diff --git a/test/dockerpy/images.py b/test/test_dockerpy/test_images.py index 1e07d25c7..c88353b79 100644 --- a/test/dockerpy/images.py +++ b/test/test_dockerpy/test_images.py @@ -14,7 +14,7 @@ class TestImages(unittest.TestCase): podman = None def setUp(self): super().setUp() - client.pull(constant.ALPINE) + common.restore_image_from_cache(self) def tearDown(self): common.remove_all_images() @@ -73,9 +73,8 @@ class TestImages(unittest.TestCase): self.assertEqual(len(allImages), 1) # Add more images client.pull(constant.BB) - client.pull(constant.NGINX) allImages = client.images() - self.assertEqual(len(allImages) , 3) + self.assertEqual(len(allImages) , 2) # List images with filter diff --git a/test/test_dockerpy/test_info_version.py b/test/test_dockerpy/test_info_version.py new file mode 100644 index 000000000..be1a2aab9 --- /dev/null +++ b/test/test_dockerpy/test_info_version.py @@ -0,0 +1,46 @@ +import unittest +import docker +from docker import Client +from . import constant +from . import common + +client = common.get_client() + +class TestInfo_Version(unittest.TestCase): + + podman = None + topContainerId = "" + + def setUp(self): + super().setUp() + common.restore_image_from_cache(self) + TestInfo_Version.topContainerId = common.run_top_container() + + def tearDown(self): + common.remove_all_containers() + common.remove_all_images() + return super().tearDown() + + @classmethod + def setUpClass(cls): + super().setUpClass() + common.enable_sock(cls) + + @classmethod + def tearDownClass(cls): + common.terminate_connection(cls) + return super().tearDownClass() + + + def test_Info(self): + self.assertIsNotNone(client.info()) + + def test_info_container_details(self): + info = client.info() + self.assertEqual(info["Containers"],1) + client.create_container(image=constant.ALPINE) + info = client.info() + self.assertEqual(info["Containers"],2) + + def test_version(self): + self.assertIsNotNone(client.version()) |