pax_global_header00006660000000000000000000000064146520636630014525gustar00rootroot0000000000000052 comment=a0af5a77022414ee54e8011f39705123ece047f5 golang-github-containerd-platforms-0.2.1/000077500000000000000000000000001465206366300204055ustar00rootroot00000000000000golang-github-containerd-platforms-0.2.1/.gitattributes000066400000000000000000000000201465206366300232700ustar00rootroot00000000000000*.go text eol=lfgolang-github-containerd-platforms-0.2.1/.github/000077500000000000000000000000001465206366300217455ustar00rootroot00000000000000golang-github-containerd-platforms-0.2.1/.github/workflows/000077500000000000000000000000001465206366300240025ustar00rootroot00000000000000golang-github-containerd-platforms-0.2.1/.github/workflows/ci.yml000066400000000000000000000044611465206366300251250ustar00rootroot00000000000000name: CI on: push: branches: [ main ] pull_request: branches: [ main ] env: # Go version we currently use to build containerd across all CI. # Note: don't forget to update `Binaries` step, as it contains the matrix of all supported Go versions. GO_VERSION: "1.21.0" permissions: # added using https://github.com/step-security/secure-workflows contents: read jobs: # # golangci-lint # linters: permissions: contents: read # for actions/checkout to fetch code pull-requests: read # for golangci/golangci-lint-action to fetch pull requests name: Linters runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ubuntu-22.04, macos-12, windows-2022] steps: - uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} cache: false # see actions/setup-go#368 - uses: actions/checkout@v3 - uses: golangci/golangci-lint-action@v3 with: version: v1.52.2 skip-cache: true args: --timeout=5m # # Project checks # project: name: Project Checks if: github.repository == 'containerd/platforms' runs-on: ubuntu-22.04 timeout-minutes: 5 steps: - uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} cache: false # see actions/setup-go#368 - uses: actions/checkout@v3 with: path: src/github.com/containerd/platforms fetch-depth: 25 - uses: containerd/project-checks@v1.1.0 with: working-directory: src/github.com/containerd/platforms repo-access-token: ${{ secrets.GITHUB_TOKEN }} tests: name: Tests runs-on: ${{ matrix.os }} timeout-minutes: 5 strategy: matrix: os: [ubuntu-22.04, macos-12, windows-2022] steps: - uses: actions/checkout@v2 with: path: src/github.com/containerd/platforms - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} - name: Set env shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - run: | go test -v -race working-directory: src/github.com/containerd/platforms golang-github-containerd-platforms-0.2.1/.golangci.yml000066400000000000000000000010561465206366300227730ustar00rootroot00000000000000linters: enable: - exportloopref # Checks for pointers to enclosing loop variables - gofmt - goimports - gosec - ineffassign - misspell - nolintlint - revive - staticcheck - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 - unconvert - unused - vet - dupword # Checks for duplicate words in the source code disable: - errcheck run: timeout: 5m skip-dirs: - api - cluster - design - docs - docs/man - releases - reports - test # e2e scripts golang-github-containerd-platforms-0.2.1/LICENSE000066400000000000000000000250151465206366300214150ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS 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 https://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. golang-github-containerd-platforms-0.2.1/README.md000066400000000000000000000032051465206366300216640ustar00rootroot00000000000000# platforms A Go package for formatting, normalizing and matching container platforms. This package is based on the Open Containers Image Spec definition of a [platform](https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/descriptor.go#L52). ## Platform Specifier 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, this package introduces "specifiers". A specifier has the format `||/[/]`. 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 runtime that matches this, the user can simply provide the component that matters. For example, if an 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. ## Project details **platforms** is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). As a containerd sub-project, you will find the: * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository.golang-github-containerd-platforms-0.2.1/compare.go000066400000000000000000000124761465206366300223740ustar00rootroot00000000000000/* 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 ( "strconv" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // MatchComparer is able to match and compare platforms to // filter and sort platforms. type MatchComparer interface { Matcher Less(specs.Platform, specs.Platform) bool } // platformVector returns an (ordered) vector of appropriate specs.Platform // objects to try matching for the given platform object (see platforms.Only). func platformVector(platform specs.Platform) []specs.Platform { vector := []specs.Platform{platform} switch platform.Architecture { case "amd64": if amd64Version, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && amd64Version > 1 { for amd64Version--; amd64Version >= 1; amd64Version-- { vector = append(vector, specs.Platform{ Architecture: platform.Architecture, OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: "v" + strconv.Itoa(amd64Version), }) } } vector = append(vector, specs.Platform{ Architecture: "386", OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, }) case "arm": if armVersion, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && armVersion > 5 { for armVersion--; armVersion >= 5; armVersion-- { vector = append(vector, specs.Platform{ Architecture: platform.Architecture, OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: "v" + strconv.Itoa(armVersion), }) } } case "arm64": variant := platform.Variant if variant == "" { variant = "v8" } vector = append(vector, platformVector(specs.Platform{ Architecture: "arm", OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: variant, })...) } return vector } // Only returns a match comparer for a single platform // using default resolution logic for the platform. // // For arm/v8, will also match arm/v7, arm/v6 and arm/v5 // For arm/v7, will also match arm/v6 and arm/v5 // For arm/v6, will also match arm/v5 // For amd64, will also match 386 func Only(platform specs.Platform) MatchComparer { return Ordered(platformVector(Normalize(platform))...) } // OnlyStrict returns a match comparer for a single platform. // // Unlike Only, OnlyStrict does not match sub platforms. // So, "arm/vN" will not match "arm/vM" where M < N, // and "amd64" will not also match "386". // // OnlyStrict matches non-canonical forms. // So, "arm64" matches "arm/64/v8". func OnlyStrict(platform specs.Platform) MatchComparer { return Ordered(Normalize(platform)) } // Ordered returns a platform MatchComparer which matches any of the platforms // but orders them in order they are provided. func Ordered(platforms ...specs.Platform) MatchComparer { matchers := make([]Matcher, len(platforms)) for i := range platforms { matchers[i] = NewMatcher(platforms[i]) } return orderedPlatformComparer{ matchers: matchers, } } // Any returns a platform MatchComparer which matches any of the platforms // with no preference for ordering. func Any(platforms ...specs.Platform) MatchComparer { matchers := make([]Matcher, len(platforms)) for i := range platforms { matchers[i] = NewMatcher(platforms[i]) } return anyPlatformComparer{ matchers: matchers, } } // All is a platform MatchComparer which matches all platforms // with preference for ordering. var All MatchComparer = allPlatformComparer{} type orderedPlatformComparer struct { matchers []Matcher } func (c orderedPlatformComparer) Match(platform specs.Platform) bool { for _, m := range c.matchers { if m.Match(platform) { return true } } return false } func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool { for _, m := range c.matchers { p1m := m.Match(p1) p2m := m.Match(p2) if p1m && !p2m { return true } if p1m || p2m { return false } } return false } type anyPlatformComparer struct { matchers []Matcher } func (c anyPlatformComparer) Match(platform specs.Platform) bool { for _, m := range c.matchers { if m.Match(platform) { return true } } return false } func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool { var p1m, p2m bool for _, m := range c.matchers { if !p1m && m.Match(p1) { p1m = true } if !p2m && m.Match(p2) { p2m = true } if p1m && p2m { return false } } // If one matches, and the other does, sort match first return p1m && !p2m } type allPlatformComparer struct{} func (allPlatformComparer) Match(specs.Platform) bool { return true } func (allPlatformComparer) Less(specs.Platform, specs.Platform) bool { return false } golang-github-containerd-platforms-0.2.1/compare_test.go000066400000000000000000000175111465206366300234260ustar00rootroot00000000000000/* 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 ( "testing" ) func TestOnly(t *testing.T) { for _, tc := range []struct { platform string matches map[bool][]string }{ { platform: "linux/amd64", matches: map[bool][]string{ true: { "linux/amd64", "linux/386", }, false: { "linux/amd64/v2", "linux/arm/v7", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/amd64/v2", matches: map[bool][]string{ true: { "linux/amd64", "linux/amd64/v1", "linux/amd64/v2", "linux/386", }, false: { "linux/amd64/v3", "linux/amd64/v4", "linux/arm/v7", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/386", matches: map[bool][]string{ true: { "linux/386", }, false: { "linux/amd64", "linux/arm/v7", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "windows/amd64", matches: map[bool][]string{ true: { "windows/amd64", "windows(10.0.17763)/amd64", }, false: { "linux/amd64", "linux/arm/v7", "linux/arm64", "windows/arm", }, }, }, { platform: "linux/arm/v8", matches: map[bool][]string{ true: { "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", }, false: { "linux/amd64", "linux/arm/v4", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v7", matches: map[bool][]string{ true: { "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", }, false: { "linux/amd64", "linux/arm/v4", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v6", matches: map[bool][]string{ true: { "linux/arm/v5", "linux/arm/v6", }, false: { "linux/amd64", "linux/arm", "linux/arm/v4", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v5", matches: map[bool][]string{ true: { "linux/arm/v5", }, false: { "linux/amd64", "linux/arm", "linux/arm/v4", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v4", matches: map[bool][]string{ true: { "linux/arm/v4", }, false: { "linux/amd64", "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm64", matches: map[bool][]string{ true: { "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "linux/arm64/v8", }, false: { "linux/amd64", "linux/arm/v4", "linux/arm/v9", "linux/arm64/v9", "windows/amd64", "windows/arm", }, }, }, } { testcase := tc t.Run(testcase.platform, func(t *testing.T) { p, err := Parse(testcase.platform) if err != nil { t.Fatal(err) } m := Only(p) for shouldMatch, platforms := range testcase.matches { for _, matchPlatform := range platforms { mp, err := Parse(matchPlatform) if err != nil { t.Fatal(err) } if match := m.Match(mp); shouldMatch != match { t.Errorf("Only(%q).Match(%q) should return %v, but returns %v", testcase.platform, matchPlatform, shouldMatch, match) } } } }) } } func TestOnlyStrict(t *testing.T) { for _, tc := range []struct { platform string matches map[bool][]string }{ { platform: "linux/amd64", matches: map[bool][]string{ true: { "linux/amd64", }, false: { "linux/386", "linux/arm/v7", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/386", matches: map[bool][]string{ true: { "linux/386", }, false: { "linux/amd64", "linux/arm/v7", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "windows/amd64", matches: map[bool][]string{ true: { "windows/amd64", "windows(10.0.17763)/amd64", }, false: { "linux/amd64", "linux/arm/v7", "linux/arm64", "windows/arm", }, }, }, { platform: "windows(10.0.17763)/amd64", matches: map[bool][]string{ true: { "windows/amd64", "windows(10.0.17763)/amd64", }, false: { "linux/amd64", "linux/arm/v7", "linux/arm64", "windows/arm", }, }, }, { platform: "linux/arm/v8", matches: map[bool][]string{ true: { "linux/arm/v8", }, false: { "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", "linux/amd64", "linux/arm/v4", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v7", matches: map[bool][]string{ true: { "linux/arm", "linux/arm/v7", }, false: { "linux/arm/v5", "linux/arm/v6", "linux/amd64", "linux/arm/v4", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v6", matches: map[bool][]string{ true: { "linux/arm/v6", }, false: { "linux/arm/v5", "linux/amd64", "linux/arm", "linux/arm/v4", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v5", matches: map[bool][]string{ true: { "linux/arm/v5", }, false: { "linux/amd64", "linux/arm", "linux/arm/v4", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm/v4", matches: map[bool][]string{ true: { "linux/arm/v4", }, false: { "linux/amd64", "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", "linux/arm64", "windows/amd64", "windows/arm", }, }, }, { platform: "linux/arm64", matches: map[bool][]string{ true: { "linux/arm64", "linux/arm64/v8", }, false: { "linux/arm", "linux/arm/v5", "linux/arm/v6", "linux/arm/v7", "linux/arm/v8", "linux/amd64", "linux/arm/v4", "linux/arm/v9", "linux/arm64/v9", "windows/amd64", "windows/arm", }, }, }, } { testcase := tc t.Run(testcase.platform, func(t *testing.T) { p, err := Parse(testcase.platform) if err != nil { t.Fatal(err) } m := OnlyStrict(p) for shouldMatch, platforms := range testcase.matches { for _, matchPlatform := range platforms { mp, err := Parse(matchPlatform) if err != nil { t.Fatal(err) } if match := m.Match(mp); shouldMatch != match { t.Errorf("OnlyStrict(%q).Match(%q) should return %v, but returns %v", testcase.platform, matchPlatform, shouldMatch, match) } } } }) } } golang-github-containerd-platforms-0.2.1/cpuinfo.go000066400000000000000000000021551465206366300224020ustar00rootroot00000000000000/* 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 ( "runtime" "sync" "github.com/containerd/log" ) // Present the ARM instruction set architecture, eg: v7, v8 // Don't use this value directly; call cpuVariant() instead. var cpuVariantValue string var cpuVariantOnce sync.Once func cpuVariant() string { cpuVariantOnce.Do(func() { if isArmArch(runtime.GOARCH) { var err error cpuVariantValue, err = getCPUVariant() if err != nil { log.L.Errorf("Error getCPUVariant for OS %s: %v", runtime.GOOS, err) } } }) return cpuVariantValue } golang-github-containerd-platforms-0.2.1/cpuinfo_linux.go000066400000000000000000000102171465206366300236170ustar00rootroot00000000000000/* 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 ( "bufio" "bytes" "errors" "fmt" "os" "runtime" "strings" "golang.org/x/sys/unix" ) // getMachineArch retrieves the machine architecture through system call func getMachineArch() (string, error) { var uname unix.Utsname err := unix.Uname(&uname) if err != nil { return "", err } arch := string(uname.Machine[:bytes.IndexByte(uname.Machine[:], 0)]) return arch, nil } // 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) { 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: %w", pattern, errNotFound) } // getCPUVariantFromArch get CPU variant from arch through a system call func getCPUVariantFromArch(arch string) (string, error) { var variant string arch = strings.ToLower(arch) if arch == "aarch64" { variant = "8" } else if arch[0:4] == "armv" && len(arch) >= 5 { // Valid arch format is in form of armvXx switch arch[3:5] { case "v8": variant = "8" case "v7": variant = "7" case "v6": variant = "6" case "v5": variant = "5" case "v4": variant = "4" case "v3": variant = "3" default: variant = "unknown" } } else { return "", fmt.Errorf("getCPUVariantFromArch invalid arch: %s, %w", arch, errInvalidArgument) } return variant, nil } // getCPUVariant returns cpu variant for ARM // We first try reading "Cpu architecture" field from /proc/cpuinfo // If we can't find it, then fall back using a system call // This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo // was not present. func getCPUVariant() (string, error) { variant, err := getCPUInfo("Cpu architecture") if err != nil { if errors.Is(err, errNotFound) { // Let's try getting CPU variant from machine architecture arch, err := getMachineArch() if err != nil { return "", fmt.Errorf("failure getting machine architecture: %v", err) } variant, err = getCPUVariantFromArch(arch) if err != nil { return "", fmt.Errorf("failure getting CPU variant from machine architecture: %v", err) } } else { return "", fmt.Errorf("failure getting CPU variant: %v", err) } } // handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7") // https://www.raspberrypi.org/forums/viewtopic.php?t=12614 if runtime.GOARCH == "arm" && variant == "7" { model, err := getCPUInfo("model name") if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") { variant = "6" } } 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 = "unknown" } return variant, nil } golang-github-containerd-platforms-0.2.1/cpuinfo_linux_test.go000066400000000000000000000060731465206366300246630ustar00rootroot00000000000000/* 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 ( "errors" "runtime" "testing" ) func TestCPUVariant(t *testing.T) { if !isArmArch(runtime.GOARCH) { t.Skip("only relevant on linux/arm") } variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"} p, err := getCPUVariant() if err != nil { t.Fatalf("Error getting CPU variant: %v", err) return } for _, variant := range variants { if p == variant { t.Logf("got valid variant as expected: %#v = %#v", p, variant) return } } t.Fatalf("could not get valid variant as expected: %v", variants) } func TestGetCPUVariantFromArch(t *testing.T) { for _, testcase := range []struct { name string input string output string expectedErr error }{ { name: "Test aarch64", input: "aarch64", output: "8", expectedErr: nil, }, { name: "Test Armv8 with capital", input: "Armv8", output: "8", expectedErr: nil, }, { name: "Test armv7", input: "armv7", output: "7", expectedErr: nil, }, { name: "Test armv6", input: "armv6", output: "6", expectedErr: nil, }, { name: "Test armv5", input: "armv5", output: "5", expectedErr: nil, }, { name: "Test armv4", input: "armv4", output: "4", expectedErr: nil, }, { name: "Test armv3", input: "armv3", output: "3", expectedErr: nil, }, { name: "Test unknown input", input: "armv9", output: "unknown", expectedErr: nil, }, { name: "Test invalid input which doesn't start with armv", input: "armxxxx", output: "", expectedErr: errInvalidArgument, }, { name: "Test invalid input whose length is less than 5", input: "armv", output: "", expectedErr: errInvalidArgument, }, } { t.Run(testcase.name, func(t *testing.T) { t.Logf("input: %v", testcase.input) variant, err := getCPUVariantFromArch(testcase.input) if err == nil { if testcase.expectedErr != nil { t.Fatalf("Expect to get error: %v, however no error got", testcase.expectedErr) } else { if variant != testcase.output { t.Fatalf("Expect to get variant: %v, however %v returned", testcase.output, variant) } } } else { if !errors.Is(err, testcase.expectedErr) { t.Fatalf("Expect to get error: %v, however error %v returned", testcase.expectedErr, err) } } }) } } golang-github-containerd-platforms-0.2.1/cpuinfo_other.go000066400000000000000000000026471465206366300236110ustar00rootroot00000000000000//go:build !linux /* 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 ( "fmt" "runtime" ) func getCPUVariant() (string, error) { var variant string if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { // Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use // runtime.GOARCH to determine the variants switch runtime.GOARCH { case "arm64": variant = "v8" case "arm": variant = "v7" default: variant = "unknown" } } else if runtime.GOOS == "freebsd" { // FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated) // detecting those variants is currently unimplemented switch runtime.GOARCH { case "arm64": variant = "v8" default: variant = "unknown" } } else { return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) } return variant, nil } golang-github-containerd-platforms-0.2.1/database.go000066400000000000000000000053171465206366300225060ustar00rootroot00000000000000/* 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 ( "runtime" "strings" ) // These function are generated from https://golang.org/src/go/build/syslist.go. // // We use switch statements because they are slightly faster than map lookups // and use a little less memory. // isKnownOS returns true if we know about the operating system. // // The OS value should be normalized before calling this function. func isKnownOS(os string) bool { switch os { case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos": return true } return false } // isArmArch returns true if the architecture is ARM. // // The arch value should be normalized before being passed to this function. func isArmArch(arch string) bool { switch arch { case "arm", "arm64": return true } return false } // isKnownArch returns true if we know about the architecture. // // The arch value should be normalized before being passed to this function. func isKnownArch(arch string) bool { switch arch { case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "loong64", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm": return true } return false } func normalizeOS(os string) string { if os == "" { return runtime.GOOS } os = strings.ToLower(os) switch os { case "macos": os = "darwin" } return os } // normalizeArch normalizes the architecture. func normalizeArch(arch, variant string) (string, string) { arch, variant = strings.ToLower(arch), strings.ToLower(variant) switch arch { case "i386": arch = "386" variant = "" case "x86_64", "x86-64", "amd64": arch = "amd64" if variant == "v1" { variant = "" } case "aarch64", "arm64": arch = "arm64" switch variant { case "8", "v8": variant = "" } case "armhf": arch = "arm" variant = "v7" case "armel": arch = "arm" variant = "v6" case "arm": switch variant { case "", "7": variant = "v7" case "5", "6", "8": variant = "v" + variant } } return arch, variant } golang-github-containerd-platforms-0.2.1/defaults.go000066400000000000000000000020051465206366300225400ustar00rootroot00000000000000/* 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 // DefaultString returns the default string specifier for the platform, // with [PR#6](https://github.com/containerd/platforms/pull/6) the result // may now also include the OSVersion from the provided platform specification. func DefaultString() string { return FormatAll(DefaultSpec()) } // DefaultStrict returns strict form of Default. func DefaultStrict() MatchComparer { return OnlyStrict(DefaultSpec()) } golang-github-containerd-platforms-0.2.1/defaults_darwin.go000066400000000000000000000023521465206366300241110ustar00rootroot00000000000000//go:build darwin /* 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 ( "runtime" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } // Default returns the default matcher for the platform. func Default() MatchComparer { return Ordered(DefaultSpec(), specs.Platform{ // darwin runtime also supports Linux binary via runu/LKL OS: "linux", Architecture: runtime.GOARCH, }) } golang-github-containerd-platforms-0.2.1/defaults_freebsd.go000066400000000000000000000023511465206366300242360ustar00rootroot00000000000000/* 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 ( "runtime" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } // Default returns the default matcher for the platform. func Default() MatchComparer { return Ordered(DefaultSpec(), specs.Platform{ OS: "linux", Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), }) } golang-github-containerd-platforms-0.2.1/defaults_unix.go000066400000000000000000000021671465206366300236140ustar00rootroot00000000000000//go:build !windows && !darwin && !freebsd /* 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 ( "runtime" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } // Default returns the default matcher for the platform. func Default() MatchComparer { return Only(DefaultSpec()) } golang-github-containerd-platforms-0.2.1/defaults_unix_test.go000066400000000000000000000022161465206366300246460ustar00rootroot00000000000000//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 ( "reflect" "runtime" "testing" specs "github.com/opencontainers/image-spec/specs-go/v1" ) func TestDefault(t *testing.T) { expected := specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, Variant: cpuVariant(), } p := DefaultSpec() if !reflect.DeepEqual(p, expected) { t.Fatalf("default platform not as expected: %#v != %#v", p, expected) } s := DefaultString() if s != FormatAll(p) { t.Fatalf("default specifier should match formatted default spec: %v != %v", s, p) } } golang-github-containerd-platforms-0.2.1/defaults_windows.go000066400000000000000000000056401465206366300243220ustar00rootroot00000000000000/* 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 ( "fmt" "runtime" "strconv" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sys/windows" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { major, minor, build := windows.RtlGetNtVersionNumbers() return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, OSVersion: fmt.Sprintf("%d.%d.%d", major, minor, build), // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } type windowsmatcher struct { specs.Platform osVersionPrefix string defaultMatcher Matcher } // Match matches platform with the same windows major, minor // and build version. func (m windowsmatcher) Match(p specs.Platform) bool { match := m.defaultMatcher.Match(p) if match && m.OS == "windows" { // HPC containers do not have OS version filled if m.OSVersion == "" || p.OSVersion == "" { return true } hostOsVersion := getOSVersion(m.osVersionPrefix) ctrOsVersion := getOSVersion(p.OSVersion) return checkHostAndContainerCompat(hostOsVersion, ctrOsVersion) } return match } func getOSVersion(osVersionPrefix string) osVersion { parts := strings.Split(osVersionPrefix, ".") if len(parts) < 3 { return osVersion{} } majorVersion, _ := strconv.Atoi(parts[0]) minorVersion, _ := strconv.Atoi(parts[1]) buildNumber, _ := strconv.Atoi(parts[2]) return osVersion{ MajorVersion: uint8(majorVersion), MinorVersion: uint8(minorVersion), Build: uint16(buildNumber), } } // Less sorts matched platforms in front of other platforms. // For matched platforms, it puts platforms with larger revision // number in front. func (m windowsmatcher) Less(p1, p2 specs.Platform) bool { m1, m2 := m.Match(p1), m.Match(p2) if m1 && m2 { r1, r2 := revision(p1.OSVersion), revision(p2.OSVersion) return r1 > 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. func Default() MatchComparer { return Only(DefaultSpec()) } golang-github-containerd-platforms-0.2.1/defaults_windows_test.go000066400000000000000000000220641465206366300253600ustar00rootroot00000000000000/* 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 ( "fmt" "reflect" "runtime" "sort" "testing" imagespec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" "golang.org/x/sys/windows" ) func TestDefault(t *testing.T) { major, minor, build := windows.RtlGetNtVersionNumbers() expected := imagespec.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, OSVersion: fmt.Sprintf("%d.%d.%d", major, minor, build), Variant: cpuVariant(), } p := DefaultSpec() if !reflect.DeepEqual(p, expected) { t.Fatalf("default platform not as expected: %#v != %#v", p, expected) } s := DefaultString() if s != FormatAll(p) { t.Fatalf("default specifier should match formatted default spec: %v != %v", s, p) } } func TestDefaultMatchComparer(t *testing.T) { defaultMatcher := Default() for _, test := range []struct { platform imagespec.Platform match bool }{ { platform: DefaultSpec(), match: true, }, { platform: imagespec.Platform{ OS: "linux", Architecture: runtime.GOARCH, }, match: false, }, } { assert.Equal(t, test.match, defaultMatcher.Match(test.platform)) } } func TestMatchComparerMatch_WCOW(t *testing.T) { major, minor, build := windows.RtlGetNtVersionNumbers() buildStr := fmt.Sprintf("%d.%d.%d", major, minor, build) m := windowsmatcher{ Platform: DefaultSpec(), osVersionPrefix: buildStr, defaultMatcher: &matcher{ Platform: Normalize(DefaultSpec()), }, } for _, test := range []struct { platform imagespec.Platform match bool }{ { platform: DefaultSpec(), match: true, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: buildStr + ".1", }, match: true, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: buildStr + ".2", }, match: true, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", // Use an nonexistent Windows build so we don't get a match. Ws2019's build is 17763/ OSVersion: "10.0.17762.1", }, match: false, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", // Use an nonexistent Windows build so we don't get a match. Ws2019's build is 17763/ OSVersion: "10.0.17764.1", }, match: false, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", }, match: true, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "linux", }, match: false, }, } { assert.Equal(t, test.match, m.Match(test.platform), "should match: %t, %s to %s", test.match, m.Platform, test.platform) } } // TestMatchComparerMatch_ABICheckWCOW checks windows platform matcher // behavior for stable ABI and non-stable ABI compliant versions func TestMatchComparerMatch_ABICheckWCOW(t *testing.T) { platformNoVersion := imagespec.Platform{ Architecture: "amd64", OS: "windows", } platformWS2019 := imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763", } platformWS2022 := imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348", } platformWindows11 := imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.22621", } matcherNoVersion := NewMatcher(platformNoVersion).(windowsmatcher) matcherWS2019 := windowsmatcher{ Platform: platformWS2019, osVersionPrefix: platformWS2019.OSVersion, defaultMatcher: &matcher{ Platform: Normalize(platformWS2019), }, } matcherWS2022 := windowsmatcher{ Platform: platformWS2022, osVersionPrefix: platformWS2022.OSVersion, defaultMatcher: &matcher{ Platform: Normalize(platformWS2022), }, } matcherWindows11 := windowsmatcher{ Platform: platformWindows11, osVersionPrefix: platformWindows11.OSVersion, defaultMatcher: &matcher{ Platform: Normalize(platformWindows11), }, } for _, test := range []struct { hostPlatformMatcher windowsmatcher testPlatform imagespec.Platform match bool }{ { hostPlatformMatcher: matcherWS2019, testPlatform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763", }, match: true, }, { hostPlatformMatcher: matcherWS2019, testPlatform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348", }, match: false, }, { hostPlatformMatcher: matcherWS2022, testPlatform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763", }, match: false, }, { hostPlatformMatcher: matcherWS2022, testPlatform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348", }, match: true, }, { hostPlatformMatcher: matcherWindows11, testPlatform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763", }, match: false, }, { hostPlatformMatcher: matcherWindows11, testPlatform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348", }, match: true, }, { hostPlatformMatcher: matcherNoVersion, testPlatform: platformWS2019, match: true, }, { hostPlatformMatcher: matcherNoVersion, testPlatform: platformNoVersion, match: true, }, { hostPlatformMatcher: matcherNoVersion, testPlatform: platformWindows11, match: true, }, } { assert.Equal(t, test.match, test.hostPlatformMatcher.Match(test.testPlatform), "should match: %t, %s to %s", test.match, test.hostPlatformMatcher.Platform, test.testPlatform) } } func TestMatchComparerMatch_LCOW(t *testing.T) { major, minor, build := windows.RtlGetNtVersionNumbers() buildStr := fmt.Sprintf("%d.%d.%d", major, minor, build) m := windowsmatcher{ Platform: imagespec.Platform{ OS: "linux", Architecture: "amd64", }, osVersionPrefix: "", defaultMatcher: &matcher{ Platform: Normalize(imagespec.Platform{ OS: "linux", Architecture: "amd64", }, ), }, } for _, test := range []struct { platform imagespec.Platform match bool }{ { platform: DefaultSpec(), match: false, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", }, match: false, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", OSVersion: buildStr + ".2", }, match: false, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "windows", // Use an nonexistent Windows build so we don't get a match. Ws2019's build is 17763/ OSVersion: "10.0.17762.1", }, match: false, }, { platform: imagespec.Platform{ Architecture: "amd64", OS: "linux", }, match: true, }, } { assert.Equal(t, test.match, m.Match(test.platform), "should match %b, %s to %s", test.match, m.Platform, test.platform) } } func TestMatchComparerLess(t *testing.T) { m := windowsmatcher{ Platform: DefaultSpec(), osVersionPrefix: "10.0.17763", defaultMatcher: &matcher{ Platform: Normalize(DefaultSpec()), }, } platforms := []imagespec.Platform{ { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17764.1", }, { Architecture: "amd64", OS: "windows", }, { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.1", }, { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.2", }, { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17762.1", }, } expected := []imagespec.Platform{ { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.2", }, { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.1", }, { Architecture: "amd64", OS: "windows", }, { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17764.1", }, { Architecture: "amd64", OS: "windows", OSVersion: "10.0.17762.1", }, } sort.SliceStable(platforms, func(i, j int) bool { return m.Less(platforms[i], platforms[j]) }) assert.Equal(t, expected, platforms) } golang-github-containerd-platforms-0.2.1/errors.go000066400000000000000000000020521465206366300222470ustar00rootroot00000000000000/* 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 "errors" // These errors mirror the errors defined in [github.com/containerd/containerd/errdefs], // however, they are not exported as they are not expected to be used as sentinel // errors by consumers of this package. // //nolint:unused // not all errors are used on all platforms. var ( errNotFound = errors.New("not found") errInvalidArgument = errors.New("invalid argument") errNotImplemented = errors.New("not implemented") ) golang-github-containerd-platforms-0.2.1/go.mod000066400000000000000000000007101465206366300215110ustar00rootroot00000000000000module github.com/containerd/platforms go 1.20 require ( github.com/containerd/log v0.1.0 github.com/opencontainers/image-spec v1.1.0-rc5 github.com/stretchr/testify v1.8.4 golang.org/x/sys v0.10.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-containerd-platforms-0.2.1/go.sum000066400000000000000000000043021465206366300215370ustar00rootroot00000000000000github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-containerd-platforms-0.2.1/platform_compat_windows.go000066400000000000000000000052651465206366300257050ustar00rootroot00000000000000/* 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 // 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 { Version uint32 MajorVersion uint8 MinorVersion uint8 Build uint16 } // Windows Client and Server build numbers. // // See: // https://learn.microsoft.com/en-us/windows/release-health/release-information // https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info // https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information const ( // rs5 (version 1809, codename "Redstone 5") corresponds to Windows Server // 2019 (ltsc2019), and Windows 10 (October 2018 Update). rs5 = 17763 // v21H2Server corresponds to Windows Server 2022 (ltsc2022). v21H2Server = 20348 // v22H2Win11 corresponds to Windows 11 (2022 Update). v22H2Win11 = 22621 ) // List of stable ABI compliant ltsc releases // Note: List must be sorted in ascending order var compatLTSCReleases = []uint16{ v21H2Server, } // CheckHostAndContainerCompat checks if given host and container // OS versions are compatible. // It includes support for stable ABI compliant versions as well. // 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 { // check major minor versions of host and guest if host.MajorVersion != ctr.MajorVersion || host.MinorVersion != ctr.MinorVersion { return false } // If host is < WS 2022, exact version match is required if host.Build < v21H2Server { return host.Build == ctr.Build } var supportedLtscRelease uint16 for i := len(compatLTSCReleases) - 1; i >= 0; i-- { if host.Build >= compatLTSCReleases[i] { supportedLtscRelease = compatLTSCReleases[i] break } } return ctr.Build >= supportedLtscRelease && ctr.Build <= host.Build } golang-github-containerd-platforms-0.2.1/platform_compat_windows_test.go000066400000000000000000000041061465206366300267350ustar00rootroot00000000000000/* 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 ( "testing" ) // Test the platform compatibility of the different // OS Versions considering two ltsc container image // versions (ltsc2019, ltsc2022) func Test_PlatformCompat(t *testing.T) { for testName, tc := range map[string]struct { hostOs uint16 ctrOs uint16 shouldRun bool }{ "RS5Host_ltsc2019": { hostOs: rs5, ctrOs: rs5, shouldRun: true, }, "RS5Host_ltsc2022": { hostOs: rs5, ctrOs: v21H2Server, shouldRun: false, }, "WS2022Host_ltsc2019": { hostOs: v21H2Server, ctrOs: rs5, shouldRun: false, }, "WS2022Host_ltsc2022": { hostOs: v21H2Server, ctrOs: v21H2Server, shouldRun: true, }, "Wind11Host_ltsc2019": { hostOs: v22H2Win11, ctrOs: rs5, shouldRun: false, }, "Wind11Host_ltsc2022": { hostOs: v22H2Win11, ctrOs: v21H2Server, shouldRun: true, }, } { // Check if ltsc2019/ltsc2022 guest images are compatible on // the given host OS versions // hostOSVersion := osVersion{ MajorVersion: 10, MinorVersion: 0, Build: tc.hostOs, } ctrOSVersion := osVersion{ MajorVersion: 10, MinorVersion: 0, Build: tc.ctrOs, } if checkHostAndContainerCompat(hostOSVersion, ctrOSVersion) != tc.shouldRun { var expectedResultStr string if !tc.shouldRun { expectedResultStr = " NOT" } t.Fatalf("Failed %v: host %v should%s be able to run guest %v", testName, tc.hostOs, expectedResultStr, tc.ctrOs) } } } golang-github-containerd-platforms-0.2.1/platforms.go000066400000000000000000000252501465206366300227470ustar00rootroot00000000000000/* 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 // `||/[/]`. 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. // // Similarly, the most common arm64 version v8, and most common amd64 version v1 // are represented without the variant. // // While these normalizations are provided, their support on arm platforms has // not yet been fully implemented and tested. package platforms import ( "fmt" "path" "regexp" "runtime" "strconv" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" ) var ( specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`) ) const osAndVersionFormat = "%s(%s)" // Platform is a type alias for convenience, so there is no need to import image-spec package everywhere. type Platform = specs.Platform // 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 newDefaultMatcher(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 FormatAll(m.Platform) } // ParseAll parses a list of platform specifiers into a list of platform. func ParseAll(specifiers []string) ([]specs.Platform, error) { platforms := make([]specs.Platform, len(specifiers)) for i, s := range specifiers { p, err := Parse(s) if err != nil { return nil, fmt.Errorf("invalid platform %s: %w", s, err) } platforms[i] = p } return platforms, nil } // Parse parses the platform specifier syntax into a platform declaration. // // Platform specifiers are in the format `[()]||[()]/[/]`. // The minimum required information for a platform specifier is the operating // system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)` // When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value, // and an empty string otherwise. // 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{}, fmt.Errorf("%q: wildcards not yet supported: %w", specifier, errInvalidArgument) } // Limit to 4 elements to prevent unbounded split parts := strings.SplitN(specifier, "/", 4) var p specs.Platform for i, part := range parts { if i == 0 { // First element is [()] osVer := osAndVersionRe.FindStringSubmatch(part) if osVer == nil { return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osAndVersionRe.String(), errInvalidArgument) } p.OS = normalizeOS(osVer[1]) p.OSVersion = osVer[2] } else { if !specifierRe.MatchString(part) { return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument) } } } switch len(parts) { case 1: // in this case, we will test that the value might be an OS (with or // without the optional OSVersion specified) and 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. 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{}, fmt.Errorf("%q: unknown operating system or architecture: %w", specifier, errInvalidArgument) case 2: // In this case, we treat as a regular OS[(OSVersion)]/arch pair. We don't care // about whether or not we know of the platform. 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.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) if p.Architecture == "arm64" && p.Variant == "" { p.Variant = "v8" } return p, nil } return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument) } // 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 path.Join(platform.OS, platform.Architecture, platform.Variant) } // FormatAll returns a string specifier that also includes the OSVersion from the // provided platform specification. func FormatAll(platform specs.Platform) string { if platform.OS == "" { return "unknown" } if platform.OSVersion != "" { OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion) return path.Join(OSAndVersion, platform.Architecture, platform.Variant) } return path.Join(platform.OS, platform.Architecture, platform.Variant) } // 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) return platform } golang-github-containerd-platforms-0.2.1/platforms_other.go000066400000000000000000000015551465206366300241520ustar00rootroot00000000000000//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), } } golang-github-containerd-platforms-0.2.1/platforms_test.go000066400000000000000000000231241465206366300240040ustar00rootroot00000000000000/* 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 ( "path" "reflect" "runtime" "testing" specs "github.com/opencontainers/image-spec/specs-go/v1" ) func TestParseSelector(t *testing.T) { var ( defaultOS = runtime.GOOS defaultArch = runtime.GOARCH defaultVariant = "" ) if defaultArch == "arm" && cpuVariant() != "v7" { defaultVariant = cpuVariant() } for _, testcase := range []struct { skip bool input string expected specs.Platform matches []specs.Platform formatted string useV2Format bool }{ // While wildcards are a valid use case for platform selection, // addressing these cases is outside the initial scope for this // package. When we do add platform wildcards, we should add in these // testcases to ensure that they are correctly represented. { skip: true, input: "*", expected: specs.Platform{ OS: "*", Architecture: "*", }, formatted: "*/*", useV2Format: false, }, { skip: true, input: "linux/*", expected: specs.Platform{ OS: "linux", Architecture: "*", }, formatted: "linux/*", useV2Format: false, }, { skip: true, input: "*/arm64", expected: specs.Platform{ OS: "*", Architecture: "arm64", }, matches: []specs.Platform{ { OS: "*", Architecture: "aarch64", }, { OS: "*", Architecture: "aarch64", Variant: "v8", }, { OS: "*", Architecture: "arm64", Variant: "v8", }, }, formatted: "*/arm64", useV2Format: false, }, { input: "linux/arm64", expected: specs.Platform{ OS: "linux", Architecture: "arm64", }, matches: []specs.Platform{ { OS: "linux", Architecture: "aarch64", }, { OS: "linux", Architecture: "aarch64", Variant: "v8", }, { OS: "linux", Architecture: "arm64", Variant: "v8", }, }, formatted: "linux/arm64", useV2Format: false, }, { input: "linux/arm64/v8", expected: specs.Platform{ OS: "linux", Architecture: "arm64", Variant: "v8", }, matches: []specs.Platform{ { OS: "linux", Architecture: "aarch64", }, { OS: "linux", Architecture: "aarch64", Variant: "v8", }, { OS: "linux", Architecture: "arm64", }, }, formatted: "linux/arm64/v8", useV2Format: false, }, { // NOTE(stevvooe): In this case, the consumer can assume this is v7 // but we leave the variant blank. This will represent the vast // majority of arm images. input: "linux/arm", expected: specs.Platform{ OS: "linux", Architecture: "arm", }, matches: []specs.Platform{ { OS: "linux", Architecture: "arm", Variant: "v7", }, { OS: "linux", Architecture: "armhf", }, { OS: "linux", Architecture: "arm", Variant: "7", }, }, formatted: "linux/arm", useV2Format: false, }, { input: "linux/arm/v6", expected: specs.Platform{ OS: "linux", Architecture: "arm", Variant: "v6", }, matches: []specs.Platform{ { OS: "linux", Architecture: "armel", }, }, formatted: "linux/arm/v6", useV2Format: false, }, { input: "linux/arm/v7", expected: specs.Platform{ OS: "linux", Architecture: "arm", Variant: "v7", }, matches: []specs.Platform{ { OS: "linux", Architecture: "arm", }, { OS: "linux", Architecture: "armhf", }, }, formatted: "linux/arm/v7", useV2Format: false, }, { input: "arm", expected: specs.Platform{ OS: defaultOS, Architecture: "arm", }, formatted: path.Join(defaultOS, "arm"), useV2Format: false, }, { input: "armel", expected: specs.Platform{ OS: defaultOS, Architecture: "arm", Variant: "v6", }, formatted: path.Join(defaultOS, "arm/v6"), useV2Format: false, }, { input: "armhf", expected: specs.Platform{ OS: defaultOS, Architecture: "arm", }, formatted: path.Join(defaultOS, "arm"), useV2Format: false, }, { input: "Aarch64", expected: specs.Platform{ OS: defaultOS, Architecture: "arm64", }, formatted: path.Join(defaultOS, "arm64"), useV2Format: false, }, { input: "x86_64", expected: specs.Platform{ OS: defaultOS, Architecture: "amd64", }, formatted: path.Join(defaultOS, "amd64"), useV2Format: false, }, { input: "Linux/x86_64", expected: specs.Platform{ OS: "linux", Architecture: "amd64", }, formatted: "linux/amd64", useV2Format: false, }, { input: "i386", expected: specs.Platform{ OS: defaultOS, Architecture: "386", }, formatted: path.Join(defaultOS, "386"), useV2Format: false, }, { input: "linux", expected: specs.Platform{ OS: "linux", Architecture: defaultArch, Variant: defaultVariant, }, formatted: path.Join("linux", defaultArch, defaultVariant), useV2Format: false, }, { input: "s390x", expected: specs.Platform{ OS: defaultOS, Architecture: "s390x", }, formatted: path.Join(defaultOS, "s390x"), useV2Format: false, }, { input: "linux/s390x", expected: specs.Platform{ OS: "linux", Architecture: "s390x", }, formatted: "linux/s390x", useV2Format: false, }, { input: "macOS", expected: specs.Platform{ OS: "darwin", Architecture: defaultArch, Variant: defaultVariant, }, formatted: path.Join("darwin", defaultArch, defaultVariant), useV2Format: false, }, { input: "windows", expected: specs.Platform{ OS: "windows", OSVersion: "", Architecture: defaultArch, Variant: defaultVariant, }, formatted: path.Join("windows", defaultArch, defaultVariant), useV2Format: false, }, { input: "windows()", expected: specs.Platform{ OS: "windows", OSVersion: "", Architecture: defaultArch, Variant: defaultVariant, }, formatted: path.Join("windows", defaultArch, defaultVariant), useV2Format: true, }, { input: "windows(10.0.17763)", expected: specs.Platform{ OS: "windows", OSVersion: "10.0.17763", Architecture: defaultArch, Variant: defaultVariant, }, formatted: path.Join("windows(10.0.17763)", defaultArch, defaultVariant), useV2Format: true, }, } { t.Run(testcase.input, func(t *testing.T) { if testcase.skip { t.Skip("this case is not yet supported") } p, err := Parse(testcase.input) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(p, testcase.expected) { t.Fatalf("platform did not match expected: %#v != %#v", p, testcase.expected) } m := NewMatcher(p) // ensure that match works on the input to the output. if ok := m.Match(testcase.expected); !ok { t.Fatalf("expected specifier %q matches %#v", testcase.input, testcase.expected) } for _, mc := range testcase.matches { if ok := m.Match(mc); !ok { t.Fatalf("expected specifier %q matches %#v", testcase.input, mc) } } formatted := "" if testcase.useV2Format == false { formatted = Format(p) } else { formatted = FormatAll(p) } if formatted != testcase.formatted { t.Fatalf("unexpected format: %q != %q", formatted, testcase.formatted) } // re-parse the formatted output and ensure we are stable reparsed, err := Parse(formatted) if err != nil { t.Fatalf("error parsing formatted output: %v", err) } if testcase.useV2Format == false { if Format(reparsed) != formatted { t.Fatalf("normalized output did not survive the round trip: %v != %v", Format(reparsed), formatted) } } else { if FormatAll(reparsed) != formatted { t.Fatalf("normalized output did not survive the round trip: %v != %v", FormatAll(reparsed), formatted) } } }) } } func TestParseSelectorInvalid(t *testing.T) { for _, testcase := range []struct { input string }{ { input: "", // empty }, { input: "/linux/arm", // leading slash }, { input: "linux/arm/", // trailing slash }, { input: "linux /arm", // spaces }, { input: "linux/&arm", // invalid character }, { input: "linux/arm/foo/bar", // too many components }, } { t.Run(testcase.input, func(t *testing.T) { if _, err := Parse(testcase.input); err == nil { t.Fatalf("should have received an error") } }) } } func FuzzPlatformsParse(f *testing.F) { f.Add("linux/amd64") f.Fuzz(func(t *testing.T, s string) { pf, err := Parse(s) if err != nil && (pf.OS != "" || pf.Architecture != "") { t.Errorf("either %+v or %+v must be nil", err, pf) } }) } golang-github-containerd-platforms-0.2.1/platforms_windows.go000066400000000000000000000020601465206366300245130ustar00rootroot00000000000000/* 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), }, } } golang-github-containerd-platforms-0.2.1/platforms_windows_test.go000066400000000000000000000021241465206366300255530ustar00rootroot00000000000000/* 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 ( "testing" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" ) func TestNormalize(t *testing.T) { require.Equal(t, DefaultSpec(), Normalize(DefaultSpec())) } func TestFallbackOnOSVersion(t *testing.T) { p := specs.Platform{ OS: "windows", Architecture: "amd64", OSVersion: "99.99.99.99", } other := specs.Platform{OS: p.OS, Architecture: p.Architecture} m := NewMatcher(p) require.True(t, m.Match(other)) }