 90cd777a6c
			
		
	
	90cd777a6c
	
	
	
		
			
			arm has been supported, but something is missing, causes test failure
    --- FAIL: TestParseSelector/linux (0.00s)
        platforms_test.go:292: arm support not fully implemented: not implemented
    --- FAIL: TestParseSelector/macOS (0.00s)
        platforms_test.go:292: arm support not fully implemented: not implemented
Signed-off-by: Shengjing Zhu <zhsj@debian.org>
		
	
		
			
				
	
	
		
			279 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			279 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    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 provides a toolkit for normalizing, matching and
 | |
| // specifying container platforms.
 | |
| //
 | |
| // Centered around OCI platform specifications, we define a string-based
 | |
| // specifier syntax that can be used for user input. With a specifier, users
 | |
| // only need to specify the parts of the platform that are relevant to their
 | |
| // context, providing an operating system or architecture or both.
 | |
| //
 | |
| // How do I use this package?
 | |
| //
 | |
| // The vast majority of use cases should simply use the match function with
 | |
| // user input. The first step is to parse a specifier into a matcher:
 | |
| //
 | |
| //   m, err := Parse("linux")
 | |
| //   if err != nil { ... }
 | |
| //
 | |
| // Once you have a matcher, use it to match against the platform declared by a
 | |
| // component, typically from an image or runtime. Since extracting an images
 | |
| // platform is a little more involved, we'll use an example against the
 | |
| // platform default:
 | |
| //
 | |
| //   if ok := m.Match(Default()); !ok { /* doesn't match */ }
 | |
| //
 | |
| // This can be composed in loops for resolving runtimes or used as a filter for
 | |
| // fetch and select images.
 | |
| //
 | |
| // More details of the specifier syntax and platform spec follow.
 | |
| //
 | |
| // Declaring Platform Support
 | |
| //
 | |
| // Components that have strict platform requirements should use the OCI
 | |
| // platform specification to declare their support. Typically, this will be
 | |
| // images and runtimes that should make these declaring which platform they
 | |
| // support specifically. This looks roughly as follows:
 | |
| //
 | |
| //   type Platform struct {
 | |
| //	   Architecture string
 | |
| //	   OS           string
 | |
| //	   Variant      string
 | |
| //   }
 | |
| //
 | |
| // Most images and runtimes should at least set Architecture and OS, according
 | |
| // to their GOARCH and GOOS values, respectively (follow the OCI image
 | |
| // specification when in doubt). ARM should set variant under certain
 | |
| // discussions, which are outlined below.
 | |
| //
 | |
| // Platform Specifiers
 | |
| //
 | |
| // While the OCI platform specifications provide a tool for components to
 | |
| // specify structured information, user input typically doesn't need the full
 | |
| // context and much can be inferred. To solve this problem, we introduced
 | |
| // "specifiers". A specifier has the format
 | |
| // `<os>|<arch>|<os>/<arch>[/<variant>]`.  The user can provide either the
 | |
| // operating system or the architecture or both.
 | |
| //
 | |
| // An example of a common specifier is `linux/amd64`. If the host has a default
 | |
| // of runtime that matches this, the user can simply provide the component that
 | |
| // matters. For example, if a image provides amd64 and arm64 support, the
 | |
| // operating system, `linux` can be inferred, so they only have to provide
 | |
| // `arm64` or `amd64`. Similar behavior is implemented for operating systems,
 | |
| // where the architecture may be known but a runtime may support images from
 | |
| // different operating systems.
 | |
| //
 | |
| // Normalization
 | |
| //
 | |
| // Because not all users are familiar with the way the Go runtime represents
 | |
| // platforms, several normalizations have been provided to make this package
 | |
| // easier to user.
 | |
| //
 | |
| // The following are performed for architectures:
 | |
| //
 | |
| //   Value    Normalized
 | |
| //   aarch64  arm64
 | |
| //   armhf    arm
 | |
| //   armel    arm/v6
 | |
| //   i386     386
 | |
| //   x86_64   amd64
 | |
| //   x86-64   amd64
 | |
| //
 | |
| // We also normalize the operating system `macos` to `darwin`.
 | |
| //
 | |
| // ARM Support
 | |
| //
 | |
| // To qualify ARM architecture, the Variant field is used to qualify the arm
 | |
| // version. The most common arm version, v7, is represented without the variant
 | |
| // unless it is explicitly provided. This is treated as equivalent to armhf. A
 | |
| // previous architecture, armel, will be normalized to arm/v6.
 | |
| //
 | |
| // While these normalizations are provided, their support on arm platforms has
 | |
| // not yet been fully implemented and tested.
 | |
| package platforms
 | |
| 
 | |
| import (
 | |
| 	"regexp"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/containerd/containerd/errdefs"
 | |
| 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
 | |
| )
 | |
| 
 | |
| // Matcher matches platforms specifications, provided by an image or runtime.
 | |
| type Matcher interface {
 | |
| 	Match(platform specs.Platform) bool
 | |
| }
 | |
| 
 | |
| // NewMatcher returns a simple matcher based on the provided platform
 | |
| // specification. The returned matcher only looks for equality based on os,
 | |
| // architecture and variant.
 | |
| //
 | |
| // One may implement their own matcher if this doesn't provide the required
 | |
| // functionality.
 | |
| //
 | |
| // Applications should opt to use `Match` over directly parsing specifiers.
 | |
