Merge pull request #16237 from alexlarsson/quadlet-updates

Various quadlet updates
This commit is contained in:
OpenShift Merge Robot
2022-10-26 04:05:40 -04:00
committed by GitHub
16 changed files with 157 additions and 41 deletions

View File

@ -23,6 +23,7 @@ import (
var (
verboseFlag bool // True if -v passed
noKmsgFlag bool
isUser bool // True if run as quadlet-user-generator executable
)
@ -134,7 +135,7 @@ func generateServiceFile(service *parser.UnitFile) error {
Debugf("writing '%s'", service.Path)
service.PrependComment("",
"Automatically generated by quadlet-generator",
fmt.Sprintf("Automatically generated by %s", os.Args[0]),
"")
f, err := os.Create(service.Path)
@ -219,6 +220,10 @@ func main() {
enableDebug()
}
if noKmsgFlag {
noKmsg = true
}
if flag.NArg() < 1 {
Logf("Missing output directory argument")
os.Exit(1)
@ -270,4 +275,5 @@ func main() {
func init() {
flag.BoolVar(&verboseFlag, "v", false, "Print debug information")
flag.BoolVar(&noKmsgFlag, "no-kmsg-log", false, "Don't log to kmsg")
}

View File

@ -15,7 +15,8 @@ Valid _mode_ values are:
- **mac=MAC**: Specify a static mac address for this container.
- **interface_name**: Specify a name for the created network interface inside the container.
For example to set a static ipv4 address and a static mac address, use `--network bridge:ip=10.88.0.10,mac=44:33:22:11:00:99`.
For example to set a static ipv4 address and a static mac address, use `--network bridge:ip=10.88.0.10,mac=44:33:22:11:00:99`.
- \<network name or ID\>[:OPTIONS,...]: Connect to a user-defined network; this is the network name or ID from a network created by **[podman network create](podman-network-create.1.md)**. Using the network name implies the bridge network mode. It is possible to specify the same options described under the bridge mode above. You can use the **--network** option multiple times to specify additional networks.
- **none**: Create a network namespace for the container but do not configure network interfaces for it, thus the container has no network connectivity.
- **container:**_id_: Reuse another container's network stack.

View File

@ -126,22 +126,39 @@ setuid and file capabilities.
#### `DropCapability=` (defaults to `all`)
Drop these capabilities from the default container capability set. The default is `all`, allowing
addition of capabilities with `AddCapability`. Set this to empty to drop no capabilities.
This can be listed multiple times.
Drop these capabilities from the default podman capability set, or `all` for all capabilities. The default if no
`DropCapability` is set is `all`. Set this to empty (i.e. `DropCapability=`) to use the default podman capability set.
This is a space separated list of capabilities. This key can be listed multiple times.
For example:
```
DropCapability=CAP_DAC_OVERRIDE CAP_IPC_OWNER
```
#### `AddCapability=`
By default, the container runs with no capabilities (due to DropCapabilities='all'). If any specific
caps are needed, then add them with this key. For example using `AddCapability=CAP_DAC_OVERRIDE`.
This can be listed multiple times.
#### `ReadOnly=` (defaults to `no`)
This is a space separated list of capabilities. This key can be listed multiple times.
For example:
```
AddCapability=CAP_DAC_OVERRIDE CAP_IPC_OWNER
```
#### `ReadOnly=` (defaults to `yes`)
If enabled, makes image read-only, with /var/tmp, /tmp and /run a tmpfs (unless disabled by `VolatileTmp=no`).
**NOTE:** Podman will automatically copy any content from the image onto the tmpfs
#### `SeccompProfile=`
Set the seccomp profile to use in the container. If unset, the default podman profile is used.
Set to either the pathname of a json file, or `unconfined` to disable the seccomp filters.
#### `RemapUsers=` (defaults to `no`)
If this is enabled, then host user and group ids are remapped in the container, such that all the uids
@ -217,6 +234,14 @@ created by using a `$name.volume` quadlet file.
This key can be listed multiple times.
#### `Network=`
Specify a custom network for the container. This has the same format as the `--network` option
to `podman run`. For example, use `host` to use the host network in the container, or `none` to
not set up networking in the container.
This key can be listed multiple times.
#### `ExposeHostPort=`
Exposes a port, or a range of ports (e.g. `50-59`), from the host to the container. Equivalent
@ -241,6 +266,16 @@ allocated port can be found with the `podman port` command.
This key can be listed multiple times.
#### `AddDevice=`
Adds a device node from the host into the container. The format of this is
`HOST-DEVICE[:CONTAINER-DEVICE][:PERMISSIONS]`, where `HOST-DEVICE` is the path of
the device node on the host, `CONTAINER-DEVICE` is the path of the device node in
the container, and `PERMISSIONS` is a list of permissions combining 'r' for read,
'w' for write, and 'm' for mknod(2).
This key can be listed multiple times.
#### `PodmanArgs=`
This key contains a list of arguments passed directly to the end of the `podman run` command

View File

@ -56,7 +56,6 @@ const (
KeyRemapUIDRanges = "RemapUidRanges"
KeyRemapGIDRanges = "RemapGidRanges"
KeyNotify = "Notify"
KeySocketActivated = "SocketActivated"
KeyExposeHostPort = "ExposeHostPort"
KeyPublishPort = "PublishPort"
KeyKeepID = "KeepId"
@ -71,6 +70,9 @@ const (
KeyRunInit = "RunInit"
KeyVolatileTmp = "VolatileTmp"
KeyTimezone = "Timezone"
KeySeccompProfile = "SeccompProfile"
KeyAddDevice = "AddDevice"
KeyNetwork = "Network"
)
// Supported keys in "Container" group
@ -89,7 +91,6 @@ var supportedContainerKeys = map[string]bool{
KeyRemapUIDRanges: true,
KeyRemapGIDRanges: true,
KeyNotify: true,
KeySocketActivated: true,
KeyExposeHostPort: true,
KeyPublishPort: true,
KeyKeepID: true,
@ -104,6 +105,9 @@ var supportedContainerKeys = map[string]bool{
KeyRunInit: true,
KeyVolatileTmp: true,
KeyTimezone: true,
KeySeccompProfile: true,
KeyAddDevice: true,
KeyNetwork: true,
}
// Supported keys in "Volume" group
@ -353,8 +357,7 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
"-d",
// But we still want output to the journal, so use the log driver.
// TODO: Once available we want to use the passthrough log-driver instead.
"--log-driver", "journald",
"--log-driver", "passthrough",
// Never try to pull the image during service start
"--pull=never")
@ -370,6 +373,13 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
podman.addf("--tz=%s", timezone)
}
networks := container.LookupAll(ContainerGroup, KeyNetwork)
for _, network := range networks {
if len(network) > 0 {
podman.addf("--network=%s", network)
}
}
// Run with a pid1 init to reap zombies by default (as most apps don't do that)
runInit := container.LookupBoolean(ContainerGroup, KeyRunInit, true)
if runInit {
@ -397,9 +407,21 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
podman.add("--security-opt=no-new-privileges")
}
// But allow overrides with AddCapability
devices := container.LookupAllStrv(ContainerGroup, KeyAddDevice)
for _, device := range devices {
podman.addf("--device=%s", device)
}
// Default to no higher level privileges or caps
seccompProfile, hasSeccompProfile := container.Lookup(ContainerGroup, KeySeccompProfile)
if hasSeccompProfile {
podman.add("--security-opt", fmt.Sprintf("seccomp=%s", seccompProfile))
}
dropCaps := []string{"all"} // Default
if container.HasKey(ContainerGroup, KeyDropCapability) {
dropCaps = container.LookupAll(ContainerGroup, KeyDropCapability)
dropCaps = container.LookupAllStrv(ContainerGroup, KeyDropCapability)
}
for _, caps := range dropCaps {
@ -407,12 +429,12 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
}
// But allow overrides with AddCapability
addCaps := container.LookupAll(ContainerGroup, KeyAddCapability)
addCaps := container.LookupAllStrv(ContainerGroup, KeyAddCapability)
for _, caps := range addCaps {
podman.addf("--cap-add=%s", strings.ToLower(caps))
}
readOnly := container.LookupBoolean(ContainerGroup, KeyReadOnly, false)
readOnly := container.LookupBoolean(ContainerGroup, KeyReadOnly, true)
if readOnly {
podman.add("--read-only")
}
@ -429,18 +451,6 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
podman.add("--read-only-tmpfs=false")
}
socketActivated := container.LookupBoolean(ContainerGroup, KeySocketActivated, false)
if socketActivated {
// TODO: This will not be needed with later podman versions that support activation directly:
// https://github.com/containers/podman/pull/11316
podman.add("--preserve-fds=1")
podmanEnv["LISTEN_FDS"] = "1"
// TODO: This will not be 2 when catatonit forwards fds:
// https://github.com/openSUSE/catatonit/pull/15
podmanEnv["LISTEN_PID"] = "2"
}
defaultContainerUID := uint32(0)
defaultContainerGID := uint32(0)

View File

@ -1,10 +1,10 @@
## assert-podman-final-args run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm -d --log-driver journald --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 imagename
[Container]
Image=imagename
# Disable all default features to get as empty podman run command as we can
RemapUsers=no
ReadOnly=no
NoNewPrivileges=no
DropCapability=
RunInit=no

View File

@ -4,7 +4,7 @@
## assert-podman-args "--rm"
## assert-podman-args "--replace"
## assert-podman-args "-d"
## assert-podman-args "--log-driver" "journald"
## assert-podman-args "--log-driver" "passthrough"
## assert-podman-args "--pull=never"
## assert-podman-args "--init"
## assert-podman-args "--runtime" "/usr/bin/crun"
@ -12,7 +12,8 @@
## assert-podman-args "--sdnotify=conmon"
## assert-podman-args "--security-opt=no-new-privileges"
## assert-podman-args "--cap-drop=all"
## assert-podman-args "--tmpfs" "/tmp:rw,size=512M,mode=1777"
## assert-podman-args "--read-only"
## !assert-podman-args "--read-only-tmpfs=false"
## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
## assert-key-is "Service" "KillMode" "mixed"
## assert-key-is "Service" "Delegate" "yes"

View File

@ -1,8 +1,11 @@
## assert-podman-args "--cap-drop=all"
## !assert-podman-args "--cap-drop=all"
## assert-podman-args "--cap-add=cap_dac_override"
## assert-podman-args "--cap-add=cap_audit_write"
## assert-podman-args "--cap-add=cap_ipc_owner"
[Container]
Image=imagename
AddCapability=CAP_DAC_OVERRIDE
# Verify that we can reset to the default cap set
DropCapability=
AddCapability=CAP_DAC_OVERRIDE CAP_AUDIT_WRITE
AddCapability=CAP_IPC_OWNER

View File

@ -0,0 +1,9 @@
## !assert-podman-args "--cap-drop=all"
## assert-podman-args "--cap-drop=cap_dac_override"
## assert-podman-args "--cap-drop=cap_audit_write"
## assert-podman-args "--cap-drop=cap_ipc_owner"
[Container]
Image=localhost/imagename
DropCapability=CAP_DAC_OVERRIDE CAP_AUDIT_WRITE
DropCapability=CAP_IPC_OWNER

View File

@ -0,0 +1,7 @@
## assert-podman-args --device=/dev/fuse
## assert-podman-args --device=/dev/loop0:r
[Container]
Image=localhost/imagename
AddDevice=/dev/fuse
AddDevice=/dev/loop0:r

View File

@ -0,0 +1,5 @@
## assert-podman-args "--network=host"
[Container]
Image=localhost/imagename
Network=host

View File

@ -1,4 +1,4 @@
## assert-failed
## assert-stderr-contains "No Image key specified"
## assert-stderr-contains "no Image key specified"
[Container]

View File

@ -0,0 +1,6 @@
## assert-podman-args "--read-only-tmpfs=false"
## assert-podman-args "--read-only"
[Container]
Image=localhost/imagename
VolatileTmp=no

View File

@ -0,0 +1,7 @@
## !assert-podman-args "--read-only"
## !assert-podman-args "--tmpfs" "/tmp:rw,size=512M,mode=1777"
[Container]
Image=localhost/imagename
VolatileTmp=no
ReadOnly=no

View File

@ -0,0 +1,6 @@
## !assert-podman-args "--read-only"
## assert-podman-args "--tmpfs" "/tmp:rw,size=512M,mode=1777"
[Container]
Image=localhost/imagename
ReadOnly=no

View File

@ -0,0 +1,5 @@
## assert-podman-args --security-opt seccomp=unconfined
[Container]
Image=localhost/imagename
SeccompProfile=unconfined

View File

@ -80,7 +80,7 @@ func findSublist(full []string, sublist []string) int {
}
func (t *quadletTestcase) assertStdErrContains(args []string, session *PodmanSessionIntegration) bool {
return strings.Contains(session.OutputToString(), args[0])
return strings.Contains(session.ErrorToString(), args[0])
}
func (t *quadletTestcase) assertKeyIs(args []string, unit *parser.UnitFile) bool {
@ -174,7 +174,10 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
}
if !ok {
s, _ := unit.ToString()
s := "(nil)"
if unit != nil {
s, _ = unit.ToString()
}
return fmt.Errorf("Failed assertion for %s: %s\n\n%s", t.serviceName, strings.Join(check, " "), s)
}
return nil
@ -189,12 +192,18 @@ func (t *quadletTestcase) check(generateDir string, session *PodmanSessionIntegr
}
file := filepath.Join(generateDir, t.serviceName)
if _, err := os.Stat(file); os.IsNotExist(err) && expectFail {
return // Successful fail
_, err := os.Stat(file)
if expectFail {
Expect(err).To(MatchError(os.ErrNotExist))
} else {
Expect(err).ToNot(HaveOccurred())
}
unit, err := parser.ParseUnitFile(file)
Expect(err).To(BeNil())
var unit *parser.UnitFile
if !expectFail {
unit, err = parser.ParseUnitFile(file)
Expect(err).To(BeNil())
}
for _, check := range t.checks {
err := t.doAssert(check, unit, session)
@ -244,7 +253,7 @@ var _ = Describe("quadlet system generator", func() {
Expect(err).To(BeNil())
// Run quadlet to convert the file
session := podmanTest.Quadlet([]string{generatedDir}, quadletDir)
session := podmanTest.Quadlet([]string{"-no-kmsg-log", generatedDir}, quadletDir)
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
@ -260,6 +269,8 @@ var _ = Describe("quadlet system generator", func() {
Entry("annotation.container", "annotation.container"),
Entry("basepodman.container", "basepodman.container"),
Entry("capabilities.container", "capabilities.container"),
Entry("capabilities2.container", "capabilities2.container"),
Entry("devices.container", "devices.container"),
Entry("env.container", "env.container"),
Entry("escapes.container", "escapes.container"),
Entry("exec.container", "exec.container"),
@ -267,6 +278,7 @@ var _ = Describe("quadlet system generator", func() {
Entry("install.container", "install.container"),
Entry("label.container", "label.container"),
Entry("name.container", "name.container"),
Entry("network.container", "network.container"),
Entry("noimage.container", "noimage.container"),
Entry("noremapuser2.container", "noremapuser2.container"),
Entry("noremapuser.container", "noremapuser.container"),
@ -275,7 +287,10 @@ var _ = Describe("quadlet system generator", func() {
Entry("podmanargs.container", "podmanargs.container"),
Entry("ports.container", "ports.container"),
Entry("ports_ipv6.container", "ports_ipv6.container"),
Entry("socketactivated.container", "socketactivated.container"),
Entry("readonly-notmpfs.container", "readonly-notmpfs.container"),
Entry("readwrite.container", "readwrite.container"),
Entry("readwrite-notmpfs.container", "readwrite-notmpfs.container"),
Entry("seccomp.container", "seccomp.container"),
Entry("timezone.container", "timezone.container"),
Entry("user.container", "user.container"),
Entry("user-host.container", "user-host.container"),