diff --git a/client/llb/imagemetaresolver/resolver.go b/client/llb/imagemetaresolver/resolver.go index e489ecf471d8..6a0c6dc31bec 100644 --- a/client/llb/imagemetaresolver/resolver.go +++ b/client/llb/imagemetaresolver/resolver.go @@ -107,7 +107,7 @@ func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string func (imr *imageMetaResolver) key(ref string, platform *ocispecs.Platform) string { if platform != nil { - ref += platforms.Format(*platform) + ref += platforms.FormatAll(*platform) } return ref } diff --git a/exporter/containerimage/annotations.go b/exporter/containerimage/annotations.go index 5973f628ad16..1bf10f6c15db 100644 --- a/exporter/containerimage/annotations.go +++ b/exporter/containerimage/annotations.go @@ -71,7 +71,7 @@ func (ag AnnotationsGroup) Platform(p *ocispecs.Platform) *Annotations { ps := []string{""} if p != nil { - ps = append(ps, platforms.Format(*p)) + ps = append(ps, platforms.FormatAll(*p)) } for _, a := range ag { diff --git a/exporter/containerimage/exptypes/annotations.go b/exporter/containerimage/exptypes/annotations.go index 37f1b205d15e..a9b74d6b9681 100644 --- a/exporter/containerimage/exptypes/annotations.go +++ b/exporter/containerimage/exptypes/annotations.go @@ -49,7 +49,7 @@ func (k AnnotationKey) PlatformString() string { if k.Platform == nil { return "" } - return platforms.Format(*k.Platform) + return platforms.FormatAll(*k.Platform) } func AnnotationIndexKey(key string) string { diff --git a/exporter/containerimage/exptypes/parse.go b/exporter/containerimage/exptypes/parse.go index bd6222338ef4..cd2256b566e9 100644 --- a/exporter/containerimage/exptypes/parse.go +++ b/exporter/containerimage/exptypes/parse.go @@ -53,7 +53,7 @@ func ParsePlatforms(meta map[string][]byte) (Platforms, error) { } } p = platforms.Normalize(p) - pk := platforms.Format(p) + pk := platforms.FormatAll(p) ps := Platforms{ Platforms: []Platform{{ID: pk, Platform: p}}, } diff --git a/exporter/verifier/platforms.go b/exporter/verifier/platforms.go index 5144b78e8e54..6f60ca62d112 100644 --- a/exporter/verifier/platforms.go +++ b/exporter/verifier/platforms.go @@ -46,13 +46,14 @@ func CheckInvalidPlatforms[T comparable](ctx context.Context, res *result.Result }) } p = platforms.Normalize(p) - _, ok := reqMap[platforms.Format(p)] + formatted := platforms.FormatAll(p) + _, ok := reqMap[formatted] if ok { warnings = append(warnings, client.VertexWarning{ Short: []byte(fmt.Sprintf("Duplicate platform result requested %q", v)), }) } - reqMap[platforms.Format(p)] = struct{}{} + reqMap[formatted] = struct{}{} reqList = append(reqList, exptypes.Platform{Platform: p}) } @@ -62,9 +63,9 @@ func CheckInvalidPlatforms[T comparable](ctx context.Context, res *result.Result if len(reqMap) == 1 && len(ps.Platforms) == 1 { pp := platforms.Normalize(ps.Platforms[0].Platform) - if _, ok := reqMap[platforms.Format(pp)]; !ok { + if _, ok := reqMap[platforms.FormatAll(pp)]; !ok { return []client.VertexWarning{{ - Short: []byte(fmt.Sprintf("Requested platform %q does not match result platform %q", req.Platforms[0], platforms.Format(pp))), + Short: []byte(fmt.Sprintf("Requested platform %q does not match result platform %q", req.Platforms[0], platforms.FormatAll(pp))), }}, nil } return nil, nil @@ -81,7 +82,7 @@ func CheckInvalidPlatforms[T comparable](ctx context.Context, res *result.Result if !mismatch { for _, p := range ps.Platforms { pp := platforms.Normalize(p.Platform) - if _, ok := reqMap[platforms.Format(pp)]; !ok { + if _, ok := reqMap[platforms.FormatAll(pp)]; !ok { mismatch = true break } @@ -100,7 +101,7 @@ func CheckInvalidPlatforms[T comparable](ctx context.Context, res *result.Result func platformsString(ps []exptypes.Platform) string { var ss []string for _, p := range ps { - ss = append(ss, platforms.Format(platforms.Normalize(p.Platform))) + ss = append(ss, platforms.FormatAll(platforms.Normalize(p.Platform))) } sort.Strings(ss) return strings.Join(ss, ",") diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go index 31ca7381c761..62b76bd5e22e 100644 --- a/frontend/dockerfile/builder/build.go +++ b/frontend/dockerfile/builder/build.go @@ -160,7 +160,7 @@ func Build(ctx context.Context, c client.Client) (_ *client.Result, err error) { if platform != nil { p = *platform } - scanTargets.Store(platforms.Format(platforms.Normalize(p)), scanTarget) + scanTargets.Store(platforms.FormatAll(platforms.Normalize(p)), scanTarget) return ref, img, baseImg, nil }) diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 934c3a338e3d..c3b06e73fc66 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -519,7 +519,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS if reachable { prefix := "[" if opt.MultiPlatformRequested && platform != nil { - prefix += platforms.Format(*platform) + " " + prefix += platforms.FormatAll(*platform) + " " } prefix += "internal]" mutRef, dgst, dt, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName, sourceresolver.Opt{ @@ -2102,7 +2102,7 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform } out := "[" if prefixPlatform && platform != nil { - out += platforms.Format(*platform) + formatTargetPlatform(*platform, platformFromEnv(env)) + " " + out += platforms.FormatAll(*platform) + formatTargetPlatform(*platform, platformFromEnv(env)) + " " } if ds.stageName != "" { out += ds.stageName + " " @@ -2136,7 +2136,7 @@ func formatTargetPlatform(base ocispecs.Platform, target *ocispecs.Platform) str return "->" + archVariant } if p.OS != base.OS { - return "->" + platforms.Format(p) + return "->" + platforms.FormatAll(p) } return "" } @@ -2483,8 +2483,8 @@ func wrapSuggestAny(err error, keys map[string]struct{}, options []string) error func validateBaseImagePlatform(name string, expected, actual ocispecs.Platform, location []parser.Range, lint *linter.Linter) { if expected.OS != actual.OS || expected.Architecture != actual.Architecture { - expectedStr := platforms.Format(platforms.Normalize(expected)) - actualStr := platforms.Format(platforms.Normalize(actual)) + expectedStr := platforms.FormatAll(platforms.Normalize(expected)) + actualStr := platforms.FormatAll(platforms.Normalize(actual)) msg := linter.RuleInvalidBaseImagePlatform.Format(name, expectedStr, actualStr) lint.Run(&linter.RuleInvalidBaseImagePlatform, location, msg) } diff --git a/frontend/dockerfile/dockerfile2llb/platform.go b/frontend/dockerfile/dockerfile2llb/platform.go index 5eb99d919a10..8276a782950b 100644 --- a/frontend/dockerfile/dockerfile2llb/platform.go +++ b/frontend/dockerfile/dockerfile2llb/platform.go @@ -45,10 +45,12 @@ func defaultArgs(po *platformOpt, overrides map[string]string, target string) *l s := [...][2]string{ {"BUILDPLATFORM", platforms.Format(bp)}, {"BUILDOS", bp.OS}, + {"BUILDOSVERSION", bp.OSVersion}, {"BUILDARCH", bp.Architecture}, {"BUILDVARIANT", bp.Variant}, {"TARGETPLATFORM", platforms.Format(tp)}, {"TARGETOS", tp.OS}, + {"TARGETOSVERSION", tp.OSVersion}, {"TARGETARCH", tp.Architecture}, {"TARGETVARIANT", tp.Variant}, {"TARGETSTAGE", target}, diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 2e7eba86715b..5ac35c2e76a6 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -214,6 +214,7 @@ var allTests = integration.TestFuncs( testTargetStageNameArg, testStepNames, testPowershellInDefaultPathOnWindows, + testPlatformWithOSVersion, ) // Tests that depend on the `security.*` entitlements @@ -5000,7 +5001,7 @@ ONBUILD RUN mkdir \out && echo 11>> \out\foo require.NoError(t, err) dockerfile = []byte(fmt.Sprintf(` - FROM %s + FROM %s `, target)) dir = integration.Tmpdir( @@ -5218,9 +5219,9 @@ func testOnBuildNamedContext(t *testing.T, sb integration.Sandbox) { dockerfile := []byte(` FROM alpine AS otherstage RUN echo -n "hello" > /testfile - + FROM base AS inputstage - + FROM scratch COPY --from=inputstage /out/foo /bar `) @@ -6811,7 +6812,7 @@ RUN cat /etc/hosts | grep foo RUN echo $HOSTNAME | grep foo RUN echo $(hostname) | grep foo `, - ` + ` FROM nanoserver RUN reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v Hostname | findstr "foo" `, @@ -7463,7 +7464,7 @@ func testNamedImageContextScratch(t *testing.T, sb integration.Sandbox) { defer c.Close() dockerfile := []byte(fmt.Sprintf( - ` + ` FROM %s AS build COPY < r2 - } - return m1 && !m2 -} - -func revision(v string) int { - parts := strings.Split(v, ".") - if len(parts) < 4 { - return 0 - } - r, err := strconv.Atoi(parts[3]) - if err != nil { - return 0 - } - return r -} - -func prefix(v string) string { - parts := strings.Split(v, ".") - if len(parts) < 4 { - return v - } - return strings.Join(parts[0:3], ".") -} - -// Default returns the current platform's default platform specification. +// Default returns the default matcher for the platform. func Default() MatchComparer { return Only(DefaultSpec()) } diff --git a/vendor/github.com/containerd/platforms/platform_compat_windows.go b/vendor/github.com/containerd/platforms/platform_windows_compat.go similarity index 64% rename from vendor/github.com/containerd/platforms/platform_compat_windows.go rename to vendor/github.com/containerd/platforms/platform_windows_compat.go index 89e66f0c0903..c3b3b0f7e794 100644 --- a/vendor/github.com/containerd/platforms/platform_compat_windows.go +++ b/vendor/github.com/containerd/platforms/platform_windows_compat.go @@ -16,9 +16,16 @@ package platforms +import ( + "strconv" + "strings" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + // osVersion is a wrapper for Windows version information // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx -type osVersion struct { +type windowsOSVersion struct { Version uint32 MajorVersion uint8 MinorVersion uint8 @@ -55,7 +62,7 @@ var compatLTSCReleases = []uint16{ // Every release after WS 2022 will support the previous ltsc // container image. Stable ABI is in preview mode for windows 11 client. // Refer: https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2022%2Cwindows-10#windows-server-host-os-compatibility -func checkHostAndContainerCompat(host, ctr osVersion) bool { +func checkWindowsHostAndContainerCompat(host, ctr windowsOSVersion) bool { // check major minor versions of host and guest if host.MajorVersion != ctr.MajorVersion || host.MinorVersion != ctr.MinorVersion { @@ -76,3 +83,65 @@ func checkHostAndContainerCompat(host, ctr osVersion) bool { } return ctr.Build >= supportedLtscRelease && ctr.Build <= host.Build } + +func getWindowsOSVersion(osVersionPrefix string) windowsOSVersion { + parts := strings.Split(osVersionPrefix, ".") + if len(parts) < 3 { + return windowsOSVersion{} + } + + majorVersion, _ := strconv.Atoi(parts[0]) + minorVersion, _ := strconv.Atoi(parts[1]) + buildNumber, _ := strconv.Atoi(parts[2]) + + return windowsOSVersion{ + MajorVersion: uint8(majorVersion), + MinorVersion: uint8(minorVersion), + Build: uint16(buildNumber), + } +} + +func winRevision(v string) int { + parts := strings.Split(v, ".") + if len(parts) < 4 { + return 0 + } + r, err := strconv.Atoi(parts[3]) + if err != nil { + return 0 + } + return r +} + +func winPrefix(v string) string { + parts := strings.Split(v, ".") + if len(parts) < 4 { + return v + } + return strings.Join(parts[0:3], ".") +} + +type windowsVersionMatcher struct { + windowsOSVersion +} + +func (m *windowsVersionMatcher) Match(v string) bool { + if m.Version == 0 || v == "" { + return true + } + osv := getWindowsOSVersion(v) + return checkWindowsHostAndContainerCompat(m.windowsOSVersion, osv) +} + +type windowsMatchComparer struct { + Matcher +} + +func (c *windowsMatchComparer) Less(p1, p2 specs.Platform) bool { + m1, m2 := c.Match(p1), c.Match(p2) + if m1 && m2 { + r1, r2 := winRevision(p1.OSVersion), winRevision(p2.OSVersion) + return r1 > r2 + } + return m1 && !m2 +} diff --git a/vendor/github.com/containerd/platforms/platforms.go b/vendor/github.com/containerd/platforms/platforms.go index 1bbbdb91dbc2..a81f2c09b3d9 100644 --- a/vendor/github.com/containerd/platforms/platforms.go +++ b/vendor/github.com/containerd/platforms/platforms.go @@ -144,18 +144,59 @@ type Matcher interface { // // Applications should opt to use `Match` over directly parsing specifiers. func NewMatcher(platform specs.Platform) Matcher { - return newDefaultMatcher(platform) + m := &matcher{ + Platform: Normalize(platform), + } + + if platform.OS == "windows" { + m.osvM = &windowsVersionMatcher{ + windowsOSVersion: getWindowsOSVersion(winPrefix(platform.OSVersion)), + } + // In prior versions, on windows, the returned matcher implements a + // MatchComprarer interface. + // This preserves that behavior for backwards compatibility. + // + // TODO: This isn't actually used in this package at all, which may have been + // an unintended side of some refactor. + // I suspect that it was intended to be used in `Ordered` but it is not since + // `Less` that is implemented here ends up getting masked due to wrapping. + if runtime.GOOS == "windows" { + return &windowsMatchComparer{m} + } + } + return m +} + +type osVerMatcher interface { + Match(string) bool } type matcher struct { specs.Platform + osvM osVerMatcher } func (m *matcher) Match(platform specs.Platform) bool { normalized := Normalize(platform) return m.OS == normalized.OS && m.Architecture == normalized.Architecture && - m.Variant == normalized.Variant + m.Variant == normalized.Variant && + m.matchOSVersion(platform) +} + +func (m *matcher) matchOSVersion(platform specs.Platform) bool { + if m.osvM != nil { + return m.osvM.Match(platform.OSVersion) + } + + if m.OSVersion == "" || platform.OSVersion == "" { + return true + } + + if m.OSVersion == platform.OSVersion { + return true + } + return false } func (m *matcher) String() string { diff --git a/vendor/github.com/containerd/platforms/platforms_other.go b/vendor/github.com/containerd/platforms/platforms_other.go deleted file mode 100644 index 03f4dcd99814..000000000000 --- a/vendor/github.com/containerd/platforms/platforms_other.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build !windows - -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package platforms - -import ( - specs "github.com/opencontainers/image-spec/specs-go/v1" -) - -// NewMatcher returns the default Matcher for containerd -func newDefaultMatcher(platform specs.Platform) Matcher { - return &matcher{ - Platform: Normalize(platform), - } -} diff --git a/vendor/github.com/containerd/platforms/platforms_windows.go b/vendor/github.com/containerd/platforms/platforms_windows.go deleted file mode 100644 index 950e2a2ddbb5..000000000000 --- a/vendor/github.com/containerd/platforms/platforms_windows.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package platforms - -import ( - specs "github.com/opencontainers/image-spec/specs-go/v1" -) - -// NewMatcher returns a Windows matcher that will match on osVersionPrefix if -// the platform is Windows otherwise use the default matcher -func newDefaultMatcher(platform specs.Platform) Matcher { - prefix := prefix(platform.OSVersion) - return windowsmatcher{ - Platform: platform, - osVersionPrefix: prefix, - defaultMatcher: &matcher{ - Platform: Normalize(platform), - }, - } -}