| func NewMatcher(platform specs.Platform) Matcher {
 | |
| 	return &matcher{
 | |
| 		Platform: Normalize(platform),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type matcher struct {
 | |
| 	specs.Platform
 | |
| }
 | |
| 
 | |
| func (m *matcher) Match(platform specs.Platform) bool {
 | |
| 	normalized := Normalize(platform)
 | |
| 	return m.OS == normalized.OS &&
 | |
| 		m.Architecture == normalized.Architecture &&
 | |
| 		m.Variant == normalized.Variant
 | |
| }
 | |
| 
 | |
| func (m *matcher) String() string {
 | |
| 	return Format(m.Platform)
 | |
| }
 | |
| 
 | |
| // Parse parses the platform specifier syntax into a platform declaration.
 | |
| //
 | |
| // Platform specifiers are in the format `<os>|<arch>|<os>/<arch>[/<variant>]`.
 | |
| // The minimum required information for a platform specifier is the operating
 | |
| // system or architecture. If there is only a single string (no slashes), the
 | |
| // value will be matched against the known set of operating systems, then fall
 | |
| // back to the known set of architectures. The missing component will be
 | |
| // inferred based on the local environment.
 | |
| func Parse(specifier string) (specs.Platform, error) {
 | |
| 	if strings.Contains(specifier, "*") {
 | |
| 		// TODO(stevvooe): need to work out exact wildcard handling
 | |
| 		return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: wildcards not yet supported", specifier)
 | |
| 	}
 | |
| 
 | |
| 	parts := strings.Split(specifier, "/")
 | |
| 
 | |
| 	for _, part := range parts {
 | |
| 		if !specifierRe.MatchString(part) {
 | |
| 			return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var p specs.Platform
 | |
| 	switch len(parts) {
 | |
| 	case 1:
 | |
| 		// in this case, we will test that the value might be an OS, then look
 | |
| 		// it up. If it is not known, we'll treat it as an architecture. Since
 | |
| 		// we have very little information about the platform here, we are
 | |
| 		// going to be a little more strict if we don't know about the argument
 | |
| 		// value.
 | |
| 		p.OS = normalizeOS(parts[0])
 | |
| 		if isKnownOS(p.OS) {
 | |
| 			// picks a default architecture
 | |
| 			p.Architecture = runtime.GOARCH
 | |
| 			if p.Architecture == "arm" && cpuVariant != "v7" {
 | |
| 				p.Variant = cpuVariant
 | |
| 			}
 | |
| 
 | |
| 			return p, nil
 | |
| 		}
 | |
| 
 | |
| 		p.Architecture, p.Variant = normalizeArch(parts[0], "")
 | |
| 		if p.Architecture == "arm" && p.Variant == "v7" {
 | |
| 			p.Variant = ""
 | |
| 		}
 | |
| 		if isKnownArch(p.Architecture) {
 | |
| 			p.OS = runtime.GOOS
 | |
| 			return p, nil
 | |
| 		}
 | |
| 
 | |
| 		return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: unknown operating system or architecture", specifier)
 | |
| 	case 2:
 | |
| 		// In this case, we treat as a regular os/arch pair. We don't care
 | |
| 		// about whether or not we know of the platform.
 | |
| 		p.OS = normalizeOS(parts[0])
 | |
| 		p.Architecture, p.Variant = normalizeArch(parts[1], "")
 | |
| 		if p.Architecture == "arm" && p.Variant == "v7" {
 | |
| 			p.Variant = ""
 | |
| 		}
 | |
| 
 | |
| 		return p, nil
 | |
| 	case 3:
 | |
| 		// we have a fully specified variant, this is rare
 | |
| 		p.OS = normalizeOS(parts[0])
 | |
| 		p.Architecture, p.Variant = normalizeArch(parts[1], parts[2])
 | |
| 		if p.Architecture == "arm64" && p.Variant == "" {
 | |
| 			p.Variant = "v8"
 | |
| 		}
 | |
| 
 | |
| 		return p, nil
 | |
| 	}
 | |
| 
 | |
| 	return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: cannot parse platform specifier", specifier)
 | |
| }
 | |
| 
 | |
| // MustParse is like Parses but panics if the specifier cannot be parsed.
 | |
| // Simplifies initialization of global variables.
 | |
| func MustParse(specifier string) specs.Platform {
 | |
| 	p, err := Parse(specifier)
 | |
| 	if err != nil {
 | |
| 		panic("platform: Parse(" + strconv.Quote(specifier) + "): " + err.Error())
 | |
| 	}
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| // Format returns a string specifier from the provided platform specification.
 | |
| func Format(platform specs.Platform) string {
 | |
| 	if platform.OS == "" {
 | |
| 		return "unknown"
 | |
| 	}
 | |
| 
 | |
| 	return joinNotEmpty(platform.OS, platform.Architecture, platform.Variant)
 | |
| }
 | |
| 
 | |
| func joinNotEmpty(s ...string) string {
 | |
| 	var ss []string
 | |
| 	for _, s := range s {
 | |
| 		if s == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		ss = append(ss, s)
 | |
| 	}
 | |
| 
 | |
| 	return strings.Join(ss, "/")
 | |
| }
 | |
| 
 | |
| // Normalize validates and translate the platform to the canonical value.
 | |
| //
 | |
| // For example, if "Aarch64" is encountered, we change it to "arm64" or if
 | |
| // "x86_64" is encountered, it becomes "amd64".
 | |
| func Normalize(platform specs.Platform) specs.Platform {
 | |
| 	platform.OS = normalizeOS(platform.OS)
 | |
| 	platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
 | |
| 
 | |
| 	// these fields are deprecated, remove them
 | |
| 	platform.OSFeatures = nil
 | |
| 	platform.OSVersion = ""
 | |
| 
 | |
| 	return platform
 | |
| }
 |