Skip to content

feat: automate Kubernetes version lifecycle management in CloudProfiles - #43

Merged
valeryia-hurynovich merged 8 commits into
cobaltcore-dev:masterfrom
adziauho:update-kuberentes-versions
Aug 11, 2026
Merged

feat: automate Kubernetes version lifecycle management in CloudProfiles#43
valeryia-hurynovich merged 8 commits into
cobaltcore-dev:masterfrom
adziauho:update-kuberentes-versions

Conversation

@adziauho

@adziauho adziauho commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements automated Kubernetes version lifecycle management in cloud-profile-sync, extending the operator to manage Kubernetes versions in CloudProfiles in addition to the existing machine image management.

Closes cc/unified-kubernetes#1174 (SAP internal)

Changes

  • New kubernetessync packageKubernetesImageUpdater writes spec.kubernetes.versions to a CloudProfile, filtering out versions whose expiration date has passed the configured threshold
  • New LandscapeKubernetesSource — fetches Kubernetes versions from two sources and merges them:
    • OCI (Keppel): reads an OCM component descriptor from an OCI artifact to discover available kube-apiserver versions
    • GitHub: fetches a YAML file (providers[].versions[]) for version classifications (supported/deprecated/expired) and expiration dates; supports both PAT and GitHub App (JWT/RSA) authentication
  • CRD extendedManagedCloudProfileSpec gains kubernetesVersionUpdateConfig with expirationThreshold and landscapeSetup (OCI + GitHub config)
  • Controller refactormanagedcloudprofile_controller.go split into:
    • cloud_profile.go — CloudProfile reconciliation and machine image / Kubernetes version update logic
    • garbage_collection.go — GC logic (no functional changes)
  • cloudprofilesync/ reorganized into ossync/ and kubernetessync/ subpackages for cleaner separation of concerns

Test plan

  • Unit tests for KubernetesImageUpdater (expiration threshold filtering)
  • Unit tests for LandscapeKubernetesSource (OCI + GitHub fetch, version merging)
  • Verify existing OS image sync tests still pass after package restructure
  • Integration test against QA landscape with a real KubernetesVersionUpdateConfig

Summary by CodeRabbit

  • New Features
    • Added optional Kubernetes version updates from Landscape sources, with expiration filtering and GitHub authentication.
    • Added OCI-based machine-image synchronization with version, architecture, capability, and in-place update support.
    • Added automatic cleanup of unreferenced, expired machine-image versions.
    • Added configuration for OCI registries, GitHub repositories, credentials, TLS, and update settings.
  • Bug Fixes
    • Preserved existing versions when update sources return no usable data.
    • Improved status condition timestamps and error reporting during CloudProfile updates.
  • Tests
    • Expanded coverage for synchronization, authentication, cleanup, and update edge cases.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds Kubernetes version update configuration, shared OSSync contracts, OCI and Landscape sources, GitHub authentication, CloudProfile reconciliation, and OCI image garbage collection.

Changes

CloudProfile API and schema contracts

Layer / File(s) Summary
ManagedCloudProfile configuration
api/v1alpha1/managedcloudprofile.go, api/v1alpha1/zz_generated.deepcopy.go, crd/...managedcloudprofiles.yaml, go.mod
Adds Kubernetes version update settings, Landscape OCI/GitHub configuration, deepcopy support, CRD validation, and direct dependencies.

Shared OSSync and OCI source contracts

