diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index abfcd0c9..23ad3bf3 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -78,6 +78,20 @@ jobs: uses: actions/checkout@v4 - name: check release-build run: make check-release-build + check-doca-drivers: + name: check doca-drivers + runs-on: ubuntu-22.04 + permissions: + contents: read + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v5 + with: + go-version: '1.23' + - name: checkout + uses: actions/checkout@v4 + - name: check doca-drivers + run: make check-doca-drivers unit-tests: name: Unit-tests runs-on: ubuntu-22.04 diff --git a/.gitignore b/.gitignore index 6a48a88f..15641af2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ testbin test build hack/manifests +hack/tmp diff --git a/Makefile b/Makefile index 0947a99f..0e73919f 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ REPO_PATH=$(ORG_PATH)/$(PACKAGE) CHART_PATH=$(CURDIR)/deployment/$(PACKAGE) TOOLSDIR=$(CURDIR)/hack/tools/bin MANIFESTDIR=$(CURDIR)/hack/manifests +HACKTMPDIR=$(CURDIR)/hack/tmp BUILDDIR=$(CURDIR)/build/_output GOFILES=$(shell find . -name "*.go" | grep -vE "(\/vendor\/)|(_test.go)") TESTPKGS=./... @@ -37,6 +38,7 @@ BUILD_VERSION := $(strip $(shell [ -d .git ] && git describe --always --tags --d BUILD_TIMESTAMP := $(shell date -u +"%Y-%m-%dT%H:%M:%S%Z") VCS_BRANCH := $(strip $(shell git rev-parse --abbrev-ref HEAD)) VCS_REF := $(strip $(shell [ -d .git ] && git rev-parse --short HEAD)) +DOCA_DRIVER_RELEASE_URL := https://raw.githubusercontent.com/Mellanox/doca-driver-build/refs/heads/main/release_manifests/ # Docker IMAGE_BUILDER?=docker @@ -109,6 +111,9 @@ $(MANIFESTDIR): $(BUILDDIR): ; $(info Creating build directory...) mkdir -p $@ +$(HACKTMPDIR): + @mkdir -p $@ + build: generate $(BUILDDIR)/$(BINARY_NAME) ; $(info Building $(BINARY_NAME)...) @ ## Build executable file $(info Done!) @@ -356,6 +361,7 @@ clean: ; $(info Cleaning...) @ ## Cleanup everything @rm -rf $(BUILDDIR) @rm -rf $(TOOLSDIR) @rm -rf $(MANIFESTDIR) + @rm -rf $(HACKTMPDIR) .PHONY: help help: ## Show this message @@ -414,6 +420,12 @@ release-build: cd hack && $(GO) run release.go --templateDir ./templates/crs/ --outputDir ../example/crs cd hack && $(GO) run release.go --templateDir ./templates/values/ --outputDir ../deployment/network-operator/ +.PHONY: check-doca-drivers +check-doca-drivers: $(HACKTMPDIR) + $(eval DRIVERVERSION := $(shell yq '.Mofed.version' hack/release.yaml | cut -d'-' -f1)) + wget $(DOCA_DRIVER_RELEASE_URL)$(DRIVERVERSION).yaml -O $(HACKTMPDIR)/doca-driver-matrix.yaml + cd hack && $(GO) run release.go --doca-driver-check --doca-driver-matrix $(HACKTMPDIR)/doca-driver-matrix.yaml + # dev environment MINIKUBE_CLUSTER_NAME = net-op-dev diff --git a/go.mod b/go.mod index e75188ca..dea79acd 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.10.0 github.com/xeipuuv/gojsonschema v1.2.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.4 k8s.io/apimachinery v0.31.4 k8s.io/client-go v0.31.4 @@ -112,7 +113,6 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.31.1 // indirect k8s.io/cli-runtime v0.31.2 // indirect k8s.io/component-base v0.31.2 // indirect diff --git a/hack/release.go b/hack/release.go index a0906a8e..9aa7d61d 100644 --- a/hack/release.go +++ b/hack/release.go @@ -30,6 +30,8 @@ import ( "sigs.k8s.io/yaml" + yamlflow "gopkg.in/yaml.v3" + mellanoxv1alpha1 "github.com/Mellanox/network-operator/api/v1alpha1" "github.com/google/go-containerregistry/pkg/authn" @@ -80,6 +82,19 @@ type Release struct { MaintenanceOperator *ReleaseImageSpec } +// DocaDriverMatrix represent the expected DOCA-Driver OS/arch combinations +type DocaDriverMatrix struct { + Precompiled []struct { + OS string `yaml:"os"` + Arch []string `yaml:"archs,flow"` + Kernels []string `yaml:"kernels,flow"` + } `yaml:"precompiled"` + DynamicallyCompiled []struct { + OS string `yaml:"os,flow"` + Arch []string `yaml:"archs,flow"` + } `yaml:"dynamically_compiled"` +} + func readDefaults(releaseDefaults string) Release { f, err := os.ReadFile(filepath.Clean(releaseDefaults)) if err != nil { @@ -136,11 +151,87 @@ func main() { outputDir := flag.String("outputDir", ".", "Destination directory to render templates to") releaseDefaults := flag.String("releaseDefaults", "release.yaml", "Destination of the release defaults definition") retrieveSha := flag.Bool("with-sha256", false, "retrieve SHA256 for container images references") + docaDriverCheck := flag.Bool("doca-driver-check", false, "Verify DOCA Driver tags") + docaDriverMatrix := flag.String("doca-driver-matrix", "tmp/doca-driver-matrix.yaml", "DOCA Driver tags matrix") flag.Parse() release := readDefaults(*releaseDefaults) readEnvironmentVariables(&release) + + if !*docaDriverCheck { + renderTemplates(&release, templateDir, outputDir, retrieveSha) + } else { + docaDriverTagsCheck(&release, docaDriverMatrix) + } +} + +func docaDriverTagsCheck(release *Release, docaDriverMatrix *string) { + f, err := os.ReadFile(filepath.Clean(*docaDriverMatrix)) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + var config DocaDriverMatrix + if err := yamlflow.Unmarshal(f, &config); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + tags, err := resolveTags(release.Mofed.Repository, release.Mofed.Image) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + if err := validateTags(config, tags, release.Mofed.Version); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } +} + +func validateTags(config DocaDriverMatrix, tags []string, version string) error { + // Build expected OS-arch combinations + expectedCombinations := make(map[string]struct{}) + for _, entry := range config.DynamicallyCompiled { + for _, arch := range entry.Arch { + key := fmt.Sprintf("%s-%s", entry.OS, arch) + expectedCombinations[key] = struct{}{} + } + } + + // Filter tags based on version prefix + filteredTags := []string{} + for _, tag := range tags { + if strings.HasPrefix(tag, version) { + filteredTags = append(filteredTags, tag) + } + } + + unfound := make([]string, 0) + // Validate if each expected combination exists in the filtered tags + for combo := range expectedCombinations { + found := false + for _, tag := range filteredTags { + if strings.Contains(tag, combo) { + found = true + break + } + } + if !found { + unfound = append(unfound, combo) + } + } + if len(unfound) > 0 { + return fmt.Errorf("missing os-arch combinations: %v", unfound) + } + + return nil +} + +func renderTemplates(release *Release, templateDir, outputDir *string, retrieveSha *bool) { if *retrieveSha { - err := resolveImagesSha(&release) + err := resolveImagesSha(release) if err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) @@ -199,14 +290,7 @@ func main() { } func resolveImagesSha(release *Release) error { - nvcrToken := os.Getenv("NGC_CLI_API_KEY") - if nvcrToken == "" { - return fmt.Errorf("NGC_CLI_API_KEY is unset") - } - auth := &authn.Basic{ - Username: "$oauthtoken", - Password: nvcrToken, - } + v := reflect.ValueOf(*release) for i := 0; i < v.NumField(); i++ { field := v.Field(i) @@ -214,7 +298,7 @@ func resolveImagesSha(release *Release) error { releaseImageSpec := field.Interface().(*ReleaseImageSpec) if strings.Contains(releaseImageSpec.Image, "doca-driver") { digests, err := resolveDocaDriversShas(releaseImageSpec.Repository, releaseImageSpec.Image, - releaseImageSpec.Version, auth) + releaseImageSpec.Version) if err != nil { return err } @@ -225,7 +309,7 @@ func resolveImagesSha(release *Release) error { } } else { digest, err := resolveImageSha(releaseImageSpec.Repository, releaseImageSpec.Image, - releaseImageSpec.Version, auth) + releaseImageSpec.Version) if err != nil { return err } @@ -238,23 +322,15 @@ func resolveImagesSha(release *Release) error { return nil } -func resolveImageSha(repo, image, tag string, auth *authn.Basic) (string, error) { +func resolveImageSha(repo, image, tag string) (string, error) { ref, err := containerregistryname.ParseReference(fmt.Sprintf("%s/%s:%s", repo, image, tag)) if err != nil { return "", err } var desc *remote.Descriptor - if strings.Contains(repo, "nvstaging") { - desc, err = remote.Get(ref, remote.WithAuth(auth)) - if err != nil { - return "", err - } - } else { - // Container registry might fail if providing unneeded auth - desc, err = remote.Get(ref) - if err != nil { - return "", err - } + desc, err = remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + if err != nil { + return "", err } digest, err := containerregistryv1.NewHash(desc.Descriptor.Digest.String()) if err != nil { @@ -263,31 +339,31 @@ func resolveImageSha(repo, image, tag string, auth *authn.Basic) (string, error) return digest.String(), nil } -func resolveDocaDriversShas(repoName, imageName, ver string, auth *authn.Basic) ([]string, error) { - shaArray := make([]string, 0) +func resolveTags(repoName, imageName string) ([]string, error) { + tags := make([]string, 0) image := fmt.Sprintf("%s/%s", repoName, imageName) repo, err := containerregistryname.NewRepository(image) if err != nil { - return shaArray, err + return tags, err } - var tags []string - if strings.Contains(repoName, "nvstaging") { - tags, err = remote.List(repo, remote.WithAuth(auth)) - if err != nil { - return shaArray, err - } - } else { - // Container registry might fail if providing unneeded auth - tags, err = remote.List(repo) - if err != nil { - return shaArray, err - } + tags, err = remote.List(repo, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + if err != nil { + return tags, err } sort.Strings(tags) + return tags, nil +} + +func resolveDocaDriversShas(repoName, imageName, ver string) ([]string, error) { + shaArray := make([]string, 0) + tags, err := resolveTags(repoName, imageName) + if err != nil { + return shaArray, err + } shaSet := make(map[string]interface{}) for _, tag := range tags { if strings.Contains(tag, ver) && (strings.Contains(tag, "rhcos") || strings.Contains(tag, "rhel")) { - digest, err := resolveImageSha(repoName, imageName, tag, auth) + digest, err := resolveImageSha(repoName, imageName, tag) if err != nil { return shaArray, err }