Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions api/v1alpha1/managedcloudprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ type ManagedCloudProfileSpec struct {
// GarbageCollection contains configuration for automated garbage collection
// +optional
GarbageCollection *GarbageCollectionConfig `json:"garbageCollection,omitempty"`

// KubernetesVersionUpdateConfig contains the source and provider information to automate Kubernetes version updates.
// +optional
KubernetesVersionUpdateConfig *KubernetesVersionUpdateConfig `json:"kubernetesVersionUpdateConfig,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Dont really like this long "Kubernetes" word. Maybe try k8s or kube . Same for kubernetessync package

}

// Copy the cloud profile spec to override some validation
Expand Down Expand Up @@ -109,13 +113,67 @@ type GarbageCollectionConfig struct {
MaxAge metav1.Duration `json:"maxAge,omitempty"`
}

type KubernetesVersionUpdateConfig struct {
// ExpirationThreshold defines the grace period after a version's expiration date.
// Versions whose expiration date has passed by more than this duration will be
// removed from the CloudProfile.
ExpirationThreshold metav1.Duration `json:"expirationThreshold,omitempty"`

// LandscapeSetup contains the required OCI and GitHub sources for Kubernetes versions.
// +optional
LandscapeSetup *LandscapeSetup `json:"landscapeSetup,omitempty"`
}

// LandscapeSetup configures the combined OCI and GitHub sources for Kubernetes versions.
type LandscapeSetup struct {
// OCI contains configuration for the OCI component-descriptor source.
OCI OCI `json:"oci"`
// Github contains configuration for fetching Kubernetes version classifications from a GitHub repository.
Github KubernetesVersionSourceGithub `json:"github"`
}

// KubernetesVersionSourceGithub configures fetching Kubernetes versions from a
// YAML file in a GitHub repository. The file has a providers[].versions[] shape.
type KubernetesVersionSourceGithub struct {
// RepositoryApiURL is the base URL of the GitHub REST API, e.g.
// "https://api.github.com" or "https://github.mycompany.com/api/v3".
RepositoryApiURL string `json:"repositoryApiUrl"`
// Repository is the owner/repo path, e.g. "my-org/landscape-setup".
Repository string `json:"repository"`
// FilePath is the path to the versions file within the repository,
// e.g. "kubernetes/versions.yaml".
FilePath string `json:"filePath"`
// Provider is the provider whose Kubernetes versions are read from the file.
Provider string `json:"provider"`
// PersonalAccessTokenSecret is a reference to a secret containing a GitHub
// personal access token. Mutually exclusive with GithubApp.
// +optional
PersonalAccessTokenSecret *SecretReference `json:"personalAccessTokenSecret,omitempty"`
// GithubApp configures authentication via a GitHub App installation.
// Mutually exclusive with PersonalAccessTokenSecret.
// +optional
GithubApp *GithubAppAuth `json:"githubApp,omitempty"`
}

// GithubAppAuth holds the credentials needed to authenticate as a GitHub App
// installation.
type GithubAppAuth struct {
// AppID is the numeric GitHub App ID.
AppID int64 `json:"appID"`
// InstallationID is the numeric installation ID for the target repository.
InstallationID int64 `json:"installationID"`
// PrivateKeySecret is a reference to a secret containing the RSA private key
// (PEM-encoded) used to sign JWTs.
PrivateKeySecret SecretReference `json:"privateKeySecret"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since in the end of this large chain of structs (Config -> Landscape -> Source -> App) we end up in the k8s Secret - maybe it will be good to put more info in the secret and not pollute the ManagedCloudProfile spec ? I'd maybe put the whole KubernetesVersionSourceGithub here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not a fun of using secrets for non secret information. If we use secrets we will lose readability and strong consistency

}

type MachineImageUpdateSource struct {
// OCI contains configuration for an OCI source.
// +optional
OCI *MachineImageUpdateSourceOCI `json:"oci,omitempty"`
OCI *OCI `json:"oci,omitempty"`
}

type MachineImageUpdateSourceOCI struct {
type OCI struct {
// Registry contains the hostname and port of the OCI registry
Registry string `json:"registry"`
// Repository contains the monitored repository
Expand Down
118 changes: 101 additions & 17 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions cloudprofilesync/k8ssync/k8s_image_updater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0
package k8ssync

import (
"context"
"errors"
"fmt"
"time"

gardenerv1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
)

// KubernetesVersionSource is the single interface for sources that return
// Kubernetes versions ready to assign to a CloudProfile.
type KubernetesVersionSource interface {
FetchVersions(ctx context.Context) ([]gardenerv1beta1.ExpirableVersion, error)
}

// KubernetesVersionUpdater writes Kubernetes versions to a CloudProfileSpec,
// dropping any version whose expiration date has already passed the configured
// threshold.
type KubernetesVersionUpdater struct {
Source KubernetesVersionSource
ExpirationThreshold time.Duration
}

func NewKubernetesVersionUpdater(source KubernetesVersionSource, expirationThreshold time.Duration) *KubernetesVersionUpdater {
return &KubernetesVersionUpdater{
Source: source,
ExpirationThreshold: expirationThreshold,
}
}

func (ku *KubernetesVersionUpdater) Update(ctx context.Context, cpSpec *gardenerv1beta1.CloudProfileSpec) error {
versions, err := ku.Source.FetchVersions(ctx)
if err != nil {
return fmt.Errorf("fetching kubernetes versions: %w", err)
}

cutoff := time.Now().Add(-ku.ExpirationThreshold)
filteredVersions := make([]gardenerv1beta1.ExpirableVersion, 0, len(versions))
for _, v := range versions {
if v.ExpirationDate != nil && v.ExpirationDate.Time.Before(cutoff) { //nolint:staticcheck
continue
}
filteredVersions = append(filteredVersions, v)
}

if len(filteredVersions) == 0 {
return errors.New("source returned no kubernetes versions after expiration filtering, refusing to wipe CloudProfile")
}
cpSpec.Kubernetes.Versions = filteredVersions
return nil
}
Loading