diff --git a/cmd/quadlet/main.go b/cmd/quadlet/main.go index 6b99f87f57..1666b8a4a3 100644 --- a/cmd/quadlet/main.go +++ b/cmd/quadlet/main.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "strings" + "unicode" "github.com/containers/podman/v4/pkg/systemd/parser" "github.com/containers/podman/v4/pkg/systemd/quadlet" @@ -210,6 +211,71 @@ func enableServiceFile(outputPath string, service *parser.UnitFile) { } } +func isImageID(imageName string) bool { + // All sha25:... names are assumed by podman to be fully specified + if strings.HasPrefix(imageName, "sha256:") { + return true + } + + // However, podman also accepts image ids as pure hex strings, + // but only those of length 64 are unambigous image ids + if len(imageName) != 64 { + return false + } + + for _, c := range imageName { + if !unicode.Is(unicode.Hex_Digit, c) { + return false + } + } + + return true +} + +func isUnambiguousName(imageName string) bool { + // Fully specified image ids are unambigous + if isImageID(imageName) { + return true + } + + // Otherwise we require a fully qualified name + firstSlash := strings.Index(imageName, "/") + if firstSlash == -1 { + // No domain or path, not fully qualified + return false + } + + // What is before the first slash can be a domain or a path + domain := imageName[:firstSlash] + + // If its a a domain (has dot or port or is "localhost") it is considered fq + if strings.ContainsAny(domain, ".:") || domain == "localhost" { + return true + } + + return false +} + +// warns if input is an ambigious name, i.e. a partial image id or a short +// name (i.e. is missing a registry) +// +// Examples: +// - short names: "image:tag", "library/fedora" +// - fully qualified names: "quay.io/image", "localhost/image:tag", +// "server.org:5000/lib/image", "sha256:..." +// +// We implement a simple version of this from scratch here to avoid +// a huge dependency in the generator just for a warning. +func warnIfAmbigiousName(container *parser.UnitFile) { + imageName, ok := container.Lookup(quadlet.ContainerGroup, quadlet.KeyImage) + if !ok { + return + } + if !isUnambiguousName(imageName) { + Logf("Warning: %s specifies the image \"%s\" which not a fully qualified image name. This is not ideal for performance and security reasons. See the podman-pull manpage discussion of short-name-aliases.conf for details.", container.Filename, imageName) + } +} + func main() { prgname := path.Base(os.Args[0]) isUser = strings.Contains(prgname, "user") @@ -252,6 +318,7 @@ func main() { switch { case strings.HasSuffix(name, ".container"): + warnIfAmbigiousName(unit) service, err = quadlet.ConvertContainer(unit, isUser) case strings.HasSuffix(name, ".volume"): service, err = quadlet.ConvertVolume(unit, name) diff --git a/cmd/quadlet/main_test.go b/cmd/quadlet/main_test.go new file mode 100644 index 0000000000..df12f2277c --- /dev/null +++ b/cmd/quadlet/main_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsUnambiguousName(t *testing.T) { + tests := []struct { + input string + res bool + }{ + // Ambiguous names + {"fedora", false}, + {"fedora:latest", false}, + {"library/fedora", false}, + {"library/fedora:latest", false}, + {"busybox@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", false}, + {"busybox:latest@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", false}, + {"d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05", false}, + {"d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05aa", false}, + + // Unambiguous names + {"quay.io/fedora", true}, + {"docker.io/fedora", true}, + {"docker.io/library/fedora:latest", true}, + {"localhost/fedora", true}, + {"localhost:5000/fedora:latest", true}, + {"example.foo.this.may.be.garbage.but.maybe.not:1234/fedora:latest", true}, + {"docker.io/library/busybox@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", true}, + {"docker.io/library/busybox:latest@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", true}, + {"docker.io/fedora@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", true}, + {"sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", true}, + {"d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a", true}, + } + + for _, test := range tests { + res := isUnambiguousName(test.input) + assert.Equal(t, res, test.res, "%q", test.input) + } +} diff --git a/test/e2e/quadlet/annotation.container b/test/e2e/quadlet/annotation.container index 5038fa6679..e16395c97f 100644 --- a/test/e2e/quadlet/annotation.container +++ b/test/e2e/quadlet/annotation.container @@ -1,11 +1,11 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename ## assert-podman-args "--annotation" "org.foo.Arg0=arg0" ## assert-podman-args "--annotation" "org.foo.Arg1=arg1" ## assert-podman-args "--annotation" "org.foo.Arg2=arg 2" ## assert-podman-args "--annotation" "org.foo.Arg3=arg3" [Container] -Image=imagename +Image=localhost/imagename Annotation=org.foo.Arg1=arg1 "org.foo.Arg2=arg 2" \ org.foo.Arg3=arg3 diff --git a/test/e2e/quadlet/basepodman.container b/test/e2e/quadlet/basepodman.container index 8204a293b9..5ac5b962a8 100644 --- a/test/e2e/quadlet/basepodman.container +++ b/test/e2e/quadlet/basepodman.container @@ -1,7 +1,7 @@ -## assert-podman-final-args run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm -d --log-driver passthrough --pull=never --runtime /usr/bin/crun --cgroups=split --sdnotify=conmon imagename +## assert-podman-final-args run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm -d --log-driver passthrough --pull=never --runtime /usr/bin/crun --cgroups=split --sdnotify=conmon localhost/imagename [Container] -Image=imagename +Image=localhost/imagename # Disable all default features to get as empty podman run command as we can ReadOnly=no diff --git a/test/e2e/quadlet/basic.container b/test/e2e/quadlet/basic.container index c5a97ea1b0..8369f75aa9 100644 --- a/test/e2e/quadlet/basic.container +++ b/test/e2e/quadlet/basic.container @@ -1,4 +1,4 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename ## assert-podman-args "--name=systemd-%N" ## assert-podman-args "--cidfile=%t/%N.cid" ## assert-podman-args "--rm" @@ -25,4 +25,4 @@ ## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n" [Container] -Image=imagename +Image=localhost/imagename diff --git a/test/e2e/quadlet/capabilities.container b/test/e2e/quadlet/capabilities.container index 4faa7ab85f..d99e30e265 100644 --- a/test/e2e/quadlet/capabilities.container +++ b/test/e2e/quadlet/capabilities.container @@ -4,7 +4,7 @@ ## assert-podman-args "--cap-add=cap_ipc_owner" [Container] -Image=imagename +Image=localhost/imagename # Verify that we can reset to the default cap set DropCapability= AddCapability=CAP_DAC_OVERRIDE CAP_AUDIT_WRITE diff --git a/test/e2e/quadlet/env.container b/test/e2e/quadlet/env.container index 26cbe5a2b9..234cfca605 100644 --- a/test/e2e/quadlet/env.container +++ b/test/e2e/quadlet/env.container @@ -1,4 +1,4 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename ## assert-podman-args --env "FOO1=foo1" ## assert-podman-args --env "FOO2=foo2 " ## assert-podman-args --env "FOO3=foo3" @@ -6,7 +6,7 @@ ## assert-podman-args --env "FOO4=foo\\nfoo" [Container] -Image=imagename +Image=localhost/imagename Environment=FOO1=foo1 "FOO2=foo2 " \ FOO3=foo3 REPLACE=replace Environment=REPLACE=replaced 'FOO4=foo\nfoo' diff --git a/test/e2e/quadlet/escapes.container b/test/e2e/quadlet/escapes.container index 051e4ed282..b9b36dc4f1 100644 --- a/test/e2e/quadlet/escapes.container +++ b/test/e2e/quadlet/escapes.container @@ -1,5 +1,5 @@ ## assert-podman-final-args "/some/path" "an arg" "a;b\\nc\\td'e" "a;b\\nc\\td" "a\"b" [Container] -Image=imagename +Image=localhost/imagename Exec=/some/path "an arg" "a;b\nc\td'e" a;b\nc\td 'a"b' diff --git a/test/e2e/quadlet/exec.container b/test/e2e/quadlet/exec.container index 2b1a6f0f1a..72cbbc4ff2 100644 --- a/test/e2e/quadlet/exec.container +++ b/test/e2e/quadlet/exec.container @@ -1,6 +1,6 @@ -## assert-podman-final-args imagename "/some/binary file" "--arg1" "arg 2" +## assert-podman-final-args localhost/imagename "/some/binary file" "--arg1" "arg 2" [Container] -Image=imagename +Image=localhost/imagename Exec="/some/binary file" --arg1 \ "arg 2" diff --git a/test/e2e/quadlet/image.container b/test/e2e/quadlet/image.container index 1cc226d7e2..b0a7fc6c4b 100644 --- a/test/e2e/quadlet/image.container +++ b/test/e2e/quadlet/image.container @@ -1,4 +1,4 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename [Container] -Image=imagename +Image=localhost/imagename diff --git a/test/e2e/quadlet/install.container b/test/e2e/quadlet/install.container index 728f43fe43..80c1cd19a1 100644 --- a/test/e2e/quadlet/install.container +++ b/test/e2e/quadlet/install.container @@ -9,7 +9,7 @@ ## assert-symlink req3.service.requires/install.service ../install.service [Container] -Image=imagename +Image=localhost/imagename [Install] Alias=alias.service \ diff --git a/test/e2e/quadlet/label.container b/test/e2e/quadlet/label.container index 753011ed0d..afc39972ce 100644 --- a/test/e2e/quadlet/label.container +++ b/test/e2e/quadlet/label.container @@ -1,11 +1,11 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename ## assert-podman-args "--label" "org.foo.Arg0=arg0" ## assert-podman-args "--label" "org.foo.Arg1=arg1" ## assert-podman-args "--label" "org.foo.Arg2=arg 2" ## assert-podman-args "--label" "org.foo.Arg3=arg3" [Container] -Image=imagename +Image=localhost/imagename Label=org.foo.Arg1=arg1 "org.foo.Arg2=arg 2" \ org.foo.Arg3=arg3 diff --git a/test/e2e/quadlet/name.container b/test/e2e/quadlet/name.container index efa6143406..34e077763e 100644 --- a/test/e2e/quadlet/name.container +++ b/test/e2e/quadlet/name.container @@ -1,5 +1,5 @@ ## assert-podman-args "--name=foobar" [Container] -Image=imagename +Image=localhost/imagename ContainerName=foobar diff --git a/test/e2e/quadlet/noremapuser.container b/test/e2e/quadlet/noremapuser.container index 351393f121..f836eac11d 100644 --- a/test/e2e/quadlet/noremapuser.container +++ b/test/e2e/quadlet/noremapuser.container @@ -2,5 +2,5 @@ ## !assert-podman-args --gidmap [Container] -Image=imagename +Image=localhost/imagename RemapUsers=no diff --git a/test/e2e/quadlet/noremapuser2.container b/test/e2e/quadlet/noremapuser2.container index d3fe1cfd32..13c526ab57 100644 --- a/test/e2e/quadlet/noremapuser2.container +++ b/test/e2e/quadlet/noremapuser2.container @@ -20,7 +20,7 @@ ## assert-podman-args --gidmap 1002:1002:4294966293 [Container] -Image=imagename +Image=localhost/imagename RemapUsers=no User=1000 Group=1001 diff --git a/test/e2e/quadlet/notify.container b/test/e2e/quadlet/notify.container index 9bc0fd3cc7..c0aacb4db2 100644 --- a/test/e2e/quadlet/notify.container +++ b/test/e2e/quadlet/notify.container @@ -1,5 +1,5 @@ ## assert-podman-args "--sdnotify=container" [Container] -Image=imagename +Image=localhost/imagename Notify=yes diff --git a/test/e2e/quadlet/other-sections.container b/test/e2e/quadlet/other-sections.container index 4836e5f770..782f6b93e9 100644 --- a/test/e2e/quadlet/other-sections.container +++ b/test/e2e/quadlet/other-sections.container @@ -1,10 +1,10 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename ## assert-key-is "Unit" "Foo" "bar1" "bar2" -## assert-key-is "X-Container" "Image" "imagename" +## assert-key-is "X-Container" "Image" "localhost/imagename" [Unit] Foo=bar1 Foo=bar2 [Container] -Image=imagename +Image=localhost/imagename diff --git a/test/e2e/quadlet/podmanargs.container b/test/e2e/quadlet/podmanargs.container index 6e97c16675..73959bc78d 100644 --- a/test/e2e/quadlet/podmanargs.container +++ b/test/e2e/quadlet/podmanargs.container @@ -3,7 +3,7 @@ ## assert-podman-args "--also" [Container] -Image=imagename +Image=localhost/imagename PodmanArgs="--foo" \ --bar PodmanArgs=--also diff --git a/test/e2e/quadlet/ports.container b/test/e2e/quadlet/ports.container index 195a6a6c54..38725dcbe9 100644 --- a/test/e2e/quadlet/ports.container +++ b/test/e2e/quadlet/ports.container @@ -1,5 +1,5 @@ [Container] -Image=imagename +Image=localhost/imagename ## assert-podman-args --expose=1000 ExposeHostPort=1000 ## assert-podman-args --expose=2000-3000 diff --git a/test/e2e/quadlet/ports_ipv6.container b/test/e2e/quadlet/ports_ipv6.container index f4ab421fad..2b2adf9562 100644 --- a/test/e2e/quadlet/ports_ipv6.container +++ b/test/e2e/quadlet/ports_ipv6.container @@ -1,5 +1,5 @@ [Container] -Image=imagename +Image=localhost/imagename ## assert-podman-args -p=[::1]:80:90 PublishPort=[::1]:80:90 diff --git a/test/e2e/quadlet/shortname.container b/test/e2e/quadlet/shortname.container new file mode 100644 index 0000000000..d74226ff96 --- /dev/null +++ b/test/e2e/quadlet/shortname.container @@ -0,0 +1,4 @@ +## assert-stderr-contains "not a fully qualified image name" + +[Container] +Image=shortname diff --git a/test/e2e/quadlet/socketactivated.container b/test/e2e/quadlet/socketactivated.container index 4b299ed79a..f8ed22f69f 100644 --- a/test/e2e/quadlet/socketactivated.container +++ b/test/e2e/quadlet/socketactivated.container @@ -5,5 +5,5 @@ ## assert-key-is "Service" "NotifyAccess" "all" [Container] -Image=imagename +Image=localhost/imagename SocketActivated=yes diff --git a/test/e2e/quadlet/timezone.container b/test/e2e/quadlet/timezone.container index 4e52bb3a25..96123539f9 100644 --- a/test/e2e/quadlet/timezone.container +++ b/test/e2e/quadlet/timezone.container @@ -1,5 +1,5 @@ ## assert-podman-args --tz=foo [Container] -Image=imagename +Image=localhost/imagename Timezone=foo diff --git a/test/e2e/quadlet/user-host.container b/test/e2e/quadlet/user-host.container index f61157024d..27a7d301bd 100644 --- a/test/e2e/quadlet/user-host.container +++ b/test/e2e/quadlet/user-host.container @@ -11,7 +11,7 @@ ## assert-podman-args --gidmap 1002:101000:99000 [Container] -Image=imagename +Image=localhost/imagename User=1000 HostUser=900 Group=1001 diff --git a/test/e2e/quadlet/user-root1.container b/test/e2e/quadlet/user-root1.container index 7ddc7917e2..ff72bd887f 100644 --- a/test/e2e/quadlet/user-root1.container +++ b/test/e2e/quadlet/user-root1.container @@ -14,7 +14,7 @@ # This means container root must map to something else [Container] -Image=imagename +Image=localhost/imagename User=1000 # Also test name parsing HostUser=root diff --git a/test/e2e/quadlet/user-root2.container b/test/e2e/quadlet/user-root2.container index f3bc22ea25..0791f8651e 100644 --- a/test/e2e/quadlet/user-root2.container +++ b/test/e2e/quadlet/user-root2.container @@ -10,7 +10,7 @@ # Map container uid root to host root [Container] -Image=imagename +Image=localhost/imagename User=0 # Also test name parsing HostUser=root diff --git a/test/e2e/quadlet/user.container b/test/e2e/quadlet/user.container index ad1276a7c2..cfd1c58232 100644 --- a/test/e2e/quadlet/user.container +++ b/test/e2e/quadlet/user.container @@ -1,7 +1,7 @@ -## assert-podman-final-args imagename +## assert-podman-final-args localhost/imagename ## assert-podman-args "--user" "998:999" [Container] -Image=imagename +Image=localhost/imagename User=998 Group=999 diff --git a/test/e2e/quadlet/volume.container b/test/e2e/quadlet/volume.container index 85baafd0a4..dc1acdc14e 100644 --- a/test/e2e/quadlet/volume.container +++ b/test/e2e/quadlet/volume.container @@ -1,10 +1,10 @@ ## assert-podman-args -v /host/dir:/container/volume ## assert-podman-args -v /host/dir2:/container/volume2:Z ## assert-podman-args -v named:/container/named -## assert-podman-args -v systemd-quadlet:/container/quadlet imagename +## assert-podman-args -v systemd-quadlet:/container/quadlet localhost/imagename [Container] -Image=imagename +Image=localhost/imagename Volume=/host/dir:/container/volume Volume=/host/dir2:/container/volume2:Z Volume=/container/empty diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index eb05a582bc..8e1f5b203a 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -291,6 +291,7 @@ var _ = Describe("quadlet system generator", func() { Entry("readwrite.container", "readwrite.container"), Entry("readwrite-notmpfs.container", "readwrite-notmpfs.container"), Entry("seccomp.container", "seccomp.container"), + Entry("shortname.container", "shortname.container"), Entry("timezone.container", "timezone.container"), Entry("user.container", "user.container"), Entry("user-host.container", "user-host.container"),