-
Notifications
You must be signed in to change notification settings - Fork 2
feat: automate Kubernetes version lifecycle management in CloudProfiles #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7829a49
b444259
e0c1344
af308d7
4e97dc8
4c884f9
30db32f
1228aad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 the cloud profile spec to override some validation | ||
|
|
@@ -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"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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 | ||
| } |
There was a problem hiding this comment.
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
k8sorkube. Same forkubernetessyncpackage