Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/containers.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,14 @@ Locks are recycled and can be reused after the associated container, pod, or vol
The default number available is 2048.
If this is changed, a lock renumbering must be performed, using the `podman system renumber` command.

**platform**=""

Specifies the default platform for image operations such as pull, build, run,
and create. When set, container engines will use this platform instead of the
host's native platform. The format is `os/arch` or `os/arch/variant` (e.g.,
`linux/amd64`, `linux/arm64/v8`). If empty (the default), the host's platform
is used.

**pod_exit_policy**="continue"

Set the exit policy of the pod when the last container exits. Supported policies are:
Expand Down
44 changes: 44 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ type EngineConfig struct {
// OCIRuntimes are the set of configured OCI runtimes (default is runc).
OCIRuntimes map[string][]string `toml:"runtimes,omitempty"`

// Platform specifies the default platform (os/arch[/variant]) for image
// operations such as pull, build, run, and create. If empty, the host's
// platform is used. Format: "os/arch" or "os/arch/variant" (e.g.,
// "linux/amd64", "linux/arm64/v8").
Platform string `toml:"platform,omitempty"`

// PlatformToOCIRuntime requests specific OCI runtime for a specified platform of image.
PlatformToOCIRuntime map[string]string `toml:"platform_to_oci_runtime,omitempty"`

Expand Down Expand Up @@ -731,6 +737,40 @@ func (c *EngineConfig) ImagePlatformToRuntime(os string, arch string) string {
return c.OCIRuntime
}

// PlatformComponents parses the Platform field and returns the individual
// os, architecture, and variant components. Empty strings are returned for
// unset components. If Platform is empty, all returned values are empty.
func (c *EngineConfig) PlatformComponents() (os, arch, variant string) {
if c.Platform == "" {
return "", "", ""
}
parts := strings.SplitN(c.Platform, "/", 3)
switch len(parts) {
case 3:
return parts[0], parts[1], parts[2]
case 2:
return parts[0], parts[1], ""
default:
return parts[0], "", ""
}
}

// validatePlatform checks that the Platform field, if set, is a valid
// os/arch or os/arch/variant string.
func (c *EngineConfig) validatePlatform() error {
if c.Platform == "" {
return nil
}
parts := strings.SplitN(c.Platform, "/", 3)
if len(parts) < 2 || parts[0] == "" || parts[1] == "" {
return fmt.Errorf("invalid platform %q: must be in the format os/arch[/variant]", c.Platform)
}
if len(parts) == 3 && parts[2] == "" {
return fmt.Errorf("invalid platform %q: variant must not be empty when specified", c.Platform)
}
return nil
}

// CheckCgroupsAndAdjustConfig checks if we're running rootless with the systemd
// cgroup manager. In case the user session isn't available, we're switching the
// cgroup manager to cgroupfs. Note, this only applies to rootless.
Expand Down Expand Up @@ -845,6 +885,10 @@ func (c *EngineConfig) Validate() error {
return err
}

if err := c.validatePlatform(); err != nil {
return err
}

if err := ValidateImageVolumeMode(c.ImageVolumeMode); err != nil {
return err
}
Expand Down
64 changes: 64 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ image_copy_tmp_dir="storage"`
gomega.Expect(defaultConfig.Network.NetavarkPluginDirs.Get()).To(gomega.Equal([]string{"/usr/netavark"}))
gomega.Expect(defaultConfig.Engine.NumLocks).To(gomega.BeEquivalentTo(2048))
gomega.Expect(defaultConfig.Engine.OCIRuntimes).To(gomega.Equal(OCIRuntimeMap))
gomega.Expect(defaultConfig.Engine.Platform).To(gomega.BeEmpty())
gomega.Expect(defaultConfig.Engine.PlatformToOCIRuntime).To(gomega.Equal(PlatformToOCIRuntimeMap))
gomega.Expect(defaultConfig.Containers.HTTPProxy).To(gomega.BeFalse())
gomega.Expect(defaultConfig.Engine.NetworkCmdOptions.Get()).To(gomega.BeEmpty())
Expand Down Expand Up @@ -528,6 +529,7 @@ image_copy_tmp_dir="storage"`
gomega.Expect(config.Containers.Privileged).To(gomega.BeTrue())
gomega.Expect(config.Containers.ReadOnly).To(gomega.BeTrue())
gomega.Expect(config.Engine.ImageParallelCopies).To(gomega.Equal(uint(10)))
gomega.Expect(config.Engine.Platform).To(gomega.Equal("linux/amd64"))
gomega.Expect(config.Engine.PlatformToOCIRuntime).To(gomega.Equal(PlatformToOCIRuntimeMap))
gomega.Expect(config.Engine.ImageDefaultFormat).To(gomega.Equal("v2s2"))
gomega.Expect(config.Engine.CompressionFormat).To(gomega.BeEquivalentTo("zstd:chunked"))
Expand Down Expand Up @@ -661,6 +663,68 @@ image_copy_tmp_dir="storage"`
err = defConf.Engine.Validate()
gomega.Expect(err).To(gomega.HaveOccurred())
})

It("should succeed with valid platform", func() {
defConf, err := defaultConfig()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

defConf.Engine.Platform = "linux/amd64"
err = defConf.Engine.Validate()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

defConf.Engine.Platform = "linux/arm64/v8"
err = defConf.Engine.Validate()
gomega.Expect(err).ToNot(gomega.HaveOccurred())
})

It("should succeed with empty platform", func() {
defConf, err := defaultConfig()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

defConf.Engine.Platform = ""
err = defConf.Engine.Validate()
gomega.Expect(err).ToNot(gomega.HaveOccurred())
})

It("should fail with invalid platform", func() {
defConf, err := defaultConfig()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

defConf.Engine.Platform = "linux"
err = defConf.Engine.Validate()
gomega.Expect(err).To(gomega.HaveOccurred())

defConf.Engine.Platform = "linux/amd64/"
err = defConf.Engine.Validate()
gomega.Expect(err).To(gomega.HaveOccurred())

defConf.Engine.Platform = "/amd64"
err = defConf.Engine.Validate()
gomega.Expect(err).To(gomega.HaveOccurred())
})

It("should parse platform components correctly", func() {
defConf, err := defaultConfig()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

defConf.Engine.Platform = "linux/amd64"
os, arch, variant := defConf.Engine.PlatformComponents()
gomega.Expect(os).To(gomega.Equal("linux"))
gomega.Expect(arch).To(gomega.Equal("amd64"))
gomega.Expect(variant).To(gomega.Equal(""))

defConf.Engine.Platform = "linux/arm64/v8"
os, arch, variant = defConf.Engine.PlatformComponents()
gomega.Expect(os).To(gomega.Equal("linux"))
gomega.Expect(arch).To(gomega.Equal("arm64"))
gomega.Expect(variant).To(gomega.Equal("v8"))

defConf.Engine.Platform = ""
os, arch, variant = defConf.Engine.PlatformComponents()
gomega.Expect(os).To(gomega.Equal(""))
gomega.Expect(arch).To(gomega.Equal(""))
gomega.Expect(variant).To(gomega.Equal(""))
})
})

Describe("Service Destinations", func() {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/testdata/containers_override.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ events_container_create_inspect_data = true
pod_exit_policy="stop"
compression_format="zstd:chunked"
cdi_spec_dirs = [ "/somepath" ]
platform = "linux/amd64"

[engine.platform_to_oci_runtime]
hello = "world"
Expand Down