diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bb7902b81bf1..3b8b2b93b6e9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -85,6 +85,32 @@ jobs: target-os: linux target-arch: amd64 + check-images: + name: "Check :: Images" + needs: [build-k0s] + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: "Workflow run :: Checkout" + uses: actions/checkout@v4 + with: + persist-credentials: false + show-progress: false + + - name: "Download :: Airgap image list" + uses: actions/download-artifact@v4 + with: + name: airgap-image-list-linux-amd64 + + - name: "Check :: Image validity" + run: | + mkdir -p embedded-bins/staging/linux/bin + make --touch airgap-images.txt + make check-image-validity + generate-sbom: name: "Build :: SBOM" needs: [build-k0s] @@ -144,9 +170,6 @@ jobs: make --touch codegen make check-unit - - name: Validate OCI images manifests - run: make check-image-validity - unittests-k0s-windows-amd64: name: "Unit tests :: windows-amd64" runs-on: windows-2022 diff --git a/Makefile b/Makefile index d91bc05627a2..73bbc9ec9cac 100644 --- a/Makefile +++ b/Makefile @@ -259,8 +259,8 @@ check-unit: go.sum codegen CGO_CFLAGS='$(BUILD_CGO_CFLAGS)' $(GO) test -tags=hack $(GO_TEST_RACE) -ldflags='$(LD_FLAGS)' `$(GO) list -tags=hack $(GO_CHECK_UNIT_DIRS)` .PHONY: check-image-validity -check-image-validity: go.sum - $(GO) run -tags=hack hack/validate-images/main.go -architectures amd64,arm64,arm +check-image-validity: airgap-images.txt + hack/validate-images.sh &2 + check=$(check_platforms "$@") + if [ "$check" != "ok" ]; then + ret=1 + printf '\033[31m%s\033[0m\n' "$check" >&2 + else + printf '\033[32mok: %s\033[0m\n' "$*" >&2 + fi +done + +exit $ret diff --git a/hack/validate-images/main.go b/hack/validate-images/main.go deleted file mode 100644 index ef433c944149..000000000000 --- a/hack/validate-images/main.go +++ /dev/null @@ -1,122 +0,0 @@ -//go:build hack - -/* -Copyright 2021 k0s 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 main - -import ( - "encoding/json" - "flag" - "fmt" - "os" - "strings" - - "github.com/k0sproject/k0s/pkg/airgap" - "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" - - "k8s.io/utils/strings/slices" - - "github.com/estesp/manifest-tool/v2/pkg/registry" - "github.com/estesp/manifest-tool/v2/pkg/store" - "github.com/estesp/manifest-tool/v2/pkg/types" - "github.com/estesp/manifest-tool/v2/pkg/util" - - ocispec "github.com/opencontainers/image-spec/specs-go/v1" -) - -func check(e error) { - if e != nil { - panic(e) - } -} - -func main() { - var architectures []string - var architecturesString string - flag.StringVar(&architecturesString, "architectures", "amd64,arm64,arm", "which architectures to search for") - flag.Parse() - architectures = strings.Split(architecturesString, ",") - if len(architectures) < 1 { - panic("No architectures given") - } - cfg := v1beta1.DefaultClusterConfig() - uris := airgap.GetImageURIs(cfg.Spec, false) - - var errs []error - errs = append(errs, validateImages(uris, architectures)...) - - // Envoy doesn't have an official ARMv7 image! - architectures = slices.Filter(nil, architectures, func(s string) bool { return s != "arm" }) - errs = append(errs, validateImages([]string{v1beta1.DefaultEnvoyProxyImage().URI()}, architectures)...) - - if len(errs) > 0 { - fmt.Fprintln(os.Stderr, "Not all images were valid.") - for _, err := range errs { - fmt.Fprintln(os.Stderr, "Error: ", err) - } - os.Exit(1) - } -} - -func validateImages(uris []string, architectures []string) (errs []error) { - for _, name := range uris { - fmt.Println("validating image", name, "to have architectures: ", architectures) - imageRef, err := util.ParseName(name) - check(err) - memoryStore := store.NewMemoryStore() - err = util.CreateRegistryHost(imageRef, "", "", true, true, "", false) - check(err) - descriptor, err := registry.FetchDescriptor(util.GetResolver(), memoryStore, imageRef) - check(err) - _, db, _ := memoryStore.Get(descriptor) - switch descriptor.MediaType { - case ocispec.MediaTypeImageIndex, types.MediaTypeDockerSchema2ManifestList: - // this is a multi-platform image descriptor; marshal to Index type - var idx ocispec.Index - check(json.Unmarshal(db, &idx)) - if validationErrs := validateList(name, architectures, idx); validationErrs != nil { - errs = append(errs, validationErrs...) - } - case ocispec.MediaTypeImageManifest, types.MediaTypeDockerSchema2Manifest: - errs = append(errs, fmt.Errorf("image %s has single manifest, but we need multiarch manifests", name)) - default: - errs = append(errs, fmt.Errorf("image %s has unknown manifest type, can't validate architectures", name)) - } - } - return -} - -func validateList(name string, architectures []string, index ocispec.Index) (errs []error) { - searchFor := map[string]bool{} - - for _, m := range index.Manifests { - for _, platformToCheck := range architectures { - if m.Platform.Architecture == platformToCheck { - searchFor[platformToCheck] = true - } - } - } - - for _, platform := range architectures { - _, found := searchFor[platform] - if !found { - errs = append(errs, fmt.Errorf("platform %s not found for image %s", platform, name)) - } - } - - return -}