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
31 changes: 16 additions & 15 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"io/ioutil"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"

"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/image"
platform "github.com/containers/image/v5/internal/pkg/platform"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/blobinfocache"
"github.com/containers/image/v5/pkg/compression"
Expand Down Expand Up @@ -703,21 +703,22 @@ func checkImageDestinationForCurrentRuntime(ctx context.Context, sys *types.Syst
if err != nil {
return errors.Wrapf(err, "Error parsing image configuration")
}

wantedOS := runtime.GOOS
if sys != nil && sys.OSChoice != "" {
wantedOS = sys.OSChoice
}
if wantedOS != c.OS {
return fmt.Errorf("Image operating system mismatch: image uses %q, expecting %q", c.OS, wantedOS)
}

wantedArch := runtime.GOARCH
if sys != nil && sys.ArchitectureChoice != "" {
wantedArch = sys.ArchitectureChoice
wantedPlatforms, err := platform.WantedPlatforms(sys)
if err != nil {
return errors.Wrapf(err, "error getting current platform information %#v", sys)
}
if wantedArch != c.Architecture {
return fmt.Errorf("Image architecture mismatch: image uses %q, expecting %q", c.Architecture, wantedArch)
for _, wantedPlatform := range wantedPlatforms {
if wantedPlatform.OS != c.OS {
return fmt.Errorf("Image operating system mismatch: image uses %q, expecting %q", c.OS, wantedPlatform.OS)
}
if wantedPlatform.Architecture != c.Architecture {
return fmt.Errorf("Image architecture mismatch: image uses %q, expecting %q", c.Architecture, wantedPlatform.Architecture)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now needs to just use logrus.Infof instead of failing.

}
/*
// TODO Waiting for https://github.com/opencontainers/image-spec/pull/777
if wantedPlatform.Variant != "" && c.Variant != "" && wantedPlatform.Variant != c.Variant {
return fmt.Errorf("Image variant mismatch: image uses %q, expecting %q", c.Variant, wantedPlatform.Variant)
}*/
}
}
return nil
Expand Down
10 changes: 0 additions & 10 deletions image/fixtures/schema2list.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@
"variant": "v6"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 527,
"digest": "sha256:a8fe0549cac196f439de3bf2b57af14f7cd4e59915ccd524428f588628a4ef31",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 527,
Expand Down
184 changes: 184 additions & 0 deletions internal/pkg/platform/platform_matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package platform

// Largely based on
// https://github.com/moby/moby/blob/bc846d2e8fe5538220e0c31e9d0e8446f6fbc022/distribution/cpuinfo_unix.go
// https://github.com/containerd/containerd/blob/726dcaea50883e51b2ec6db13caff0e7936b711d/platforms/cpuinfo.go

import (
"bufio"
"fmt"
"os"
"runtime"
"strings"

"github.com/containers/image/v5/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)

// For Linux, the kernel has already detected the ABI, ISA and Features.
// So we don't need to access the ARM registers to detect platform information
// by ourselves. We can just parse these information from /proc/cpuinfo
func getCPUInfo(pattern string) (info string, err error) {
if runtime.GOOS != "linux" {
return "", fmt.Errorf("getCPUInfo for OS %s not implemented", runtime.GOOS)
}

cpuinfo, err := os.Open("/proc/cpuinfo")
if err != nil {
return "", err
}
defer cpuinfo.Close()

// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
// the first core is enough.
scanner := bufio.NewScanner(cpuinfo)
for scanner.Scan() {
newline := scanner.Text()
list := strings.Split(newline, ":")

if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
return strings.TrimSpace(list[1]), nil
}
}

// Check whether the scanner encountered errors
err = scanner.Err()
if err != nil {
return "", err
}

return "", fmt.Errorf("getCPUInfo for pattern: %s not found", pattern)
}

func getCPUVariantWindows() string {
// Windows only supports v7 for ARM32 and v8 for ARM64 and so we can use
// runtime.GOARCH to determine the variants
var variant string
switch runtime.GOARCH {
case "arm64":
variant = "v8"
case "arm":
variant = "v7"
default:
variant = ""
}

return variant
}

func getCPUVariantArm() string {
variant, err := getCPUInfo("Cpu architecture")
if err != nil {
return ""
}
// TODO handle RPi Zero mismatch (https://github.com/moby/moby/pull/36121#issuecomment-398328286)

switch strings.ToLower(variant) {
case "8", "aarch64":
variant = "v8"
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
variant = "v7"
case "6", "6tej":
variant = "v6"
case "5", "5t", "5te", "5tej":
variant = "v5"
case "4", "4t":
variant = "v4"
case "3":
variant = "v3"
default:
variant = ""
}

return variant
}

func getCPUVariant(os string, arch string) string {
if os == "windows" {
return getCPUVariantWindows()
}
if arch == "arm" || arch == "arm64" {
return getCPUVariantArm()
}
return ""
}

var compatibility = map[string][]string{
"arm": {"v7", "v6", "v5"},
"arm64": {"v8"},
}

// Returns all compatible platforms with the platform specifics possibly overriden by user,
// the most compatible platform is first.
// If some option (arch, os, variant) is not present, a value from current platform is detected.
func WantedPlatforms(ctx *types.SystemContext) ([]imgspecv1.Platform, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: This can currently never fail, and it’s an internal API. The error return value could be dropped, which would simplify callers a bit.

wantedArch := runtime.GOARCH
if ctx != nil && ctx.ArchitectureChoice != "" {
wantedArch = ctx.ArchitectureChoice
}
wantedOS := runtime.GOOS
if ctx != nil && ctx.OSChoice != "" {
wantedOS = ctx.OSChoice
}

wantedVariant := getCPUVariant(runtime.GOOS, runtime.GOARCH)
if ctx != nil && ctx.VariantChoice != "" {
wantedVariant = ctx.VariantChoice
}

var wantedPlatforms []imgspecv1.Platform
if wantedVariant != "" && compatibility[wantedArch] != nil {
wantedPlatforms = make([]imgspecv1.Platform, 0, len(compatibility[wantedArch]))
wantedIndex := -1
for i, v := range compatibility[wantedArch] {
if wantedVariant == v {
wantedIndex = i
break
}
}
// user wants a variant which we know nothing about - not even compatibility
if wantedIndex == -1 {
wantedPlatforms = []imgspecv1.Platform{
{
OS: wantedOS,
Architecture: wantedArch,
Variant: wantedVariant,
},
}
} else {
for i := wantedIndex; i < len(compatibility[wantedArch]); i++ {
v := compatibility[wantedArch][i]
wantedPlatforms = append(wantedPlatforms, imgspecv1.Platform{
OS: wantedOS,
Architecture: wantedArch,
Variant: v,
})
}
}
} else {
wantedPlatforms = []imgspecv1.Platform{
{
OS: wantedOS,
Architecture: wantedArch,
Variant: wantedVariant,
},
}
}

return wantedPlatforms, nil
}

func MatchesPlatform(image imgspecv1.Platform, wanted imgspecv1.Platform) bool {
if image.Architecture != wanted.Architecture {
return false
}
if image.OS != wanted.OS {
return false
}

if wanted.Variant == "" || image.Variant == wanted.Variant {
return true
}

return false
}
46 changes: 46 additions & 0 deletions internal/pkg/platform/platform_matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package platform

import (
"testing"

"github.com/containers/image/v5/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)

func TestWantedPlatformsCompatibility(t *testing.T) {
ctx := &types.SystemContext{
ArchitectureChoice: "arm",
OSChoice: "linux",
VariantChoice: "v6",
}
platforms, err := WantedPlatforms(ctx)
assert.Nil(t, err)
assert.Equal(t, len(platforms), 2)
assert.Equal(t, platforms[0], imgspecv1.Platform{
OS: ctx.OSChoice,
Architecture: ctx.ArchitectureChoice,
Variant: "v6",
})
assert.Equal(t, platforms[1], imgspecv1.Platform{
OS: ctx.OSChoice,
Architecture: ctx.ArchitectureChoice,
Variant: "v5",
})
}

func TestWantedPlatformsCustom(t *testing.T) {
ctx := &types.SystemContext{
ArchitectureChoice: "armel",
OSChoice: "freeBSD",
VariantChoice: "custom",
}
platforms, err := WantedPlatforms(ctx)
assert.Nil(t, err)
assert.Equal(t, len(platforms), 1)
assert.Equal(t, platforms[0], imgspecv1.Platform{
OS: ctx.OSChoice,
Architecture: ctx.ArchitectureChoice,
Variant: ctx.VariantChoice,
})
}
30 changes: 17 additions & 13 deletions manifest/docker_schema2_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package manifest
import (
"encoding/json"
"fmt"
"runtime"

platform "github.com/containers/image/v5/internal/pkg/platform"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -92,21 +92,25 @@ func (list *Schema2List) UpdateInstances(updates []ListUpdate) error {
// ChooseInstance parses blob as a schema2 manifest list, and returns the digest
// of the image which is appropriate for the current environment.
func (list *Schema2List) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) {
wantedArch := runtime.GOARCH
if ctx != nil && ctx.ArchitectureChoice != "" {
wantedArch = ctx.ArchitectureChoice
}
wantedOS := runtime.GOOS
if ctx != nil && ctx.OSChoice != "" {
wantedOS = ctx.OSChoice
wantedPlatforms, err := platform.WantedPlatforms(ctx)
if err != nil {
return "", errors.Wrapf(err, "error getting platform information %#v", ctx)
}

for _, d := range list.Manifests {
if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
return d.Digest, nil
for _, wantedPlatform := range wantedPlatforms {
for _, d := range list.Manifests {
imagePlatform := imgspecv1.Platform{
Architecture: d.Platform.Architecture,
OS: d.Platform.OS,
OSVersion: d.Platform.OSVersion,
OSFeatures: dupStringSlice(d.Platform.OSFeatures),
Variant: d.Platform.Variant,
}
if platform.MatchesPlatform(imagePlatform, wantedPlatform) {
return d.Digest, nil
}
}
}
return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS)
return "", fmt.Errorf("no image found in manifest list for architecture %s, variant %s, OS %s", wantedPlatforms[0].Architecture, wantedPlatforms[0].Variant, wantedPlatforms[0].OS)
}

// Serialize returns the list in a blob format.
Expand Down
13 changes: 8 additions & 5 deletions manifest/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ func TestChooseInstance(t *testing.T) {
matchedInstances: map[string]digest.Digest{
"amd64": "sha256:030fcb92e1487b18c974784dcc110a93147c9fc402188370fbfd17efabffc6af",
"s390x": "sha256:e5aa1b0a24620228b75382997a0977f609b3ca3a95533dafdef84c74cc8df642",
// There are several "arm" images with different variants;
// the current code returns the first match. NOTE: This is NOT an API promise.
"arm": "sha256:9142d97ef280a7953cf1a85716de49a24cc1dd62776352afad67e635331ff77a",
"arm": "sha256:b5dbad4bdb4444d919294afe49a095c23e86782f98cdf0aa286198ddb814b50b",
"arm64": "sha256:dc472a59fb006797aa2a6bfb54cc9c57959bb0a6d11fadaa608df8c16dea39cf",
},
unmatchedInstances: []string{
"unmatched",
Expand All @@ -101,10 +100,14 @@ func TestChooseInstance(t *testing.T) {
require.NoError(t, err)
// Match found
for arch, expected := range manifestList.matchedInstances {
digest, err := list.ChooseInstance(&types.SystemContext{
ctx := &types.SystemContext{
ArchitectureChoice: arch,
OSChoice: "linux",
})
}
if arch == "arm" {
ctx.VariantChoice = "v7"
}
digest, err := list.ChooseInstance(ctx)
require.NoError(t, err, arch)
assert.Equal(t, expected, digest)
}
Expand Down
Loading