diff --git a/cmd/buildah/bud.go b/cmd/buildah/bud.go index 93c9acfd1..959ef9da8 100644 --- a/cmd/buildah/bud.go +++ b/cmd/buildah/bud.go @@ -301,6 +301,11 @@ func budCmd(c *cobra.Command, inputArgs []string, iopts budOptions) error { capabilities := defaultContainerConfig.Capabilities("", iopts.CapAdd, iopts.CapDrop) + os, arch, err := parse.PlatformFromOptions(c) + if err != nil { + return err + } + options := imagebuildah.BuildOptions{ ContextDirectory: contextDir, PullPolicy: pullPolicy, @@ -341,6 +346,8 @@ func budCmd(c *cobra.Command, inputArgs []string, iopts budOptions) error { Devices: devices, DefaultEnv: defaultContainerConfig.GetDefaultEnv(), SignBy: iopts.SignBy, + Architecture: arch, + OS: os, } if iopts.Quiet { diff --git a/commit.go b/commit.go index aa0ac2713..05b2437c8 100644 --- a/commit.go +++ b/commit.go @@ -300,6 +300,14 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options case archive.Gzip: systemContext.DirForceCompress = true } + + if systemContext.ArchitectureChoice != b.Architecture() { + systemContext.ArchitectureChoice = b.Architecture() + } + if systemContext.OSChoice != b.OS() { + systemContext.OSChoice = b.OS() + } + var manifestBytes []byte if manifestBytes, err = cp.Image(ctx, policyContext, maybeCachedDest, maybeCachedSrc, getCopyOptions(b.store, options.ReportWriter, nil, systemContext, "", false, options.SignBy)); err != nil { return imgID, nil, "", errors.Wrapf(err, "error copying layers and metadata for container %q", b.ContainerID) diff --git a/contrib/completions/bash/buildah b/contrib/completions/bash/buildah index e2e4eb1e4..47471d3c8 100644 --- a/contrib/completions/bash/buildah +++ b/contrib/completions/bash/buildah @@ -375,6 +375,7 @@ return 1 " local options_with_args=" + --arch --add-host --annotation --authfile @@ -410,6 +411,7 @@ return 1 --net --network --no-pivot + --os --pid --platform --runtime diff --git a/docs/buildah-bud.md b/docs/buildah-bud.md index 89b11c8f7..17fc1ee2c 100644 --- a/docs/buildah-bud.md +++ b/docs/buildah-bud.md @@ -42,6 +42,10 @@ Add an image *annotation* (e.g. annotation=*value*) to the image metadata. Can b Note: this information is not present in Docker image formats, so it is discarded when writing images in Docker formats. +**--arch**="ARCH" + +Set the ARCH instead of the architecture of the current machine in the image manifest and config. + **--authfile** *path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `buildah login`. @@ -327,6 +331,10 @@ another process. Do not use existing cached images for the container build. Build from the start with a new set of cached layers. +**--os**="OS" + +Set the OS instead of the current operating system of the machine in the image manifest and config. + **--pid** *how* Sets the configuration for PID namespaces when handling `RUN` instructions. @@ -336,11 +344,11 @@ that the PID namespace in which `buildah` itself is being run should be reused, or it can be the path to a PID namespace which is already in use by another process. -**--platform**="Linux" +**--platform**="OS/ARCH" -This option has no effect on the build. Other container engines use this option -to control the execution platform for the build (e.g., Windows, Linux) which is -not required for Buildah as it supports only Linux. +Set the OS/ARCH instead of the current operating system and architecture of the +machine in the image manifest and config (for example `linux/arm`). If +`--platform` is set, then the values of `--arch` and `--os` will be overwritten. **--pull** diff --git a/imagebuildah/build.go b/imagebuildah/build.go index 5d315ac67..14c65ca0c 100644 --- a/imagebuildah/build.go +++ b/imagebuildah/build.go @@ -160,6 +160,10 @@ type BuildOptions struct { DefaultEnv []string // SignBy is the fingerprint of a GPG key to use for signing images. SignBy string + // Architecture specifies the target architecture of the image to be built. + Architecture string + // OS is the specifies the operating system of the image to be built. + OS string } // BuildDockerfiles parses a set of one or more Dockerfiles (which may be diff --git a/imagebuildah/executor.go b/imagebuildah/executor.go index 839d91a71..c098a54f2 100644 --- a/imagebuildah/executor.go +++ b/imagebuildah/executor.go @@ -94,6 +94,8 @@ type Executor struct { capabilities []string devices []configs.Device signBy string + architecture string + os string } // NewExecutor creates a new instance of the imagebuilder.Executor interface. @@ -151,6 +153,8 @@ func NewExecutor(store storage.Store, options BuildOptions, mainNode *parser.Nod capabilities: options.Capabilities, devices: options.Devices, signBy: options.SignBy, + architecture: options.Architecture, + os: options.OS, } if exec.err == nil { exec.err = os.Stderr diff --git a/imagebuildah/stage_executor.go b/imagebuildah/stage_executor.go index 60fa10888..0fda0a124 100644 --- a/imagebuildah/stage_executor.go +++ b/imagebuildah/stage_executor.go @@ -1132,6 +1132,8 @@ func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, cr } s.builder.SetHostname(config.Hostname) s.builder.SetDomainname(config.Domainname) + s.builder.SetArchitecture(s.executor.architecture) + s.builder.SetOS(s.executor.os) s.builder.SetUser(config.User) s.builder.ClearPorts() for p := range config.ExposedPorts { diff --git a/pkg/cli/common.go b/pkg/cli/common.go index 1e74072d5..5805b681b 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/containers/buildah" + "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" "github.com/containers/common/pkg/config" "github.com/opencontainers/runtime-spec/specs-go" @@ -46,6 +47,7 @@ type NameSpaceResults struct { // BudResults represents the results for Bud flags type BudResults struct { Annotation []string + Arch string Authfile string BuildArg []string CacheFrom string @@ -61,6 +63,7 @@ type BudResults struct { Logfile string Loglevel int NoCache bool + OS string Platform string Pull bool PullAlways bool @@ -145,6 +148,7 @@ func GetLayerFlags(flags *LayerResults) pflag.FlagSet { // GetBudFlags returns common bud flags func GetBudFlags(flags *BudResults) pflag.FlagSet { fs := pflag.FlagSet{} + fs.StringVar(&flags.Arch, "arch", runtime.GOARCH, "set the ARCH instead of the architecture of the current machine in the image manifest and config") fs.StringArrayVar(&flags.Annotation, "annotation", []string{}, "Set metadata for an image (default [])") fs.StringVar(&flags.Authfile, "authfile", GetDefaultAuthFile(), "path of the authentication file.") fs.StringArrayVar(&flags.BuildArg, "build-arg", []string{}, "`argument=value` to supply to the builder") @@ -161,7 +165,8 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.BoolVar(&flags.NoCache, "no-cache", false, "Do not use existing cached images for the container build. Build from the start with a new set of cached layers.") fs.StringVar(&flags.Logfile, "logfile", "", "log to `file` instead of stdout/stderr") fs.IntVar(&flags.Loglevel, "loglevel", 0, "adjust logging level (range from -2 to 3)") - fs.StringVar(&flags.Platform, "platform", "", "CLI compatibility: no action or effect") + fs.StringVar(&flags.OS, "os", runtime.GOOS, "set the OS instead of the current operating system of the machine in the image manifest and config") + fs.StringVar(&flags.Platform, "platform", parse.DefaultPlatform(), "set the OS/ARCH instead of the current operating system and architecture of the machine in the image manifest and config (for example `linux/arm`)") fs.BoolVar(&flags.Pull, "pull", true, "pull the image from the registry if newer or not present in store, if false, only pull the image if not present") fs.BoolVar(&flags.PullAlways, "pull-always", false, "pull the image even if the named image is present in store") fs.BoolVar(&flags.PullNever, "pull-never", false, "do not pull the image, use the image present in store if available") diff --git a/pkg/parse/parse.go b/pkg/parse/parse.go index d77bd61bb..59b6f4cba 100644 --- a/pkg/parse/parse.go +++ b/pkg/parse/parse.go @@ -9,6 +9,7 @@ import ( "net" "os" "path/filepath" + "runtime" "strconv" "strings" "unicode" @@ -606,6 +607,46 @@ func getAuthFile(authfile string) string { return os.Getenv("REGISTRY_AUTH_FILE") } +// PlatformFromOptions parses the operating system (os) and architecture (arch) +// from the provided command line options. +func PlatformFromOptions(c *cobra.Command) (os, arch string, err error) { + os = runtime.GOOS + arch = runtime.GOARCH + + if selectedOS, err := c.Flags().GetString("os"); err == nil && selectedOS != runtime.GOOS { + os = selectedOS + } + if selectedArch, err := c.Flags().GetString("arch"); err == nil && selectedArch != runtime.GOARCH { + arch = selectedArch + } + + if pf, err := c.Flags().GetString("platform"); err == nil && pf != DefaultPlatform() { + selectedOS, selectedArch, err := parsePlatform(pf) + if err != nil { + return "", "", errors.Wrap(err, "unable to parse platform") + } + arch = selectedArch + os = selectedOS + } + + return os, arch, nil +} + +const platformSep = "/" + +// DefaultPlatform returns the standard platform for the current system +func DefaultPlatform() string { + return runtime.GOOS + platformSep + runtime.GOARCH +} + +func parsePlatform(platform string) (os, arch string, err error) { + split := strings.Split(platform, platformSep) + if len(split) != 2 { + return "", "", errors.Errorf("invalid platform syntax for %q (use OS/ARCH)", platform) + } + return split[0], split[1], nil +} + func parseCreds(creds string) (string, string) { if creds == "" { return "", "" diff --git a/tests/bud.bats b/tests/bud.bats index 966864cae..b5905e320 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -1750,3 +1750,48 @@ EOM run_buildah run testctr-working-container ls /file-0.0.1.txt run_buildah rm -a } + +@test "bud with custom arch" { + run_buildah bud --signature-policy ${TESTSDIR}/policy.json \ + -f ${TESTSDIR}/bud/from-scratch/Dockerfile \ + -t arch-test \ + --arch=arm + + run_buildah inspect --format "{{ .Docker.Architecture }}" arch-test + expect_output arm + + run_buildah inspect --format "{{ .OCIv1.Architecture }}" arch-test + expect_output arm +} + +@test "bud with custom os" { + run_buildah bud --signature-policy ${TESTSDIR}/policy.json \ + -f ${TESTSDIR}/bud/from-scratch/Dockerfile \ + -t os-test \ + --os=windows + + run_buildah inspect --format "{{ .Docker.OS }}" os-test + expect_output windows + + run_buildah inspect --format "{{ .OCIv1.OS }}" os-test + expect_output windows +} + +@test "bud with custom platform" { + run_buildah bud --signature-policy ${TESTSDIR}/policy.json \ + -f ${TESTSDIR}/bud/from-scratch/Dockerfile \ + -t platform-test \ + --platform=windows/arm + + run_buildah inspect --format "{{ .Docker.OS }}" platform-test + expect_output windows + + run_buildah inspect --format "{{ .OCIv1.OS }}" platform-test + expect_output windows + + run_buildah inspect --format "{{ .Docker.Architecture }}" platform-test + expect_output arm + + run_buildah inspect --format "{{ .OCIv1.Architecture }}" platform-test + expect_output arm +}