diff --git a/.github/actions/prepare-release/action.yaml b/.github/actions/prepare-release/action.yaml new file mode 100644 index 0000000..c79a54f --- /dev/null +++ b/.github/actions/prepare-release/action.yaml @@ -0,0 +1,13 @@ +name: Prepare-Release + +runs: + using: composite + steps: + - uses: actions/setup-go@v6 + with: + go-version: '1.26' + - name: prepare-release + shell: bash + run: | + set -eu + make generate diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5e8973d..12f1ac2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,6 +16,8 @@ jobs: uses: gardener/cc-utils/.github/workflows/prepare.yaml@v1 with: mode: ${{ inputs.mode }} + version-operation: ${{ inputs.release-version }} + version-commit-callback-action-path: .github/actions/prepare-release permissions: id-token: write diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a635ce8..1c1f7a5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -33,6 +33,7 @@ jobs: with: release-commit-target: branch next-version: ${{ inputs.next-version }} + next-version-callback-action-path: .github/actions/prepare-release assets: | - name: gardener-landscape-kit-darwin-amd64 type: ocm-resource diff --git a/Makefile b/Makefile index 67a6037..adf12b5 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ generate: tools-for-generate $(GOIMPORTS) $(FLUX_CLI) $(YQ) @REPO_ROOT=$(REPO_ROOT) GARDENER_HACK_DIR=$(GARDENER_HACK_DIR) $(HACK_DIR)/update-codegen.sh @GARDENER_HACK_DIR=$(GARDENER_HACK_DIR) $(HACK_DIR)/update-github-templates.sh @ARRAY_KEY=matchPackageNames NEEDLE='// GENERATOR-PIN' GARDENER_HACK_DIR=$(GARDENER_HACK_DIR) RENOVATE_CONFIG=$(REPO_ROOT)/.github/renovate.json5 bash $(GARDENER_HACK_DIR)/generate-renovate-ignore-deps.sh + @$(HACK_DIR)/sync-glk-version.sh $(MAKE) format .PHONY: check diff --git a/componentvector/components.go b/componentvector/components.go index 8965c5f..84c4759 100644 --- a/componentvector/components.go +++ b/componentvector/components.go @@ -37,4 +37,6 @@ const ( NameGardenerGardenerExtensionShootNetworkingProblemdetector = "github.com/gardener/gardener-extension-shoot-networking-problemdetector" // NameGardenerGardenerExtensionShootOidcService is a constant for a component in the component vector with name 'github.com/gardener/gardener-extension-shoot-oidc-service'. NameGardenerGardenerExtensionShootOidcService = "github.com/gardener/gardener-extension-shoot-oidc-service" + // NameGardenerGardenerLandscapeKit is a constant for a component in the component vector with name 'github.com/gardener/gardener-landscape-kit'. + NameGardenerGardenerLandscapeKit = "github.com/gardener/gardener-landscape-kit" ) diff --git a/componentvector/components.yaml b/componentvector/components.yaml index 24f1c3e..1c66521 100644 --- a/componentvector/components.yaml +++ b/componentvector/components.yaml @@ -1,4 +1,7 @@ components: +- name: github.com/gardener/gardener-landscape-kit + sourceRepository: https://github.com/gardener/gardener-landscape-kit + version: v0.2.0-dev - name: github.com/gardener/gardener sourceRepository: https://github.com/gardener/gardener version: v1.140.1 @@ -122,11 +125,11 @@ components: helmChart: repository: europe-docker.pkg.dev/gardener-project/releases/charts/gardener/extensions/shoot-dns-service-admission-runtime shootDnsServiceAdmissionApplication: - helmChart: - repository: europe-docker.pkg.dev/gardener-project/releases/charts/gardener/extensions/shoot-dns-service-admission-application + helmChart: + repository: europe-docker.pkg.dev/gardener-project/releases/charts/gardener/extensions/shoot-dns-service-admission-application shootDnsService: - helmChart: - repository: europe-docker.pkg.dev/gardener-project/releases/charts/gardener/extensions/shoot-dns-service + helmChart: + repository: europe-docker.pkg.dev/gardener-project/releases/charts/gardener/extensions/shoot-dns-service - name: github.com/gardener/gardener-extension-shoot-oidc-service sourceRepository: https://github.com/gardener/gardener-extension-shoot-oidc-service version: v0.38.0 diff --git a/docs/api-reference/landscapekit-v1alpha1.md b/docs/api-reference/landscapekit-v1alpha1.md index 9b23792..cc9b2fd 100644 --- a/docs/api-reference/landscapekit-v1alpha1.md +++ b/docs/api-reference/landscapekit-v1alpha1.md @@ -150,6 +150,23 @@ _Appears in:_ | `landscape` _string_ | Landscape is the relative path to the landscape directory within the Git repository. | | Required: \{\}
| +#### VersionCheckMode + +_Underlying type:_ _string_ + +VersionCheckMode controls the behavior when the tool version doesn't match the component version. + + + +_Appears in:_ +- [VersionConfiguration](#versionconfiguration) + +| Field | Description | +| --- | --- | +| `Strict` | VersionCheckModeStrict indicates that version mismatches should cause an error.
| +| `Warning` | VersionCheckModeWarning indicates that version mismatches should only log a warning.
| + + #### VersionConfiguration @@ -164,5 +181,6 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `defaultVersionsUpdateStrategy` _[DefaultVersionsUpdateStrategy](#defaultversionsupdatestrategy)_ | UpdateStrategy determines whether the versions in the default vector should be updated from the release branch on resolve.
Possible values are "Disabled" (default) and "ReleaseBranch". | | Optional: \{\}
| +| `checkMode` _[VersionCheckMode](#versioncheckmode)_ | CheckMode determines the behavior when the tool version doesn't match the gardener-landscape-kit version in the component vector.
Possible values are "Strict" (default) and "Warning".
In strict mode, version mismatches cause errors. In warning mode, only warnings are logged. | | Optional: \{\}
| diff --git a/example/20-componentconfig-glk.yaml b/example/20-componentconfig-glk.yaml index 77b010d..26522e7 100644 --- a/example/20-componentconfig-glk.yaml +++ b/example/20-componentconfig-glk.yaml @@ -19,4 +19,5 @@ kind: LandscapeKitConfiguration # - component-name # versionConfig: # defaultVersionsUpdateStrategy: ReleaseBranch +# checkMode: Strict # or Warning # mergeMode: Hint diff --git a/hack/sync-glk-version.sh b/hack/sync-glk-version.sh new file mode 100755 index 0000000..28522ec --- /dev/null +++ b/hack/sync-glk-version.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors +# +# SPDX-License-Identifier: Apache-2.0 + +set -o errexit +set -o nounset +set -o pipefail + +# This script syncs the gardener-landscape-kit component version in +# componentvector/components.yaml with the version specified in the VERSION file. +# If the component doesn't exist, it adds it as the first entry. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +VERSION_FILE="$REPO_ROOT/VERSION" +COMPONENTS_FILE="$REPO_ROOT/componentvector/components.yaml" + +# Read the VERSION file +if [[ ! -f "$VERSION_FILE" ]]; then + echo "ERROR: VERSION file not found at $VERSION_FILE" >&2 + exit 1 +fi + +VERSION=$(cat "$VERSION_FILE" | tr -d '[:space:]') + +if [[ -z "$VERSION" ]]; then + echo "ERROR: VERSION file is empty" >&2 + exit 1 +fi + +# Check if components.yaml exists +if [[ ! -f "$COMPONENTS_FILE" ]]; then + echo "ERROR: components.yaml not found at $COMPONENTS_FILE" >&2 + exit 1 +fi + +GLK_COMPONENT_NAME="github.com/gardener/gardener-landscape-kit" +REPO_URL="https://$GLK_COMPONENT_NAME" + +# Check if the GLK component already exists in the file +if [[ $(yq eval '[.components[] | select(.name == "'"$GLK_COMPONENT_NAME"'")] | length' "$COMPONENTS_FILE") -gt 0 ]]; then + # Component exists - update its version + yq eval -i --indent 2 -c "(.components[] | select(.name == \"$GLK_COMPONENT_NAME\") | .version) = \"$VERSION\"" "$COMPONENTS_FILE" + echo "Updated $GLK_COMPONENT_NAME component version to $VERSION" +else + # Component doesn't exist - prepend it as the first entry + yq eval -i --indent 2 -c ".components = [{\"name\": \"$GLK_COMPONENT_NAME\", \"sourceRepository\": \"$REPO_URL\", \"version\": \"$VERSION\"}] + .components" "$COMPONENTS_FILE" + echo "Added $GLK_COMPONENT_NAME component with version $VERSION as first entry" +fi diff --git a/pkg/apis/config/v1alpha1/types.go b/pkg/apis/config/v1alpha1/types.go index a6c4207..2e0d075 100644 --- a/pkg/apis/config/v1alpha1/types.go +++ b/pkg/apis/config/v1alpha1/types.go @@ -117,12 +117,33 @@ var AllowedDefaultVersionsUpdateStrategies = []string{ string(DefaultVersionsUpdateStrategyDisabled), } +// VersionCheckMode controls the behavior when the tool version doesn't match the component version. +type VersionCheckMode string + +const ( + // VersionCheckModeStrict indicates that version mismatches should cause an error. + VersionCheckModeStrict VersionCheckMode = "Strict" + // VersionCheckModeWarning indicates that version mismatches should only log a warning. + VersionCheckModeWarning VersionCheckMode = "Warning" +) + +// AllowedVersionCheckModes lists all allowed version check modes. +var AllowedVersionCheckModes = []string{ + string(VersionCheckModeStrict), + string(VersionCheckModeWarning), +} + // VersionConfiguration contains configuration for versioning. type VersionConfiguration struct { // UpdateStrategy determines whether the versions in the default vector should be updated from the release branch on resolve. // Possible values are "Disabled" (default) and "ReleaseBranch". // +optional DefaultVersionsUpdateStrategy *DefaultVersionsUpdateStrategy `json:"defaultVersionsUpdateStrategy,omitempty"` + // CheckMode determines the behavior when the tool version doesn't match the gardener-landscape-kit version in the component vector. + // Possible values are "Strict" (default) and "Warning". + // In strict mode, version mismatches cause errors. In warning mode, only warnings are logged. + // +optional + CheckMode *VersionCheckMode `json:"checkMode,omitempty"` } // MergeMode controls how operator overwrites are handled during three-way merge. diff --git a/pkg/apis/config/v1alpha1/validation/validation.go b/pkg/apis/config/v1alpha1/validation/validation.go index 704cee8..65a7b47 100644 --- a/pkg/apis/config/v1alpha1/validation/validation.go +++ b/pkg/apis/config/v1alpha1/validation/validation.go @@ -169,5 +169,9 @@ func ValidateVersionConfig(conf *configv1alpha1.VersionConfiguration, fldPath *f allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultVersionsUpdateStrategy"), *conf.DefaultVersionsUpdateStrategy, "allowed values are: "+strings.Join(configv1alpha1.AllowedDefaultVersionsUpdateStrategies, ", "))) } + if conf.CheckMode != nil && !slices.Contains(configv1alpha1.AllowedVersionCheckModes, string(*conf.CheckMode)) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("checkMode"), *conf.CheckMode, "allowed values are: "+strings.Join(configv1alpha1.AllowedVersionCheckModes, ", "))) + } + return allErrs } diff --git a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go index 3ce6502..02f7197 100644 --- a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go @@ -200,6 +200,11 @@ func (in *VersionConfiguration) DeepCopyInto(out *VersionConfiguration) { *out = new(DefaultVersionsUpdateStrategy) **out = **in } + if in.CheckMode != nil { + in, out := &in.CheckMode, &out.CheckMode + *out = new(VersionCheckMode) + **out = **in + } return } diff --git a/pkg/cmd/generate/base/base.go b/pkg/cmd/generate/base/base.go index 48dd8eb..edc991f 100644 --- a/pkg/cmd/generate/base/base.go +++ b/pkg/cmd/generate/base/base.go @@ -56,6 +56,10 @@ func run(_ context.Context, opts *options.Options) error { return fmt.Errorf("failed to register components: %w", err) } + if err := version.CheckGLKComponentVersion(componentOpts.GetComponentVector(), opts.Config, opts.Log); err != nil { + return fmt.Errorf("version check failed: %w", err) + } + if err := reg.GenerateBase(componentOpts); err != nil { return err } diff --git a/pkg/cmd/generate/landscape/landscape.go b/pkg/cmd/generate/landscape/landscape.go index cc84505..82843ba 100644 --- a/pkg/cmd/generate/landscape/landscape.go +++ b/pkg/cmd/generate/landscape/landscape.go @@ -77,6 +77,10 @@ func run(_ context.Context, opts *options.Options) error { return fmt.Errorf("failed to create component options: %w", err) } + if err := version.CheckGLKComponentVersion(componentOpts.GetComponentVector(), opts.Config, opts.Log); err != nil { + return fmt.Errorf("version validation failed: %w", err) + } + reg := registry.New() if err := registry.RegisterAllComponents(reg, opts.Config); err != nil { return fmt.Errorf("failed to register components: %w", err) diff --git a/pkg/components/gardener/landscape-kit/component.go b/pkg/components/gardener/landscape-kit/component.go new file mode 100644 index 0000000..ec1631d --- /dev/null +++ b/pkg/components/gardener/landscape-kit/component.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package landscapekit + +const ( + // ComponentName is the name of the gardener-landscape-kit component. + ComponentName = "gardener-landscape-kit" +) diff --git a/pkg/utils/version/metadata.go b/pkg/utils/version/metadata.go index 69a73c7..70d7963 100644 --- a/pkg/utils/version/metadata.go +++ b/pkg/utils/version/metadata.go @@ -12,10 +12,14 @@ import ( "strings" "github.com/Masterminds/semver/v3" + "github.com/go-logr/logr" "github.com/spf13/afero" apimachineryversion "k8s.io/apimachinery/pkg/version" componentbaseversion "k8s.io/component-base/version" + "github.com/gardener/gardener-landscape-kit/componentvector" + configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1" + utilscomponentvector "github.com/gardener/gardener-landscape-kit/pkg/utils/componentvector" "github.com/gardener/gardener-landscape-kit/pkg/utils/files" ) @@ -139,6 +143,39 @@ func ValidateLandscapeVersionCompatibility(targetPath string, fs afero.Afero) er return ValidateVersionCompatibility(baseMetadata.Version, version.GitVersion) } +// CheckGLKComponentVersion validates that the tool version matches the gardener-landscape-kit +// component version in the component vector. +// The behavior depends on the checkMode in the configuration: +// - If checkMode is "Strict" (or nil/default), returns an error on mismatch. +// - If checkMode is "Warning", logs a warning on mismatch and returns nil. +func CheckGLKComponentVersion(cv utilscomponentvector.Interface, config *configv1alpha1.LandscapeKitConfiguration, log logr.Logger) error { + toolVersion := version.GitVersion + componentVersion, found := cv.FindComponentVersion(componentvector.NameGardenerGardenerLandscapeKit) + + if !found { + return fmt.Errorf("gardener-landscape-kit component not found in component vector - this should not happen as it's part of the default component vector") + } + + if toolVersion != componentVersion { + // Determine the check mode (default to Strict) + checkMode := configv1alpha1.VersionCheckModeStrict + if config != nil && config.VersionConfig != nil && config.VersionConfig.CheckMode != nil { + checkMode = *config.VersionConfig.CheckMode + } + + err := fmt.Errorf("version mismatch: tool version (%s) does not match gardener-landscape-kit component version (%s) in component vector - obtain the matching gardener-landscape-kit version or adjust the component vector", toolVersion, componentVersion) + + if checkMode == configv1alpha1.VersionCheckModeWarning { + log.Error(err, "Precheck failed") + return nil + } + + return err + } + + return nil +} + // Get returns the version of GLK. func Get() apimachineryversion.Info { return version diff --git a/pkg/utils/version/metadata_test.go b/pkg/utils/version/metadata_test.go index 49e929e..5ff57bd 100644 --- a/pkg/utils/version/metadata_test.go +++ b/pkg/utils/version/metadata_test.go @@ -8,10 +8,14 @@ import ( "encoding/json" "path/filepath" + "github.com/go-logr/logr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spf13/afero" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1" + "github.com/gardener/gardener-landscape-kit/pkg/utils/componentvector" "github.com/gardener/gardener-landscape-kit/pkg/utils/version" ) @@ -231,4 +235,152 @@ var _ = Describe("Version Metadata", func() { Expect(err).To(MatchError(ContainSubstring("is newer than base generation version"))) }) }) + + Describe("#CheckGLKComponentVersion", func() { + var log logr.Logger + + BeforeEach(func() { + log = zap.New(zap.WriteTo(GinkgoWriter)) + }) + + type testCase struct { + componentVersion string + checkMode *configv1alpha1.VersionCheckMode + expectError bool + errorContains []string + } + + DescribeTable("version checking behavior", + func(tc testCase) { + baseYAML := []byte(` +components: + - name: github.com/gardener/gardener-landscape-kit + sourceRepository: https://github.com/gardener/gardener-landscape-kit + version: ` + tc.componentVersion + ` +`) + cv, err := componentvector.NewWithOverride(baseYAML) + Expect(err).NotTo(HaveOccurred()) + + var config *configv1alpha1.LandscapeKitConfiguration + if tc.checkMode != nil { + config = &configv1alpha1.LandscapeKitConfiguration{ + VersionConfig: &configv1alpha1.VersionConfiguration{ + CheckMode: tc.checkMode, + }, + } + } + + err = version.CheckGLKComponentVersion(cv, config, log) + + if tc.expectError { + Expect(err).To(HaveOccurred()) + for _, substr := range tc.errorContains { + Expect(err.Error()).To(ContainSubstring(substr)) + } + } else { + Expect(err).NotTo(HaveOccurred()) + } + }, + Entry("should pass when versions match with nil config (default strict)", + testCase{ + componentVersion: version.Get().GitVersion, + checkMode: nil, + expectError: false, + }), + Entry("should pass when versions match in strict mode", + testCase{ + componentVersion: version.Get().GitVersion, + checkMode: ptr(configv1alpha1.VersionCheckModeStrict), + expectError: false, + }), + Entry("should pass when versions match in warning mode", + testCase{ + componentVersion: version.Get().GitVersion, + checkMode: ptr(configv1alpha1.VersionCheckModeWarning), + expectError: false, + }), + Entry("should fail when versions differ in strict mode", + testCase{ + componentVersion: "v0.99.99-test", + checkMode: ptr(configv1alpha1.VersionCheckModeStrict), + expectError: true, + errorContains: []string{"version mismatch", version.Get().GitVersion, "v0.99.99-test"}, + }), + Entry("should not fail when versions differ in warning mode", + testCase{ + componentVersion: "v0.99.99-test", + checkMode: ptr(configv1alpha1.VersionCheckModeWarning), + expectError: false, + }), + Entry("should use exact string matching - v0.2.0-dev vs v0.2.0 in strict mode", + func() testCase { + currentVersion := version.Get().GitVersion + var differentButRelated string + if currentVersion == "v0.2.0-dev" { + differentButRelated = "v0.2.0" + } else { + differentButRelated = currentVersion + "-modified" + } + return testCase{ + componentVersion: differentButRelated, + checkMode: ptr(configv1alpha1.VersionCheckModeStrict), + expectError: true, + errorContains: []string{"version mismatch"}, + } + }()), + Entry("should use exact string matching - v0.2.0-dev vs v0.2.0 in warning mode", + func() testCase { + currentVersion := version.Get().GitVersion + var differentButRelated string + if currentVersion == "v0.2.0-dev" { + differentButRelated = "v0.2.0" + } else { + differentButRelated = currentVersion + "-modified" + } + return testCase{ + componentVersion: differentButRelated, + checkMode: ptr(configv1alpha1.VersionCheckModeWarning), + expectError: false, + } + }()), + ) + + It("should fail when GLK component is not found in both modes", func() { + baseYAML := []byte(` +components: + - name: github.com/gardener/other-component + sourceRepository: https://github.com/gardener/other-component + version: v1.0.0 +`) + cv, err := componentvector.NewWithOverride(baseYAML) + Expect(err).NotTo(HaveOccurred()) + + // Test strict mode + strictMode := configv1alpha1.VersionCheckModeStrict + strictConfig := &configv1alpha1.LandscapeKitConfiguration{ + VersionConfig: &configv1alpha1.VersionConfiguration{ + CheckMode: &strictMode, + }, + } + + err = version.CheckGLKComponentVersion(cv, strictConfig, log) + Expect(err).To(MatchError(ContainSubstring("gardener-landscape-kit component not found"))) + + // Test warning mode + warningMode := configv1alpha1.VersionCheckModeWarning + warningConfig := &configv1alpha1.LandscapeKitConfiguration{ + VersionConfig: &configv1alpha1.VersionConfiguration{ + CheckMode: &warningMode, + }, + } + + err = version.CheckGLKComponentVersion(cv, warningConfig, log) + Expect(err).To(MatchError(ContainSubstring("gardener-landscape-kit component not found"))) + }) + }) }) + +// ptr is a helper function to create a pointer to a value +func ptr[T any](v T) *T { + return &v +}