Skip to content

Commit 9123b5c

Browse files
committed
image build: pass built-in args to dockerfile-json
Signed-off-by: Adam Cmiel <acmiel@redhat.com>
1 parent f0333aa commit 9123b5c

File tree

5 files changed

+166
-4
lines changed

5 files changed

+166
-4
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.3
55
toolchain go1.24.6
66

77
require (
8+
github.com/containerd/platforms v1.0.0-rc.1
89
github.com/containers/image/v5 v5.36.2
910
github.com/keilerkonzept/dockerfile-json v1.2.2
1011
github.com/onsi/gomega v1.38.0
@@ -16,6 +17,7 @@ require (
1617

1718
require (
1819
github.com/agext/levenshtein v1.2.3 // indirect
20+
github.com/containerd/log v0.1.0 // indirect
1921
github.com/containerd/typeurl/v2 v2.2.3 // indirect
2022
github.com/containers/storage v1.59.1 // indirect
2123
github.com/docker/go-units v0.5.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7r
22
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
33
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
44
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
5+
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
6+
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
7+
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
8+
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
59
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
610
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
711
github.com/containers/image/v5 v5.36.2 h1:GcxYQyAHRF/pLqR4p4RpvKllnNL8mOBn0eZnqJbfTwk=

integration_tests/build_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,80 @@ LABEL test.label="build-args-test"
571571
Expect(containerfileLabels).To(ContainElements(expectedLabels))
572572
})
573573

574+
t.Run("PlatformBuildArgs", func(t *testing.T) {
575+
contextDir := setupTestContext(t)
576+
577+
writeContainerfile(contextDir, `
578+
FROM scratch
579+
580+
ARG TARGETPLATFORM
581+
ARG TARGETOS
582+
ARG TARGETARCH
583+
ARG TARGETVARIANT
584+
ARG BUILDPLATFORM
585+
ARG BUILDOS
586+
ARG BUILDARCH
587+
ARG BUILDVARIANT
588+
589+
LABEL TARGETPLATFORM=$TARGETPLATFORM
590+
LABEL TARGETOS=$TARGETOS
591+
LABEL TARGETARCH=$TARGETARCH
592+
LABEL TARGETVARIANT=$TARGETVARIANT
593+
LABEL BUILDPLATFORM=$BUILDPLATFORM
594+
LABEL BUILDOS=$BUILDOS
595+
LABEL BUILDARCH=$BUILDARCH
596+
LABEL BUILDVARIANT=$BUILDVARIANT
597+
598+
LABEL test.label="platform-build-args-test"
599+
`)
600+
601+
outputRef := "localhost/test-image-platform-build-args:" + GenerateUniqueTag(t)
602+
containerfileJsonPath := "/workspace/parsed-containerfile.json"
603+
604+
buildParams := BuildParams{
605+
Context: contextDir,
606+
OutputRef: outputRef,
607+
Push: false,
608+
ContainerfileJsonOutput: containerfileJsonPath,
609+
}
610+
611+
container := setupBuildContainerWithCleanup(t, buildParams, nil)
612+
613+
err := runBuild(container, buildParams)
614+
Expect(err).ToNot(HaveOccurred())
615+
616+
// Verify platform values match between the parsed Containerfile and the actual image
617+
imageLabels := getImageLabels(container, outputRef)
618+
containerfileLabels := getContainerfileLabels(container, containerfileJsonPath)
619+
620+
labelsToCheck := []string{
621+
"TARGETPLATFORM",
622+
"TARGETOS",
623+
"TARGETARCH",
624+
"TARGETVARIANT",
625+
"BUILDPLATFORM",
626+
"BUILDOS",
627+
"BUILDARCH",
628+
"BUILDVARIANT",
629+
}
630+
631+
for _, label := range labelsToCheck {
632+
imageLabel := imageLabels[label]
633+
containerfileLabel := containerfileLabels[label]
634+
635+
// the *VARIANT values will be empty on platforms other than ARM
636+
expectEmpty := strings.HasSuffix(label, "VARIANT") && runtime.GOARCH != "arm"
637+
if !expectEmpty {
638+
Expect(imageLabel).ToNot(BeEmpty(), fmt.Sprintf("label %s is empty on the built image", label))
639+
Expect(containerfileLabel).ToNot(BeEmpty(), fmt.Sprintf("label %s is empty in the containerfile JSON", label))
640+
}
641+
642+
Expect(imageLabel).To(Equal(containerfileLabel),
643+
fmt.Sprintf("image label: %s=%s; containerfile label: %s=%s", label, imageLabel, label, containerfileLabel),
644+
)
645+
}
646+
})
647+
574648
t.Run("ContainerfileJsonOutput", func(t *testing.T) {
575649
contextDir := setupTestContext(t)
576650

pkg/commands/build.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/konflux-ci/konflux-build-cli/pkg/common"
1414
"github.com/spf13/cobra"
1515

16+
"github.com/containerd/platforms"
1617
"github.com/keilerkonzept/dockerfile-json/pkg/buildargs"
1718
"github.com/keilerkonzept/dockerfile-json/pkg/dockerfile"
1819
l "github.com/konflux-ci/konflux-build-cli/pkg/logger"
@@ -426,9 +427,21 @@ func (c *Build) parseContainerfile() (*dockerfile.Dockerfile, error) {
426427
}
427428

428429
func (c *Build) createBuildArgExpander() (dockerfile.SingleWordExpander, error) {
429-
args := make(map[string]string)
430-
431-
// Load from --build-args-file
430+
// Define built-in ARG variables
431+
// See https://docs.docker.com/build/building/variables/#multi-platform-build-arguments
432+
platform := platforms.DefaultSpec()
433+
args := map[string]string{
434+
"TARGETPLATFORM": platforms.Format(platform),
435+
"TARGETOS": platform.OS,
436+
"TARGETARCH": platform.Architecture,
437+
"TARGETVARIANT": platform.Variant,
438+
"BUILDPLATFORM": platforms.Format(platform),
439+
"BUILDOS": platform.OS,
440+
"BUILDARCH": platform.Architecture,
441+
"BUILDVARIANT": platform.Variant,
442+
}
443+
444+
// Load from --build-args-file, can override built-in args
432445
if c.Params.BuildArgsFile != "" {
433446
fileArgs, err := buildargs.ParseBuildArgFile(c.Params.BuildArgsFile)
434447
if err != nil {
@@ -437,7 +450,7 @@ func (c *Build) createBuildArgExpander() (dockerfile.SingleWordExpander, error)
437450
maps.Copy(args, fileArgs)
438451
}
439452

440-
// CLI --build-args take precedence
453+
// CLI --build-args take precedence over everything else
441454
for _, arg := range c.Params.BuildArgs {
442455
key, value, hasValue := strings.Cut(arg, "=")
443456
if hasValue {

pkg/commands/build_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,75 @@ func Test_Build_createBuildArgExpander(t *testing.T) {
673673
g.Expect(value).To(Equal("from-file"))
674674
})
675675

676+
t.Run("should provide built-in platform args by default", func(t *testing.T) {
677+
c := &Build{
678+
Params: &BuildParams{},
679+
}
680+
681+
expander, err := c.createBuildArgExpander()
682+
g.Expect(err).ToNot(HaveOccurred())
683+
684+
// Check that all built-in platform args are available
685+
platformArgs := []string{
686+
"TARGETPLATFORM", "TARGETOS", "TARGETARCH", "TARGETVARIANT",
687+
"BUILDPLATFORM", "BUILDOS", "BUILDARCH", "BUILDVARIANT",
688+
}
689+
690+
for _, arg := range platformArgs {
691+
value, err := expander(arg)
692+
// TARGETVARIANT and BUILDVARIANT can be empty on non-ARM platforms
693+
if arg == "TARGETVARIANT" || arg == "BUILDVARIANT" {
694+
g.Expect(err).ToNot(HaveOccurred())
695+
} else {
696+
g.Expect(err).ToNot(HaveOccurred())
697+
g.Expect(value).ToNot(BeEmpty(), "arg %s should not be empty", arg)
698+
}
699+
}
700+
})
701+
702+
t.Run("should allow file args to override built-in platform args", func(t *testing.T) {
703+
tempDir := t.TempDir()
704+
testutil.WriteFileTree(t, tempDir, map[string]string{
705+
"build-args": "TARGETOS=custom-os\nTARGETARCH=custom-arch\n",
706+
})
707+
708+
c := &Build{
709+
Params: &BuildParams{
710+
BuildArgsFile: filepath.Join(tempDir, "build-args"),
711+
},
712+
}
713+
714+
expander, err := c.createBuildArgExpander()
715+
g.Expect(err).ToNot(HaveOccurred())
716+
717+
value, err := expander("TARGETOS")
718+
g.Expect(err).ToNot(HaveOccurred())
719+
g.Expect(value).To(Equal("custom-os"))
720+
721+
value, err = expander("TARGETARCH")
722+
g.Expect(err).ToNot(HaveOccurred())
723+
g.Expect(value).To(Equal("custom-arch"))
724+
})
725+
726+
t.Run("should allow CLI args to override built-in platform args", func(t *testing.T) {
727+
c := &Build{
728+
Params: &BuildParams{
729+
BuildArgs: []string{"TARGETOS=custom-os", "TARGETARCH=custom-arch"},
730+
},
731+
}
732+
733+
expander, err := c.createBuildArgExpander()
734+
g.Expect(err).ToNot(HaveOccurred())
735+
736+
value, err := expander("TARGETOS")
737+
g.Expect(err).ToNot(HaveOccurred())
738+
g.Expect(value).To(Equal("custom-os"))
739+
740+
value, err = expander("TARGETARCH")
741+
g.Expect(err).ToNot(HaveOccurred())
742+
g.Expect(value).To(Equal("custom-arch"))
743+
})
744+
676745
t.Run("should return error for undefined build arg", func(t *testing.T) {
677746
c := &Build{
678747
Params: &BuildParams{},

0 commit comments

Comments
 (0)