Layer / File(s) Summary
OSSynс source and provider contracts
cloudprofilesync/ossync/os_image_updater.go, cloudprofilesync/ossync/provider/ironcore/*
Introduces shared SourceImage, Source, and Provider types. In-place update metadata now comes from SupportInPlaceUpdate.
OCI repository and source integration
cloudprofilesync/ocirepo/*, cloudprofilesync/ossync/source/oci/*, cloudprofilesync/ossync/suite_test.go
Adds OCI repository construction, concurrent manifest processing, feature filtering, source-image extraction, and OSSync integration tests.

Landscape Kubernetes version source

Layer / File(s) Summary
Landscape retrieval and authentication
cloudprofilesync/k8ssync/k8s_image_updater.go, cloudprofilesync/k8ssync/source/landscape/*
Fetches OCI-supported Kubernetes versions, reads GitHub provider classifications, intersects the results, and supports PAT and GitHub App authentication.
Expiration filtering
cloudprofilesync/k8ssync/k8s_image_updater_test.go
Filters expired versions, retains non-expiring and recent versions, propagates source errors, and preserves existing data when no versions remain.

Controller reconciliation and garbage collection

Layer / File(s) Summary
CloudProfile reconciliation
controllers/cloud_profile.go, controllers/managedcloudprofile_controller.go, controllers/managedcloudprofile_controller_test.go
Reconciles CloudProfiles, applies image and Kubernetes version updates, loads credentials, preserves condition timestamps, and updates controller tests.
OCI image garbage collection
controllers/garbage_collection.go, controllers/managedcloudprofile_controller_test.go
Lists registry tags, preserves referenced images, deletes expired unreferenced images, cleans provider configuration, and reports failures through status.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ManagedCloudProfileController
  participant KubernetesVersionUpdater
  participant LandscapeKubernetesSource
  participant OCIRepository
  participant GitHubAPI
  ManagedCloudProfileController->>KubernetesVersionUpdater: Update CloudProfile versions
  KubernetesVersionUpdater->>LandscapeKubernetesSource: FetchVersions(ctx)
  LandscapeKubernetesSource->>OCIRepository: Read latest tag and component descriptor
  LandscapeKubernetesSource->>GitHubAPI: Read provider classifications
  LandscapeKubernetesSource-->>KubernetesVersionUpdater: Return intersected versions
  KubernetesVersionUpdater-->>ManagedCloudProfileController: Update CloudProfileSpec
Loading

Possibly related PRs

Suggested reviewers: defo89

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: automated Kubernetes version lifecycle management in CloudProfiles.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (9)
cloudprofilesync/ossync/source/oci/os_source_test.go (1)

104-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the _usi to SupportInPlaceUpdate mapping.

This fixture includes _usi, but it does not assert versions[0].SupportInPlaceUpdate. Add a true assertion here and a valid-feature fixture without _usi that asserts false. This covers the new source-to-updater contract.

Proposed test addition
 Expect(versions[0].Capabilities).To(Equal(gardencorev1beta1.Capabilities{
 	"architecture": {"amd64"},
 	"feature":      {"sci", "_usi"},
 }))
+Expect(versions[0].SupportInPlaceUpdate).To(BeTrue())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/ossync/source/oci/os_source_test.go` around lines 104 - 108,
Extend the OCI fixture tests around NewOCI to assert that a source entry
containing _usi maps versions[0].SupportInPlaceUpdate to true. Add a separate
valid-feature fixture without _usi and assert the same field is false, covering
both sides of the source-to-updater contract.
controllers/cloud_profile.go (1)

38-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider moving the network calls out of the CreateOrPatch mutate function.

updateMachineImages contacts an OCI registry. updateKubernetesVersions contacts an OCI registry and the GitHub API. Both run inside the mutate closure passed to controllerutil.CreateOrPatch. Two consequences follow:

  • The closure is not guaranteed to run exactly once. Any future conflict-retry wrapper around CreateOrPatch repeats every remote call.
  • A slow registry or a slow GitHub endpoint holds the closure open while the CloudProfile object is staged for patching, which lengthens the window for a conflicting write.

Resolve the source versions before the CreateOrPatch call, then apply the resolved values inside the closure. This also makes the closure pure and easier to test.

This is a structural change. Defer it if the current behaviour is acceptable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/cloud_profile.go` around lines 38 - 58, The CreateOrPatch mutate
closure currently performs remote calls through updateMachineImages and
updateKubernetesVersions. Resolve all machine-image and Kubernetes-version
updates before invoking controllerutil.CreateOrPatch, then apply those
precomputed values inside the closure while preserving error propagation and
existing defaults.
controllers/garbage_collection.go (3)

192-195: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Retry the CloudProfile update on conflict.

deleteVersions performs a Get at line 116 and an Update at line 192. reconcileCloudProfile patched the same CloudProfile moments earlier in the same reconcile, and the informer cache may still serve the pre-patch resourceVersion. The Update then returns a Conflict error.

Line 104 handles only apierrors.IsInvalid, so a conflict propagates up, sets FailedReconcileStatus, and returns an error. The ManagedCloudProfile reports a failure for a transient and expected condition.

Wrap the read-modify-write in retry.RetryOnConflict.

♻️ Proposed refactor
+import "k8s.io/client-go/util/retry"
-	if err := r.Update(ctx, &cp); err != nil {
-		return err
-	}
-	return nil
+	return r.Update(ctx, &cp)

Then wrap the whole Get-mutate-Update body of deleteVersions in retry.RetryOnConflict(retry.DefaultRetry, func() error { ... }) so that the retry re-reads the CloudProfile.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/garbage_collection.go` around lines 192 - 195, Wrap the entire
Get-mutate-Update flow in deleteVersions with retry.RetryOnConflict using
retry.DefaultRetry, re-reading the CloudProfile on each attempt before applying
mutations and calling Update. Return the retry result while preserving the
existing invalid-error handling and successful nil result.

33-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider an explicit registry type instead of hostname substring matching.

getRegistryProvider selects the Keppel client when the lowercased registry host contains keppel. A Keppel deployment on a host without that substring falls through to errors.New("no registry provider found for registry"), and garbage collection then fails for a valid configuration.

An explicit registryType field on the OCI source in the API removes the guess. This code moved unchanged during the file split, so treat it as a follow-up rather than a blocker for this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/garbage_collection.go` around lines 33 - 41, Update
getRegistryProvider to select the provider from an explicit registryType field
on the OCI source rather than matching “keppel” in the hostname. Propagate the
registry type through the caller and return KeppelClient when the configured
type is Keppel, while preserving validation for empty or unsupported types.

88-101: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚖️ Poor tradeoff

The referenced-version snapshot and the CloudProfile update are not atomic.

getReferencedVersions lists Shoots, then deleteVersions updates the CloudProfile. A Shoot created between the two steps can reference a version that this pass removes. The removal does not delete the registry image, so the effect is a Shoot that references a version absent from its CloudProfile. Gardener then fails to reconcile that Shoot.

Two mitigations are available:

  • Add a grace period so that only versions older than MaxAge plus a buffer are eligible, which shrinks the window.
  • Re-list the Shoots immediately before the Update and abort when the referenced set grew.

The current 5-minute requeue does not close the gap, because the next pass makes the same decision.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/garbage_collection.go` around lines 88 - 101, Make the
garbage-collection decision safe against Shoots created after the initial
getReferencedVersions snapshot: before updating the CloudProfile, re-list the
Shoots and compare the newly referenced set with the original, aborting the
deletion/update when it has grown; retain the existing deletion flow only when
no new references are detected. Alternatively, enforce a MaxAge grace buffer
when selecting versions in the versionsToDelete loop.
cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go (2)

145-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the JWT assertions to cover the signature and the claims.

The test only counts three dot-separated segments. It passes even if the signature is invalid, iss is wrong, or exp is missing. GitHub rejects all three cases at runtime, so the test gives little protection for the App authentication path.

💚 Proposed fix to verify the signature and the claims
 	jwt, err := tr.mintJWT()
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
 	}
-	if parts := strings.Split(jwt, "."); len(parts) != 3 {
-		t.Fatalf("expected 3 JWT parts, got %d", len(parts))
-	}
+	parts := strings.Split(jwt, ".")
+	if len(parts) != 3 {
+		t.Fatalf("expected 3 JWT parts, got %d", len(parts))
+	}
+
+	sig, err := base64.RawURLEncoding.DecodeString(parts[2])
+	if err != nil {
+		t.Fatalf("decoding signature: %v", err)
+	}
+	digest := sha256.Sum256([]byte(parts[0] + "." + parts[1]))
+	if err := rsa.VerifyPKCS1v15(&key.PublicKey, crypto.SHA256, digest[:], sig); err != nil {
+		t.Fatalf("signature verification failed: %v", err)
+	}
+
+	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
+	if err != nil {
+		t.Fatalf("decoding payload: %v", err)
+	}
+	var claims struct {
+		Iat int64 `json:"iat"`
+		Exp int64 `json:"exp"`
+		Iss int64 `json:"iss"`
+	}
+	if err := json.Unmarshal(payload, &claims); err != nil {
+		t.Fatalf("decoding claims: %v", err)
+	}
+	if claims.Iss != 42 {
+		t.Errorf("expected iss 42, got %d", claims.Iss)
+	}
+	if claims.Exp <= claims.Iat {
+		t.Errorf("expected exp %d to be after iat %d", claims.Exp, claims.Iat)
+	}

Add "crypto", "crypto/sha256", and "encoding/base64" to the imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go`
around lines 145 - 156, Strengthen TestGithubAppTransport_MintJWT by parsing the
JWT and verifying its signature with the generated key, using SHA-256 and
base64url decoding as needed. Assert that the claims include the expected app ID
in iss and a valid exp value, while preserving the existing error and three-part
checks.

208-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the PKCS8 branches.

parseRSAPrivateKey handles a PRIVATE KEY block and rejects a non-RSA PKCS8 key. Neither branch is tested. Operators commonly convert a GitHub App key to PKCS8, so this path runs in production.

💚 Proposed fix to add PKCS8 cases
 	t.Run("errors on unsupported PEM type", func(t *testing.T) {
 		b := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("x")})
 		if _, err := parseRSAPrivateKey(b); err == nil || !strings.Contains(err.Error(), "unsupported") {
 			t.Fatalf("expected unsupported error, got %v", err)
 		}
 	})
+	t.Run("parses PKCS8 PEM", func(t *testing.T) {
+		key, _ := generateTestKey(t)
+		der, err := x509.MarshalPKCS8PrivateKey(key)
+		if err != nil {
+			t.Fatalf("marshalling PKCS8: %v", err)
+		}
+		b := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der})
+		if _, err := parseRSAPrivateKey(b); err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+	})
+	t.Run("errors on a non-RSA PKCS8 key", func(t *testing.T) {
+		_, priv, err := ed25519.GenerateKey(rand.Reader)
+		if err != nil {
+			t.Fatalf("generating ed25519 key: %v", err)
+		}
+		der, err := x509.MarshalPKCS8PrivateKey(priv)
+		if err != nil {
+			t.Fatalf("marshalling PKCS8: %v", err)
+		}
+		b := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der})
+		if _, err := parseRSAPrivateKey(b); err == nil || !strings.Contains(err.Error(), "not RSA") {
+			t.Fatalf("expected not-RSA error, got %v", err)
+		}
+	})

Add "crypto/ed25519" to the imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go`
around lines 208 - 226, Extend TestParseRSAPrivateKey with PKCS8 coverage:
generate a valid RSA private key encoded in a PRIVATE KEY PEM block and assert
parseRSAPrivateKey succeeds, then generate an ed25519 key, encode it as PKCS8 in
the same PEM type, and assert parsing fails with the expected non-RSA error. Add
the crypto/ed25519 import needed for the unsupported-key case.
controllers/managedcloudprofile_controller_test.go (1)

163-188: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a provider-config variant and set Type.

Two small improvements apply to this builder:

  1. Four fixtures wrap the builder in an immediately invoked function only to attach ProviderConfig: lines 933-945, 1034-1047, 1137-1149, and 1227-1238. A second helper removes that repetition.
  2. The doc comment states "a minimal valid CloudProfileSpec", but Type is never set. CloudProfileSpec.Type carries no +optional marker, so the generated CloudProfile has type: "". Setting a value makes the fixtures represent a CloudProfile that real Gardener admission accepts.
♻️ Proposed refactor
 func baseCloudProfileSpec(machineImages ...gardenerv1beta1.MachineImage) v1alpha1.CloudProfileSpec {
 	amd64 := "amd64"
 	usable := true
 	spec := v1alpha1.CloudProfileSpec{
+		Type: "ironcore-metal",
 		Regions: []gardenerv1beta1.Region{
 			{
 				Name: "foo",
 			},
 		},
// baseCloudProfileSpecWithProviderConfig returns baseCloudProfileSpec with the
// given raw provider configuration attached.
func baseCloudProfileSpecWithProviderConfig(raw []byte, machineImages ...gardenerv1beta1.MachineImage) v1alpha1.CloudProfileSpec {
	spec := baseCloudProfileSpec(machineImages...)
	spec.ProviderConfig = &runtime.RawExtension{Raw: raw}
	return spec
}

Then each fixture simplifies, for example at lines 933-945:

-				CloudProfile: func() v1alpha1.CloudProfileSpec {
-					cp := baseCloudProfileSpec(
-						gardenerv1beta1.MachineImage{
-							Name: "cap-image",
-							Versions: []gardenerv1beta1.MachineImageVersion{
-								{ExpirableVersion: gardenerv1beta1.ExpirableVersion{Version: rawTag}, Architectures: []string{"amd64"}},
-								{ExpirableVersion: gardenerv1beta1.ExpirableVersion{Version: cleanVersion}, Architectures: []string{"amd64"}},
-							},
-						},
-					)
-					cp.ProviderConfig = &runtime.RawExtension{Raw: raw}
-					return cp
-				}(),
+				CloudProfile: baseCloudProfileSpecWithProviderConfig(raw,
+					gardenerv1beta1.MachineImage{
+						Name: "cap-image",
+						Versions: []gardenerv1beta1.MachineImageVersion{
+							{ExpirableVersion: gardenerv1beta1.ExpirableVersion{Version: rawTag}, Architectures: []string{"amd64"}},
+							{ExpirableVersion: gardenerv1beta1.ExpirableVersion{Version: cleanVersion}, Architectures: []string{"amd64"}},
+						},
+					},
+				),

Setting Type may require updating the assertion at line 273, which compares the full spec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/managedcloudprofile_controller_test.go` around lines 163 - 188,
Update baseCloudProfileSpec to set CloudProfileSpec.Type to a valid provider
type so generated fixtures satisfy Gardener admission, and adjust the full-spec
assertion near the existing base builder tests. Add a
baseCloudProfileSpecWithProviderConfig helper that accepts raw provider
configuration, delegates to baseCloudProfileSpec, and attaches it via
ProviderConfig; replace the four immediately invoked fixture builders with this
helper.
cloudprofilesync/kubernetessync/source/landscape/landscape_source.go (1)

401-422: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a maintained JWT library instead of hand-rolling RS256 signing.

github.com/golang-jwt/jwt/v5 supports SignMethodRS256 for GitHub App tokens and avoids manual base64 encoding, SHA-256 signing, and mustJSON failures unless jwt.ParseRSAPrivateKeyFromPEM already rejects the private key. Use the current implementation if keeping dependencies off this path is a hard preference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source.go` around
lines 401 - 422, Replace the hand-rolled JWT construction in
githubAppTransport.mintJWT with github.com/golang-jwt/jwt/v5, using
SigningMethodRS256 and claims for iat, exp, and iss. Sign the token with t.key
and preserve the existing error-wrapping and token validity timings; remove the
manual header, payload, hashing, and base64-signing logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cloudprofilesync/kubernetessync/kubernetes_image_updater.go`:
- Around line 40-44: The expiration cutoff in kubernetes_image_updater.go lines
40-44 must use now plus ku.ExpirationThreshold so versions expiring within the
positive threshold are removed; add tests covering that case and negative
thresholds. In api/v1alpha1/managedcloudprofile.go lines 116-120, add
Kubebuilder validation rejecting durations below 0s. Regenerate
crd/cloudprofilesync.cobaltcore.dev_managedcloudprofiles.yaml lines 608-612 so
its schema enforces duration(self) >= duration('0s').

In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go`:
- Around line 230-306: Extract the version-intersection loop from FetchVersions
into a pure intersectVersions helper and have FetchVersions call it; update the
test to cover that helper directly with ExpirableVersion values, removing the
unused githubSrv and abandoned comments. Split the ref query assertion into a
focused TestFetchGithubFileAppendsRef test around fetchClassification,
preserving the expected ref=v1.2.3 behavior.

In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source.go`:
- Around line 299-305: Limit response-body reads to 2048 bytes in both
fetchGithubFile
(cloudprofilesync/kubernetessync/source/landscape/landscape_source.go:299-305)
and exchangeInstallationToken
(cloudprofilesync/kubernetessync/source/landscape/landscape_source.go:439-445)
by wrapping each body with io.LimitReader before reading or decoding; preserve
JSON decoding on the limited reader in exchangeInstallationToken and include the
bounded content in errors.
- Around line 363-399: Add a mutex field to githubAppTransport and use it in
installationToken to guard the cached token check, token exchange, and
cached/expiresAt writes as one critical section. Ensure concurrent callers
cannot race or perform duplicate exchanges, while leaving JWT creation and
existing error behavior unchanged.
- Around line 126-131: Set a finite request timeout on the http.Client assigned
to githubClient in the LandscapeKubernetesSource constructor, rather than
leaving Timeout at its zero value. Choose the project’s established timeout
configuration or an appropriate bounded duration so FetchVersions cannot block
reconciliation indefinitely.
- Around line 183-191: Update the tag-selection logic around slices.MaxFunc to
first filter out every tag rejected by semver.ParseTolerant, then compute the
maximum using only parseable semantic versions and their semver comparison.
Remove the mixed string-comparison fallback, while preserving the existing
result handling when no valid tags remain.
- Around line 320-327: Update the expirable-version mapping loop to set
Classification to nil when v.Classification is empty, while retaining a pointer
to the value for non-empty classifications. Use k8s.io/utils/ptr to construct
the conditional pointer and preserve the existing Version and ExpirationDate
mappings.

In `@controllers/cloud_profile.go`:
- Around line 114-122: Validate that the provider configuration in the update
request is present before proceeding; when update.Provider.IroncoreMetal is nil,
return an explicit configuration error instead of leaving provider nil. Update
the provider-selection logic around ossync.Provider and preserve the existing
IroncoreProvider construction for valid configurations.
- Around line 75-86: Update reconcileCloudProfile to call
patchStatusAndCondition unconditionally after successful reconciliation, rather
than only when op != controllerutil.OperationResultNone, so unchanged
CloudProfiles recover stale Failed status. Preserve the existing success status
and CloudProfileApplied condition values, and verify patchStatusAndCondition
remains idempotent without updating LastTransitionTime or writing status when
nothing changed.

In `@controllers/garbage_collection.go`:
- Around line 198-204: Refactor reconcileGarbageCollection and
getReferencedVersions so the reconcile fetches the ShootList and CloudProfile
once before iterating mcp.Spec.MachineImageUpdates, then passes those snapshots
into getReferencedVersions for per-image filtering. Remove the duplicate
List/Get operations from getReferencedVersions, preserve its referenced-version
results, and verify the manager’s cached-client configuration intentionally
supports the all-Shoot snapshot.
- Around line 256-262: Extend RegistryClient.GetTags and its implementations to
accept the OCI connection parameters, then update reconcileGarbageCollection and
fetchKeppelTags to use them instead of hardcoding secure transport. Pass the
same oci.Params as updateMachineImages, including the password resolved by
getCredential, so insecure registries and authenticated Keppel requests work
consistently; update fake RegistryClient implementations and tests to match the
new signature.
- Around line 177-190: Pass the Shoot-referenced versions set from
reconcileGarbageCollection into deleteVersions, then update deleteVersions so
its cascade-delete predicate preserves any version present in that set before
removing clean versions with no remaining flavors. Add coverage for a
Shoot-referenced clean version whose provider config entry has no capability
flavors.

In `@controllers/managedcloudprofile_controller_test.go`:
- Around line 33-73: Add a KubernetesVersionSourceFactory field to Reconciler,
mirroring OCISourceFactory, and update updateKubernetesVersions to obtain its
source through that injectable factory instead of calling landscapeSetupSource
directly. Provide fake and configurable test sources, then add coverage for
writing Kubernetes versions, expiration filtering, missing source configuration,
missing GitHub credentials, and PAT/GitHub App credential resolution. Keep the
existing KubernetesVersionUpdateConfig-absent behavior unchanged.

---

Nitpick comments:
In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go`:
- Around line 145-156: Strengthen TestGithubAppTransport_MintJWT by parsing the
JWT and verifying its signature with the generated key, using SHA-256 and
base64url decoding as needed. Assert that the claims include the expected app ID
in iss and a valid exp value, while preserving the existing error and three-part
checks.
- Around line 208-226: Extend TestParseRSAPrivateKey with PKCS8 coverage:
generate a valid RSA private key encoded in a PRIVATE KEY PEM block and assert
parseRSAPrivateKey succeeds, then generate an ed25519 key, encode it as PKCS8 in
the same PEM type, and assert parsing fails with the expected non-RSA error. Add
the crypto/ed25519 import needed for the unsupported-key case.

In `@cloudprofilesync/kubernetessync/source/landscape/landscape_source.go`:
- Around line 401-422: Replace the hand-rolled JWT construction in
githubAppTransport.mintJWT with github.com/golang-jwt/jwt/v5, using
SigningMethodRS256 and claims for iat, exp, and iss. Sign the token with t.key
and preserve the existing error-wrapping and token validity timings; remove the
manual header, payload, hashing, and base64-signing logic.

In `@cloudprofilesync/ossync/source/oci/os_source_test.go`:
- Around line 104-108: Extend the OCI fixture tests around NewOCI to assert that
a source entry containing _usi maps versions[0].SupportInPlaceUpdate to true.
Add a separate valid-feature fixture without _usi and assert the same field is
false, covering both sides of the source-to-updater contract.

In `@controllers/cloud_profile.go`:
- Around line 38-58: The CreateOrPatch mutate closure currently performs remote
calls through updateMachineImages and updateKubernetesVersions. Resolve all
machine-image and Kubernetes-version updates before invoking
controllerutil.CreateOrPatch, then apply those precomputed values inside the
closure while preserving error propagation and existing defaults.

In `@controllers/garbage_collection.go`:
- Around line 192-195: Wrap the entire Get-mutate-Update flow in deleteVersions
with retry.RetryOnConflict using retry.DefaultRetry, re-reading the CloudProfile
on each attempt before applying mutations and calling Update. Return the retry
result while preserving the existing invalid-error handling and successful nil
result.
- Around line 33-41: Update getRegistryProvider to select the provider from an
explicit registryType field on the OCI source rather than matching “keppel” in
the hostname. Propagate the registry type through the caller and return
KeppelClient when the configured type is Keppel, while preserving validation for
empty or unsupported types.
- Around line 88-101: Make the garbage-collection decision safe against Shoots
created after the initial getReferencedVersions snapshot: before updating the
CloudProfile, re-list the Shoots and compare the newly referenced set with the
original, aborting the deletion/update when it has grown; retain the existing
deletion flow only when no new references are detected. Alternatively, enforce a
MaxAge grace buffer when selecting versions in the versionsToDelete loop.

In `@controllers/managedcloudprofile_controller_test.go`:
- Around line 163-188: Update baseCloudProfileSpec to set CloudProfileSpec.Type
to a valid provider type so generated fixtures satisfy Gardener admission, and
adjust the full-spec assertion near the existing base builder tests. Add a
baseCloudProfileSpecWithProviderConfig helper that accepts raw provider
configuration, delegates to baseCloudProfileSpec, and attaches it via
ProviderConfig; replace the four immediately invoked fixture builders with this
helper.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 931d66fb-dadb-465f-8997-fedeaae76b1c

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9bd11 and 4e97dc8.

📒 Files selected for processing (19)
  • api/v1alpha1/managedcloudprofile.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • cloudprofilesync/kubernetessync/kubernetes_image_updater.go
  • cloudprofilesync/kubernetessync/source/landscape/landscape_source.go
  • cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go
  • cloudprofilesync/ossync/os_image_updater.go
  • cloudprofilesync/ossync/os_image_updater_test.go
  • cloudprofilesync/ossync/provider/ironcore/provider.go
  • cloudprofilesync/ossync/provider/ironcore/provider_test.go
  • cloudprofilesync/ossync/source/oci/os_source.go
  • cloudprofilesync/ossync/source/oci/os_source_test.go
  • cloudprofilesync/ossync/source/oci/suite_test.go
  • cloudprofilesync/ossync/suite_test.go
  • controllers/cloud_profile.go
  • controllers/garbage_collection.go
  • controllers/managedcloudprofile_controller.go
  • controllers/managedcloudprofile_controller_test.go
  • crd/cloudprofilesync.cobaltcore.dev_managedcloudprofiles.yaml
  • go.mod

Comment thread cloudprofilesync/kubernetessync/kubernetes_image_updater.go Outdated
Comment thread cloudprofilesync/kubernetessync/source/landscape/landscape_source_test.go Outdated
Comment thread cloudprofilesync/k8ssync/source/landscape/landscape_source.go
Comment thread cloudprofilesync/kubernetessync/source/landscape/landscape_source.go Outdated
Comment thread cloudprofilesync/k8ssync/source/landscape/landscape_source.go
Comment thread controllers/cloud_profile.go
Comment thread controllers/garbage_collection.go
Comment thread controllers/garbage_collection.go Outdated
Comment thread controllers/garbage_collection.go
Comment thread controllers/managedcloudprofile_controller_test.go
Comment thread api/v1alpha1/managedcloudprofile.go
Comment thread api/v1alpha1/managedcloudprofile.go
Comment thread cloudprofilesync/kubernetessync/kubernetes_image_updater.go Outdated
Comment thread cloudprofilesync/kubernetessync/kubernetes_image_updater.go Outdated
Comment thread cloudprofilesync/kubernetessync/kubernetes_image_updater.go Outdated
Comment thread cloudprofilesync/ossync/source/oci/os_source.go Outdated
Comment thread cloudprofilesync/ossync/os_image_updater.go
Comment thread controllers/cloud_profile.go Outdated
Comment thread controllers/cloud_profile.go
Comment thread controllers/cloud_profile.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
cloudprofilesync/k8ssync/source/landscape/landscape_source_test.go (3)

287-296: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename freePort to match its return value.

The function returns a full host:port address, not a port. Callers use it as "http://"+addr. Rename it to freeAddr so the name matches the value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/k8ssync/source/landscape/landscape_source_test.go` around
lines 287 - 296, Rename the helper function freePort to freeAddr and update
every caller to use the new name, preserving its existing host:port return value
and behavior.

167-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a concurrent subtest for the installation-token cache.

This test drives RoundTrip sequentially, so it cannot detect a data race. The source added mu to githubAppTransport to guard cached and expiresAt. A subtest that issues parallel requests through one transport, run under -race, protects that guarantee against future changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/k8ssync/source/landscape/landscape_source_test.go` around
lines 167 - 215, Extend TestGithubAppTransport_TokenCaching with a concurrent
subtest that reuses one githubAppTransport and issues multiple parallel
RoundTrip calls, coordinating completion and failures safely. Run enough
requests to verify the installation-token endpoint is called only once, while
preserving the existing sequential cache assertion and ensuring the test is
race-detector safe.

316-333: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The readiness loop can exit without failing the test.

The outer condition and the inner deadline check duplicate the same timeout. If time.Now().Before(deadline) becomes false at the top of an iteration, the loop exits, startRegistry returns the cleanup function, and no failure is reported. The doc comment on lines 298-300 states that the helper fails the test immediately when the registry is not ready. The tests then hit connection errors in FetchVersions, which hides the real cause. A single 500 ms budget is also short for a loaded CI runner.

♻️ Proposed fix to make the readiness check deterministic
-	deadline := time.Now().Add(500 * time.Millisecond)
-	for time.Now().Before(deadline) {
+	ready := false
+	deadline := time.Now().Add(5 * time.Second)
+	for !ready && time.Now().Before(deadline) {
 		req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+addr, http.NoBody)
 		if err != nil {
 			cancel()
 			t.Fatalf("building readiness request: %v", err)
 		}
 		resp, err := http.DefaultClient.Do(req)
 		if err == nil {
 			resp.Body.Close()
-			break
+			ready = true
+			break
 		}
 		time.Sleep(10 * time.Millisecond)
-		if time.Now().After(deadline) {
-			cancel()
-			t.Fatalf("registry on %s did not become ready within 500ms", addr)
-		}
 	}
+	if !ready {
+		cancel()
+		t.Fatalf("registry on %s did not become ready in time", addr)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/k8ssync/source/landscape/landscape_source_test.go` around
lines 316 - 333, Update the readiness check in startRegistry so exhausting the
startup deadline always calls t.Fatalf instead of allowing the loop to return
successfully. Use one timeout-controlled retry flow, preserve immediate failure
for request-construction errors, and increase the readiness budget beyond 500 ms
to accommodate loaded CI runners.
cloudprofilesync/ocirepo/ocirepo.go (1)

27-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fail fast when only one credential is set.

New applies authentication only when both Username and Password are non-empty. If an operator configures a username but omits the password, the repository silently falls back to anonymous access. The failure then surfaces later as a 401 or 404 from the registry, which hides the configuration mistake.

♻️ Proposed fix to reject partial credentials
-	if params.Username != "" && params.Password != "" {
+	switch {
+	case params.Username != "" && params.Password != "":
 		repo.Client = &auth.Client{
 			Client: retry.DefaultClient,
 			Cache:  auth.NewCache(),
 			Credential: auth.StaticCredential(params.Registry, auth.Credential{
 				Username: params.Username,
 				Password: params.Password,
 			}),
 		}
-	}
+	case params.Username != "" || params.Password != "":
+		return nil, errors.New("both username and password must be set for registry authentication")
+	}

Add "errors" to the imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/ocirepo/ocirepo.go` around lines 27 - 36, Update New to
validate Username and Password together: return an errors.New configuration
error when exactly one credential is provided, while preserving authenticated
setup when both are present and anonymous access when both are empty. Add the
errors import and ensure the validation occurs before constructing repo.Client.
cloudprofilesync/k8ssync/k8s_image_updater_test.go (3)

27-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale "image" naming after the rename to KubernetesVersionUpdater. The updater writes spec.kubernetes.versions and handles no images. The rename reached the type and the constructor, but the filenames and the test function name still say "image".

  • cloudprofilesync/k8ssync/k8s_image_updater_test.go#L27-L27: rename TestKubernetesImageUpdater_Update to TestKubernetesVersionUpdater_Update, and rename the file to kubernetes_version_updater_test.go.
  • cloudprofilesync/k8ssync/k8s_image_updater.go#L20-L26: rename the file to kubernetes_version_updater.go so it matches the KubernetesVersionUpdater type it declares.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/k8ssync/k8s_image_updater_test.go` at line 27, Rename
cloudprofilesync/k8ssync/k8s_image_updater_test.go to
kubernetes_version_updater_test.go and update TestKubernetesImageUpdater_Update
to TestKubernetesVersionUpdater_Update. Also rename
cloudprofilesync/k8ssync/k8s_image_updater.go to kubernetes_version_updater.go;
no implementation changes are needed.

59-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This subtest duplicates "refuses to wipe CloudProfile" and does not verify dropping.

The source returns one expired version, so Update returns an error and never assigns. The assertions are the same as the subtest at lines 125-141. The name promises drop behavior, but the drop path is only observable in the "mixed" subtest at lines 90-114. Add a surviving version so the subtest verifies the drop.

♻️ Proposed fix to make the subtest verify dropping
 	t.Run("drops version expired beyond threshold", func(t *testing.T) {
 		src := &fakeSource{versions: []gardenerv1beta1.ExpirableVersion{
 			{Version: "1.29.0", ExpirationDate: expiry(now.Add(-60 * 24 * time.Hour))},
+			{Version: "1.31.0"},
 		}}
 		ku := NewKubernetesVersionUpdater(src, 30*24*time.Hour)
 		var spec gardenerv1beta1.CloudProfileSpec
-		spec.Kubernetes.Versions = []gardenerv1beta1.ExpirableVersion{{Version: "existing"}}
-		err := ku.Update(context.Background(), &spec)
-		if err == nil {
-			t.Fatal("expected error when all versions filtered, got nil")
-		}
-		// CloudProfile must not have been modified.
-		if len(spec.Kubernetes.Versions) != 1 || spec.Kubernetes.Versions[0].Version != "existing" {
-			t.Errorf("spec was modified despite error: %v", spec.Kubernetes.Versions)
-		}
+		if err := ku.Update(context.Background(), &spec); err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if len(spec.Kubernetes.Versions) != 1 || spec.Kubernetes.Versions[0].Version != "1.31.0" {
+			t.Errorf("expected only 1.31.0 to survive, got %v", spec.Kubernetes.Versions)
+		}
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/k8ssync/k8s_image_updater_test.go` around lines 59 - 74,
Update the “drops version expired beyond threshold” subtest to include both an
expired and a non-expired source version, so Update succeeds while filtering out
only the expired entry. Assert that the resulting spec contains the surviving
version and excludes the expired one, rather than asserting an error and
unchanged “existing” data.

116-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a subtest for a source that returns an empty version list.

fakeSource with versions: nil and no error exercises a distinct branch. Update then returns the "after expiration filtering" error even though no filtering occurred. A test pins the current contract and shows whether the message needs to distinguish the two causes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloudprofilesync/k8ssync/k8s_image_updater_test.go` around lines 116 - 123,
Extend the Update tests with a subtest for an empty successful source response
using fakeSource configured with versions: nil and no error. Call ku.Update with
an empty CloudProfileSpec, assert it returns an error, and verify the error
matches the current “after expiration filtering” contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cloudprofilesync/k8ssync/source/landscape/landscape_source.go`:
- Around line 196-203: Update the tag parsing loop in the semver selection flow
to accept only complete semantic-version tags, rejecting partial numeric values
such as “2024” and “2024.11” even when semver.ParseTolerant returns no error.
Ensure rejected tags are excluded from parseable so the existing “no semver tags
found” error remains returned when no full semver tags exist, while valid
release tags continue to reach slices.MaxFunc and fetchComponentDescriptor.

In `@cloudprofilesync/ocirepo/ocirepo.go`:
- Around line 21-40: Update New so both authenticated and anonymous repository
requests use an explicit HTTP client with a finite Timeout. Configure the client
used by auth.Client instead of relying on retry.DefaultClient, and assign the
same timeout-bounded client to repo.Client when credentials are absent; preserve
the existing authentication and repository setup.

---

Nitpick comments:
In `@cloudprofilesync/k8ssync/k8s_image_updater_test.go`:
- Line 27: Rename cloudprofilesync/k8ssync/k8s_image_updater_test.go to
kubernetes_version_updater_test.go and update TestKubernetesImageUpdater_Update
to TestKubernetesVersionUpdater_Update. Also rename
cloudprofilesync/k8ssync/k8s_image_updater.go to kubernetes_version_updater.go;
no implementation changes are needed.
- Around line 59-74: Update the “drops version expired beyond threshold” subtest
to include both an expired and a non-expired source version, so Update succeeds
while filtering out only the expired entry. Assert that the resulting spec
contains the surviving version and excludes the expired one, rather than
asserting an error and unchanged “existing” data.
- Around line 116-123: Extend the Update tests with a subtest for an empty
successful source response using fakeSource configured with versions: nil and no
error. Call ku.Update with an empty CloudProfileSpec, assert it returns an
error, and verify the error matches the current “after expiration filtering”
contract.

In `@cloudprofilesync/k8ssync/source/landscape/landscape_source_test.go`:
- Around line 287-296: Rename the helper function freePort to freeAddr and
update every caller to use the new name, preserving its existing host:port
return value and behavior.
- Around line 167-215: Extend TestGithubAppTransport_TokenCaching with a
concurrent subtest that reuses one githubAppTransport and issues multiple
parallel RoundTrip calls, coordinating completion and failures safely. Run
enough requests to verify the installation-token endpoint is called only once,
while preserving the existing sequential cache assertion and ensuring the test
is race-detector safe.
- Around line 316-333: Update the readiness check in startRegistry so exhausting
the startup deadline always calls t.Fatalf instead of allowing the loop to
return successfully. Use one timeout-controlled retry flow, preserve immediate
failure for request-construction errors, and increase the readiness budget
beyond 500 ms to accommodate loaded CI runners.

In `@cloudprofilesync/ocirepo/ocirepo.go`:
- Around line 27-36: Update New to validate Username and Password together:
return an errors.New configuration error when exactly one credential is
provided, while preserving authenticated setup when both are present and
anonymous access when both are empty. Add the errors import and ensure the
validation occurs before constructing repo.Client.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 11251af1-5b3e-45f2-a4f9-f699add9f58b

📥 Commits

Reviewing files that changed from the base of the PR and between 4e97dc8 and 1228aad.

📒 Files selected for processing (13)
  • api/v1alpha1/managedcloudprofile.go
  • cloudprofilesync/k8ssync/k8s_image_updater.go
  • cloudprofilesync/k8ssync/k8s_image_updater_test.go
  • cloudprofilesync/k8ssync/source/landscape/landscape_source.go
  • cloudprofilesync/k8ssync/source/landscape/landscape_source_test.go
  • cloudprofilesync/ocirepo/ocirepo.go
  • cloudprofilesync/ossync/source/oci/os_source.go
  • cloudprofilesync/ossync/source/oci/os_source_test.go
  • controllers/cloud_profile.go
  • controllers/garbage_collection.go
  • controllers/managedcloudprofile_controller.go
  • controllers/managedcloudprofile_controller_test.go
  • crd/cloudprofilesync.cobaltcore.dev_managedcloudprofiles.yaml
🚧 Files skipped from review as they are similar to previous changes (7)
  • api/v1alpha1/managedcloudprofile.go
  • cloudprofilesync/ossync/source/oci/os_source_test.go
  • crd/cloudprofilesync.cobaltcore.dev_managedcloudprofiles.yaml
  • controllers/garbage_collection.go
  • controllers/managedcloudprofile_controller.go
  • controllers/cloud_profile.go
  • controllers/managedcloudprofile_controller_test.go

Comment thread cloudprofilesync/k8ssync/source/landscape/landscape_source.go
Comment thread cloudprofilesync/ocirepo/ocirepo.go
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
Signed-off-by: Aliaksei Dziauho <a.dziauho@sap.com>
@adziauho
adziauho force-pushed the update-kuberentes-versions branch from 1228aad to 52043ec Compare August 11, 2026 11:30
@valeryia-hurynovich
valeryia-hurynovich merged commit b2c33d1 into cobaltcore-dev:master Aug 11, 2026
8 checks passed
@adziauho
adziauho deleted the update-kuberentes-versions branch August 11, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants