summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libpod/runtime.go4
-rw-r--r--pkg/api/handlers/libpod/generate.go34
-rw-r--r--pkg/domain/infra/abi/play.go1
-rw-r--r--pkg/systemd/generate/common.go14
-rw-r--r--pkg/systemd/generate/containers.go4
-rw-r--r--pkg/systemd/generate/containers_test.go42
-rw-r--r--pkg/systemd/generate/pods.go3
-rw-r--r--pkg/systemd/generate/pods_test.go44
-rw-r--r--test/apiv2/20-containers.at2
-rwxr-xr-xtest/apiv2/test-apiv244
-rw-r--r--test/e2e/generate_systemd_test.go52
-rw-r--r--test/e2e/systemd_activate_test.go107
-rw-r--r--test/utils/utils.go2
13 files changed, 323 insertions, 30 deletions
diff --git a/libpod/runtime.go b/libpod/runtime.go
index d19997709..07653217a 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -210,6 +210,10 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R
}
if err := shutdown.Register("libpod", func(sig os.Signal) error {
+ // For `systemctl stop podman.service` support, exit code should be 0
+ if sig == syscall.SIGTERM {
+ os.Exit(0)
+ }
os.Exit(1)
return nil
}); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists {
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go
index 7e08dd4a8..28785b00d 100644
--- a/pkg/api/handlers/libpod/generate.go
+++ b/pkg/api/handlers/libpod/generate.go
@@ -25,18 +25,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
RestartSec uint `schema:"restartSec"`
StopTimeout uint `schema:"stopTimeout"`
StartTimeout uint `schema:"startTimeout"`
- ContainerPrefix string `schema:"containerPrefix"`
- PodPrefix string `schema:"podPrefix"`
- Separator string `schema:"separator"`
+ ContainerPrefix *string `schema:"containerPrefix"`
+ PodPrefix *string `schema:"podPrefix"`
+ Separator *string `schema:"separator"`
Wants []string `schema:"wants"`
After []string `schema:"after"`
Requires []string `schema:"requires"`
}{
- StartTimeout: 0,
- StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout,
- ContainerPrefix: "container",
- PodPrefix: "pod",
- Separator: "-",
+ StartTimeout: 0,
+ StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
@@ -44,6 +41,21 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
return
}
+ var ContainerPrefix = "container"
+ if query.ContainerPrefix != nil {
+ ContainerPrefix = *query.ContainerPrefix
+ }
+
+ var PodPrefix = "pod"
+ if query.PodPrefix != nil {
+ PodPrefix = *query.PodPrefix
+ }
+
+ var Separator = "-"
+ if query.Separator != nil {
+ Separator = *query.Separator
+ }
+
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateSystemdOptions{
Name: query.Name,
@@ -53,9 +65,9 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
RestartPolicy: query.RestartPolicy,
StartTimeout: &query.StartTimeout,
StopTimeout: &query.StopTimeout,
- ContainerPrefix: query.ContainerPrefix,
- PodPrefix: query.PodPrefix,
- Separator: query.Separator,
+ ContainerPrefix: ContainerPrefix,
+ PodPrefix: PodPrefix,
+ Separator: Separator,
RestartSec: &query.RestartSec,
Wants: query.Wants,
After: query.After,
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index e16da4ed9..4d8c5a381 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -462,6 +462,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil {
return nil, err
}
+ specGen.RawImageName = container.Image
rtSpec, spec, opts, err := generate.MakeContainer(ctx, ic.Libpod, specGen, false, nil)
if err != nil {
return nil, err
diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go
index a6f8f7cd4..e53d37897 100644
--- a/pkg/systemd/generate/common.go
+++ b/pkg/systemd/generate/common.go
@@ -137,3 +137,17 @@ func removeArg(arg string, args []string) []string {
}
return newArgs
}
+
+// This function is used to get name of systemd service from prefix, separator, and
+// container/pod name. If prefix is empty, the service name does not include the
+// separator. This is to avoid a situation where service name starts with the separator
+// which is usually hyphen.
+func getServiceName(prefix string, separator string, name string) string {
+ serviceName := name
+
+ if len(prefix) > 0 {
+ serviceName = prefix + separator + name
+ }
+
+ return serviceName
+}
diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go
index ea829c810..c01bb1baf 100644
--- a/pkg/systemd/generate/containers.go
+++ b/pkg/systemd/generate/containers.go
@@ -236,7 +236,9 @@ func containerServiceName(ctr *libpod.Container, options entities.GenerateSystem
if options.Name {
nameOrID = ctr.Name()
}
- serviceName := fmt.Sprintf("%s%s%s", options.ContainerPrefix, options.Separator, nameOrID)
+
+ serviceName := getServiceName(options.ContainerPrefix, options.Separator, nameOrID)
+
return nameOrID, serviceName
}
diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go
index 2f653a4b9..b9bf7c317 100644
--- a/pkg/systemd/generate/containers_test.go
+++ b/pkg/systemd/generate/containers_test.go
@@ -91,6 +91,30 @@ Type=forking
WantedBy=default.target
`
+ goodNameEmptyContainerPrefix := `# foobar.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman foobar.service
+Documentation=man:podman-generate-systemd(1)
+Wants=network-online.target
+After=network-online.target
+RequiresMountsFor=/var/run/containers/storage
+
+[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
+Restart=on-failure
+TimeoutStopSec=70
+ExecStart=/usr/bin/podman start foobar
+ExecStop=/usr/bin/podman stop -t 10 foobar
+ExecStopPost=/usr/bin/podman stop -t 10 foobar
+PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+Type=forking
+
+[Install]
+WantedBy=default.target
+`
+
goodNameCustomWants := `# container-foobar.service
# autogenerated by Podman CI
@@ -1206,6 +1230,24 @@ WantedBy=default.target
false,
true,
},
+ {"good with name and empty container-prefix",
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "foobar",
+ ContainerNameOrID: "foobar",
+ PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
+ EnvVariable: define.EnvVariable,
+ GraphRoot: "/var/lib/containers/storage",
+ RunRoot: "/var/run/containers/storage",
+ },
+ goodNameEmptyContainerPrefix,
+ false,
+ false,
+ false,
+ false,
+ },
}
for _, tt := range tests {
test := tt
diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go
index 003c23e77..78ae6391b 100644
--- a/pkg/systemd/generate/pods.go
+++ b/pkg/systemd/generate/pods.go
@@ -242,7 +242,8 @@ func generatePodInfo(pod *libpod.Pod, options entities.GenerateSystemdOptions) (
nameOrID = pod.Name()
ctrNameOrID = infraCtr.Name()
}
- serviceName := fmt.Sprintf("%s%s%s", options.PodPrefix, options.Separator, nameOrID)
+
+ serviceName := getServiceName(options.PodPrefix, options.Separator, nameOrID)
info := podInfo{
ServiceName: serviceName,
diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go
index b37e0825b..dcb18780c 100644
--- a/pkg/systemd/generate/pods_test.go
+++ b/pkg/systemd/generate/pods_test.go
@@ -67,6 +67,32 @@ WantedBy=default.target
podGood := serviceInfo + headerInfo + podContent
podGoodNoHeaderInfo := serviceInfo + podContent
+ podGoodWithEmptyPrefix := `# 123abc.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman 123abc.service
+Documentation=man:podman-generate-systemd(1)
+Wants=network-online.target
+After=network-online.target
+RequiresMountsFor=/var/run/containers/storage
+Requires=container-1.service container-2.service
+Before=container-1.service container-2.service
+
+[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
+Restart=on-failure
+TimeoutStopSec=102
+ExecStart=/usr/bin/podman start jadda-jadda-infra
+ExecStop=/usr/bin/podman stop -t 42 jadda-jadda-infra
+ExecStopPost=/usr/bin/podman stop -t 42 jadda-jadda-infra
+PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+Type=forking
+
+[Install]
+WantedBy=default.target
+`
+
podGoodCustomWants := `# pod-123abc.service
# autogenerated by Podman CI
@@ -580,6 +606,24 @@ WantedBy=default.target
false,
false,
},
+ {"pod with empty pod-prefix",
+ podInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "123abc",
+ InfraNameOrID: "jadda-jadda-infra",
+ PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 42,
+ PodmanVersion: "CI",
+ GraphRoot: "/var/lib/containers/storage",
+ RunRoot: "/var/run/containers/storage",
+ RequiredServices: []string{"container-1", "container-2"},
+ CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "bar=arg with space"},
+ },
+ podGoodWithEmptyPrefix,
+ false,
+ false,
+ false,
+ },
}
for _, tt := range tests {
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 49f8fb3fc..94de2cf24 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -280,7 +280,7 @@ t DELETE containers/$cid_top 204
t POST containers/create \
Image=$ENV_WORKDIR_IMG \
WorkingDir=/dataDir \
- StopSignal=9 \
+ StopSignal=\"9\" \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2
index ff328cfc8..c3545522e 100755
--- a/test/apiv2/test-apiv2
+++ b/test/apiv2/test-apiv2
@@ -194,8 +194,16 @@ function jsonify() {
local rhs
IFS='=' read lhs rhs <<<"$i"
- # If right-hand side already includes double quotes, do nothing
- if [[ ! $rhs =~ \" ]]; then
+ if [[ $rhs =~ \" || $rhs == true || $rhs == false || $rhs =~ ^-?[0-9]+$ ]]; then
+ # rhs has been pre-formatted for JSON or a non-string, do not change it
+ :
+ elif [[ $rhs == False ]]; then
+ # JSON boolean is lowercase only
+ rhs=false
+ elif [[ $rhs == True ]]; then
+ # JSON boolean is lowercase only
+ rhs=true
+ else
rhs="\"${rhs}\""
fi
settings_out+=("\"${lhs}\":${rhs}")
@@ -241,26 +249,30 @@ function t() {
# entrypoint path can include a descriptive comment; strip it off
path=${path%% *}
- # path may include JSONish params that curl will barf on; url-encode them
- path="${path//'['/%5B}"
- path="${path//']'/%5D}"
- path="${path//'{'/%7B}"
- path="${path//'}'/%7D}"
- path="${path//':'/%3A}"
+ local url=$path
+ if ! [[ $path =~ ^'http://' ]]; then
+ # path may include JSONish params that curl will barf on; url-encode them
+ path="${path//'['/%5B}"
+ path="${path//']'/%5D}"
+ path="${path//'{'/%7B}"
+ path="${path//'}'/%7D}"
+ path="${path//':'/%3A}"
+
+ # If given path begins with /, use it as-is; otherwise prepend /version/
+ url=http://$HOST:$PORT
+ case "$path" in
+ /*) url="$url$path" ;;
+ libpod/*) url="$url/v4.0.0/$path" ;;
+ *) url="$url/v1.41/$path" ;;
+ esac
+ fi
# curl -X HEAD but without --head seems to wait for output anyway
if [[ $method == "HEAD" ]]; then
curl_args="--head"
fi
- local expected_code=$1; shift
- # If given path begins with /, use it as-is; otherwise prepend /version/
- local url=http://$HOST:$PORT
- case "$path" in
- /*) url="$url$path" ;;
- libpod/*) url="$url/v4.0.0/$path" ;;
- *) url="$url/v1.41/$path" ;;
- esac
+ local expected_code=$1; shift
# Log every action we do
echo "-------------------------------------------------------------" >>$LOG
diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go
index 55b9a8037..e4b854332 100644
--- a/test/e2e/generate_systemd_test.go
+++ b/test/e2e/generate_systemd_test.go
@@ -423,6 +423,20 @@ var _ = Describe("Podman generate systemd", func() {
})
+ It("podman generate systemd --container-prefix ''", func() {
+ n := podmanTest.Podman([]string{"create", "--name", "foo", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n).Should(Exit(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--name", "--container-prefix", "", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ // Grepping the output (in addition to unit tests)
+ Expect(session.OutputToString()).To(ContainSubstring("# foo.service"))
+
+ })
+
It("podman generate systemd --separator _", func() {
n := podmanTest.Podman([]string{"create", "--name", "foo", "alpine", "top"})
n.WaitWithDefaultTimeout()
@@ -485,6 +499,44 @@ var _ = Describe("Podman generate systemd", func() {
Expect(session.OutputToString()).To(ContainSubstring("BindsTo=p_foo.service"))
})
+ It("podman generate systemd pod --pod-prefix '' --container-prefix '' --separator _ change all prefixes/separator", func() {
+ n := podmanTest.Podman([]string{"pod", "create", "--name", "foo"})
+ n.WaitWithDefaultTimeout()
+ Expect(n).Should(Exit(0))
+
+ n = podmanTest.Podman([]string{"create", "--pod", "foo", "--name", "foo-1", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n).Should(Exit(0))
+
+ n = podmanTest.Podman([]string{"create", "--pod", "foo", "--name", "foo-2", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n).Should(Exit(0))
+
+ // test systemd generate with empty pod prefix
+ session1 := podmanTest.Podman([]string{"generate", "systemd", "--pod-prefix", "", "--name", "foo"})
+ session1.WaitWithDefaultTimeout()
+ Expect(session1).Should(Exit(0))
+
+ // Grepping the output (in addition to unit tests)
+ Expect(session1.OutputToString()).To(ContainSubstring("# foo.service"))
+ Expect(session1.OutputToString()).To(ContainSubstring("Requires=container-foo-1.service container-foo-2.service"))
+ Expect(session1.OutputToString()).To(ContainSubstring("# container-foo-1.service"))
+ Expect(session1.OutputToString()).To(ContainSubstring("BindsTo=foo.service"))
+
+ // test systemd generate with empty container and pod prefix
+ session2 := podmanTest.Podman([]string{"generate", "systemd", "--container-prefix", "", "--pod-prefix", "", "--separator", "_", "--name", "foo"})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2).Should(Exit(0))
+
+ // Grepping the output (in addition to unit tests)
+ Expect(session2.OutputToString()).To(ContainSubstring("# foo.service"))
+ Expect(session2.OutputToString()).To(ContainSubstring("Requires=foo-1.service foo-2.service"))
+ Expect(session2.OutputToString()).To(ContainSubstring("# foo-1.service"))
+ Expect(session2.OutputToString()).To(ContainSubstring("# foo-2.service"))
+ Expect(session2.OutputToString()).To(ContainSubstring("BindsTo=foo.service"))
+
+ })
+
It("podman generate systemd pod with containers --new", func() {
tmpDir, err := ioutil.TempDir("", "")
Expect(err).To(BeNil())
diff --git a/test/e2e/systemd_activate_test.go b/test/e2e/systemd_activate_test.go
new file mode 100644
index 000000000..d5434868d
--- /dev/null
+++ b/test/e2e/systemd_activate_test.go
@@ -0,0 +1,107 @@
+package integration
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+ "time"
+
+ testUtils "github.com/containers/podman/v4/test/utils"
+ podmanUtils "github.com/containers/podman/v4/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Systemd activate", func() {
+ var tempDir string
+ var err error
+ var podmanTest *PodmanTestIntegration
+
+ BeforeEach(func() {
+ tempDir, err = testUtils.CreateTempDirInTempDir()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ os.Exit(1)
+ }
+
+ podmanTest = PodmanTestCreate(tempDir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ processTestResult(CurrentGinkgoTestDescription())
+ })
+
+ It("stop podman.service", func() {
+ SkipIfRemote("Testing stopped service requires both podman and podman-remote binaries")
+
+ activate, err := exec.LookPath("systemd-socket-activate")
+ if err != nil {
+ activate = "/usr/bin/systemd-socket-activate"
+ }
+ stat, err := os.Stat(activate)
+ switch {
+ case errors.Is(err, fs.ErrNotExist):
+ Skip(activate + " required for systemd activation tests")
+ case stat.Mode()&0111 == 0:
+ Skip("Unable to execute " + activate)
+ case err != nil:
+ Skip(err.Error())
+ }
+
+ // systemd-socket-activate does not support DNS lookups
+ host := "127.0.0.1"
+ port, err := podmanUtils.GetRandomPort()
+ Expect(err).ToNot(HaveOccurred())
+
+ activateSession := testUtils.StartSystemExec(activate, []string{
+ fmt.Sprintf("--listen=%s:%d", host, port),
+ podmanTest.PodmanBinary,
+ "--root=" + filepath.Join(tempDir, "server_root"),
+ "system", "service",
+ "--time=0",
+ })
+ Expect(activateSession.Exited).ShouldNot(Receive(), "Failed to start podman service")
+
+ // Curried functions for specialized podman calls
+ podmanRemote := func(args ...string) *testUtils.PodmanSession {
+ args = append([]string{"--url", fmt.Sprintf("tcp://%s:%d", host, port)}, args...)
+ return testUtils.SystemExec(podmanTest.RemotePodmanBinary, args)
+ }
+
+ podman := func(args ...string) *testUtils.PodmanSession {
+ args = append([]string{"--root", filepath.Join(tempDir, "server_root")}, args...)
+ return testUtils.SystemExec(podmanTest.PodmanBinary, args)
+ }
+
+ containerName := "top_" + testUtils.RandomString(8)
+ apiSession := podmanRemote(
+ "create", "--tty", "--name", containerName, "--entrypoint", "top",
+ "quay.io/libpod/alpine_labels:latest",
+ )
+ Expect(apiSession).Should(Exit(0))
+
+ apiSession = podmanRemote("start", containerName)
+ Expect(apiSession).Should(Exit(0))
+
+ apiSession = podmanRemote("inspect", "--format={{.State.Running}}", containerName)
+ Expect(apiSession).Should(Exit(0))
+ Expect(apiSession.OutputToString()).To(Equal("true"))
+
+ // Emulate 'systemd stop podman.service'
+ activateSession.Signal(syscall.SIGTERM)
+ time.Sleep(2)
+ Eventually(activateSession).Should(Exit(0))
+
+ abiSession := podman("inspect", "--format={{.State.Running}}", containerName)
+ Expect(abiSession).To(Exit(0))
+ Expect(abiSession.OutputToString()).To(Equal("true"))
+ })
+})
diff --git a/test/utils/utils.go b/test/utils/utils.go
index 14092a2a5..8fe45dca0 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -368,6 +368,7 @@ func CreateTempDirInTempDir() (string, error) {
// SystemExec is used to exec a system command to check its exit code or output
func SystemExec(command string, args []string) *PodmanSession {
c := exec.Command(command, args...)
+ fmt.Println("Execing " + c.String() + "\n")
session, err := Start(c, GinkgoWriter, GinkgoWriter)
if err != nil {
Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
@@ -379,6 +380,7 @@ func SystemExec(command string, args []string) *PodmanSession {
// StartSystemExec is used to start exec a system command
func StartSystemExec(command string, args []string) *PodmanSession {
c := exec.Command(command, args...)
+ fmt.Println("Execing " + c.String() + "\n")
session, err := Start(c, GinkgoWriter, GinkgoWriter)
if err != nil {
Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))