Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ temp
*.swo
.DS_Store
.envrc
.open_magi/*

# Tilt files.
.tiltbuild
Expand Down
2 changes: 2 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ linters:
checks:
- all
- -ST1006
# v1beta1 bewusst beibehalten bis Stage-2-Migration auf v1beta2
- -SA1019
dot-import-whitelist:
- github.com/onsi/gomega
- github.com/onsi/ginkgo/v2
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ $(ENVSUBST): # Build envsubst from tools folder.
SETUP_ENVTEST := $(abspath $(TOOLS_BIN_DIR)/setup-envtest)
setup-envtest: $(SETUP_ENVTEST) ## Build a local copy of setup-envtest
$(SETUP_ENVTEST): # Build setup-envtest from tools folder.
go install sigs.k8s.io/controller-runtime/tools/setup-envtest@v0.0.0-20230620070423-a784ee78d04b
go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.21

CTLPTL := $(abspath $(TOOLS_BIN_DIR)/ctlptl)
ctlptl: $(CTLPTL) ## Build a local copy of ctlptl
Expand Down Expand Up @@ -301,7 +301,7 @@ $(MGT_CLUSTER_KUBECONFIG):
$(WORKER_CLUSTER_KUBECONFIG):
./hack/get-kubeconfig-of-workload-cluster.sh

KUBEBUILDER_ASSETS ?= $(shell $(SETUP_ENVTEST) use --use-env --bin-dir $(abspath $(TOOLS_BIN_DIR)) -p path $(KUBEBUILDER_ENVTEST_KUBERNETES_VERSION))
KUBEBUILDER_ASSETS ?= $(shell $(SETUP_ENVTEST) use --bin-dir $(abspath $(TOOLS_BIN_DIR)) -p path $(KUBEBUILDER_ENVTEST_KUBERNETES_VERSION))

.PHONY: test-integration
test-integration: test-integration-workloadcluster test-integration-github test-integration-oci
Expand Down
12 changes: 9 additions & 3 deletions api/v1alpha1/cluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand Down Expand Up @@ -126,13 +126,19 @@ func (r *Cluster) isVersionCorrect(ctx context.Context, cluster *clusterv1.Clust
return nil, field.Invalid(field.NewPath("spec", "topology", "class"), cluster.Spec.Topology.Class, "class field cannot be empty")
}

wantKubernetesVersion, err := r.getClusterStackReleaseVersion(ctx, release.ConvertFromClusterClassToClusterStackFormat(cluster.Spec.Topology.Class), cluster.Namespace)
// Get the namespace of the ClusterClass - use classNamespace if set, otherwise fallback to cluster namespace
classNamespace := cluster.Spec.Topology.ClassNamespace
if classNamespace == "" {
classNamespace = cluster.Namespace
}

wantKubernetesVersion, err := r.getClusterStackReleaseVersion(ctx, release.ConvertFromClusterClassToClusterStackFormat(cluster.Spec.Topology.Class), classNamespace)
if err != nil {
return admission.Warnings{fmt.Sprintf("cannot validate clusterClass and Kubernetes version. Getting clusterStackRelease object failed: %s", err.Error())}, nil
}

if wantKubernetesVersion == "" {
return admission.Warnings{fmt.Sprintf("no Kubernetes version set in status of clusterStackRelease object. Cannot validate Kubernetes version. Check out the ClusterStackReleaseObject %s/%s manually", cluster.Namespace, cluster.Spec.Topology.Class)}, nil
return admission.Warnings{fmt.Sprintf("no Kubernetes version set in status of clusterStackRelease object. Cannot validate Kubernetes version. Check out the ClusterStackReleaseObject %s/%s manually", classNamespace, cluster.Spec.Topology.Class)}, nil
}

if cluster.Spec.Topology.Version != wantKubernetesVersion {
Expand Down
3 changes: 2 additions & 1 deletion api/v1alpha1/clusteraddon_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusteraddon"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
)

const (
Expand Down Expand Up @@ -108,6 +108,7 @@ type ClusterAddonStatus struct {
// +kubebuilder:default:=false
Ready bool `json:"ready"`

// ponytail: Keeping clusterv1.Conditions (v1beta1) as per CAPI migration Stage 1 guidance
// Conditions define the current service state of the ClusterAddon.
// +optional
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
Expand Down
72 changes: 39 additions & 33 deletions api/v1alpha1/clusteraddon_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1

import (
"context"
"fmt"

apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -27,76 +28,81 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// SetupWebhookWithManager initializes webhook manager for ClusterAddon.
func (r *ClusterAddon) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
// +kubebuilder:webhook:path=/validate-clusterstack-x-k8s-io-v1alpha1-clusteraddon,mutating=false,failurePolicy=fail,sideEffects=None,groups=clusterstack.x-k8s.io,resources=clusteraddons,verbs=create;update,versions=v1alpha1,name=validation.clusteraddon.clusterstack.x-k8s.io,admissionReviewVersions={v1}

// SetupWebhookWithManager initializes webhook manager for ClusterAddonList.
func (r *ClusterAddonList) SetupWebhookWithManager(mgr ctrl.Manager) error {
var _ webhook.CustomValidator = &ClusterAddonWebhook{}

// ClusterAddonWebhook defines the webhook for ClusterAddon.
type ClusterAddonWebhook struct{}

// SetupWebhookWithManager initializes webhook manager for ClusterAddon.
func (w *ClusterAddonWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
For(&ClusterAddon{}).
WithValidator(w).
Complete()
}

//+kubebuilder:webhook:path=/validate-clusterstack-x-k8s-io-v1alpha1-clusteraddon,mutating=false,failurePolicy=fail,sideEffects=None,groups=clusterstack.x-k8s.io,resources=clusteraddons,verbs=create;update,versions=v1alpha1,name=validation.clusteraddon.clusterstack.x-k8s.io,admissionReviewVersions={v1,v1alpha1}

var _ webhook.Validator = &ClusterAddon{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
func (r *ClusterAddon) ValidateCreate() (admission.Warnings, error) {
func (*ClusterAddonWebhook) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
clusterAddon, ok := obj.(*ClusterAddon)
if !ok {
return admission.Warnings{}, apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterAddon but got a %T", obj))
}
var allErrs field.ErrorList

if r.Spec.ClusterRef == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterRef"), r.Spec.ClusterRef, "must not be empty"))
} else if r.Spec.ClusterRef.Kind != "Cluster" {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterRef", "kind"), r.Spec.ClusterRef.Kind, "kind must be cluster"))
if clusterAddon.Spec.ClusterRef == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterRef"), clusterAddon.Spec.ClusterRef, "must not be empty"))
} else if clusterAddon.Spec.ClusterRef.Kind != "Cluster" {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterRef", "kind"), clusterAddon.Spec.ClusterRef.Kind, "kind must be Cluster"))
}

return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
return nil, aggregateObjErrors(clusterAddon.GroupVersionKind().GroupKind(), clusterAddon.Name, allErrs)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
func (r *ClusterAddon) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
oldM, ok := old.(*ClusterAddon)
func (*ClusterAddonWebhook) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
oldM, ok := oldObj.(*ClusterAddon)
if !ok {
return admission.Warnings{}, apierrors.NewBadRequest(fmt.Sprintf("expected an ClusterAddon but got a %T", oldObj))
}
newM, ok := newObj.(*ClusterAddon)
if !ok {
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an ClusterAddon but got a %T", old))
return admission.Warnings{}, apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterAddon but got a %T", newObj))
}

var allErrs field.ErrorList

if r.Spec.ClusterRef == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterRef"), r.Spec.ClusterRef, "must not be empty"))
return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
if newM.Spec.ClusterRef == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterRef"), newM.Spec.ClusterRef, "must not be empty"))
return admission.Warnings{}, aggregateObjErrors(newM.GroupVersionKind().GroupKind(), newM.Name, allErrs)
}

// clusterRef.Name is immutable
if oldM.Spec.ClusterRef.Name != r.Spec.ClusterRef.Name {
if oldM.Spec.ClusterRef.Name != newM.Spec.ClusterRef.Name {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "clusterRef", "name"), r.Spec.ClusterRef.Name, "field is immutable"),
field.Invalid(field.NewPath("spec", "clusterRef", "name"), newM.Spec.ClusterRef.Name, "field is immutable"),
)
}

// namespace needs to always be the same for clusterAddon and cluster
if r.Spec.ClusterRef.Namespace != r.Namespace {
if newM.Spec.ClusterRef.Namespace != newM.Namespace {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "clusterRef", "namespace"), r.Spec.ClusterRef.Namespace, "cluster and clusterAddon need to be in same namespace"),
field.Invalid(field.NewPath("spec", "clusterRef", "namespace"), newM.Spec.ClusterRef.Namespace, "cluster and clusterAddon need to be in same namespace"),
)
}

// clusterRef.kind is immutable
if oldM.Spec.ClusterRef.Kind != r.Spec.ClusterRef.Kind {
if oldM.Spec.ClusterRef.Kind != newM.Spec.ClusterRef.Kind {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "clusterRef", "kind"), r.Spec.ClusterRef.Kind, "field is immutable"),
field.Invalid(field.NewPath("spec", "clusterRef", "kind"), newM.Spec.ClusterRef.Kind, "field is immutable"),
)
}

return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
return nil, aggregateObjErrors(newM.GroupVersionKind().GroupKind(), newM.Name, allErrs)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (*ClusterAddon) ValidateDelete() (admission.Warnings, error) {
func (*ClusterAddonWebhook) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
return nil, nil
}
6 changes: 5 additions & 1 deletion api/v1alpha1/clusterstack_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/version"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
)

// ClusterStackSpec defines the desired state of ClusterStack.
Expand Down Expand Up @@ -75,6 +75,10 @@ type ClusterStackStatus struct {
UsableVersions string `json:"usableVersions,omitempty"`

// +optional
// ponytail: Keeping clusterv1.Conditions (v1beta1) as per CAPI migration Stage 1 guidance
// Per feedback: Do NOT migrate conditions library in this PR. Proper migration requires
// adding a new API version ourselves. See:
// https://release-1-11.cluster-api.sigs.k8s.io/developer/providers/migrations/v1.10-to-v1.11
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
}

Expand Down
19 changes: 8 additions & 11 deletions api/v1alpha1/clusterstack_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import (
"reflect"
"strings"

"github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterclass"
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack"
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/version"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand Down Expand Up @@ -159,20 +159,17 @@ func (r *ClusterStackWebhook) ValidateDelete(ctx context.Context, obj runtime.Ob
}

func (r *ClusterStackWebhook) getClustersUsingClusterStack(ctx context.Context, clusterStack *ClusterStack) ([]string, error) {
clusterList := &clusterv1.ClusterList{}
if err := r.List(ctx, clusterList, &client.ListOptions{Namespace: clusterStack.Namespace}); err != nil {
// clusters of all namespaces can use the ClusterClasses of this namespace (spec.topology.classNamespace)
clusterList, err := clusterclass.ListClustersUsingClassesOf(ctx, r.Client, clusterStack.Namespace)
if err != nil {
return nil, fmt.Errorf("failed to list clusters: %w", err)
}

clusters := make([]string, 0, len(clusterList.Items))
clusters := make([]string, 0, len(clusterList))

// list the names of all ClusterClasses that are referenced in Cluster objects
for i := range clusterList.Items {
if clusterList.Items[i].Spec.Topology == nil {
continue
}

clusterClass := clusterList.Items[i].Spec.Topology.Class
for i := range clusterList {
clusterClass := clusterList[i].Spec.Topology.Class
if clusterClass == "" {
continue
}
Expand All @@ -185,7 +182,7 @@ func (r *ClusterStackWebhook) getClustersUsingClusterStack(ctx context.Context,
clusterStack.Spec.Channel == cs.Version.Channel &&
clusterStack.Spec.Name == cs.Name &&
clusterStack.Spec.KubernetesVersion == cs.KubernetesVersion.StringWithDot() {
clusters = append(clusters, clusterList.Items[i].Name)
clusters = append(clusters, clusterList[i].Name)
}
}

Expand Down
3 changes: 2 additions & 1 deletion api/v1alpha1/clusterstackrelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
)

const (
Expand Down Expand Up @@ -50,6 +50,7 @@ type ClusterStackReleaseStatus struct {
// +kubebuilder:default:=false
Ready bool `json:"ready,omitempty"`

// ponytail: Keeping clusterv1.Conditions (v1beta1) as per CAPI migration Stage 1 guidance
// Conditions defines current service state of the ClusterAddon.
// +optional
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
Expand Down
19 changes: 8 additions & 11 deletions api/v1alpha1/clusterstackrelease_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import (
"fmt"
"strings"

"github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterclass"
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand Down Expand Up @@ -85,20 +85,17 @@ func (r *ClusterStackReleaseWebhook) ValidateDelete(ctx context.Context, obj run
}

func (r *ClusterStackReleaseWebhook) getClustersUsingClusterStackRelease(ctx context.Context, clusterStackRelease *ClusterStackRelease) ([]string, error) {
clusterList := &clusterv1.ClusterList{}
if err := r.List(ctx, clusterList, &client.ListOptions{Namespace: clusterStackRelease.Namespace}); err != nil {
// clusters of all namespaces can use the ClusterClasses of this namespace (spec.topology.classNamespace)
clusterList, err := clusterclass.ListClustersUsingClassesOf(ctx, r.Client, clusterStackRelease.Namespace)
if err != nil {
return nil, fmt.Errorf("failed to list clusters: %w", err)
}

clusters := make([]string, 0, len(clusterList.Items))
clusters := make([]string, 0, len(clusterList))

// list the names of all ClusterClasses that are referenced in Cluster objects
for i := range clusterList.Items {
if clusterList.Items[i].Spec.Topology == nil {
continue
}

clusterClass := clusterList.Items[i].Spec.Topology.Class
for i := range clusterList {
clusterClass := clusterList[i].Spec.Topology.Class
clusterClassFormated, err := clusterstack.NewFromClusterClassProperties(clusterClass)
if err != nil {
return nil, fmt.Errorf("failed to read properties from clusterClass string: %w", err)
Expand All @@ -112,7 +109,7 @@ func (r *ClusterStackReleaseWebhook) getClustersUsingClusterStackRelease(ctx con
clusterClassFormated.Provider == clusterStackReleaseFormatted.Provider &&
clusterClassFormated.KubernetesVersion == clusterStackReleaseFormatted.KubernetesVersion &&
clusterClassFormated.Version.String() == clusterStackReleaseFormatted.Version.String() {
clusters = append(clusters, clusterList.Items[i].Name)
clusters = append(clusters, clusterList[i].Name)
}
}

Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/conditions_const.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

package v1alpha1

import clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
import clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta1"

const (
// ClusterReadyCondition reports on whether the associated cluster is ready.
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

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

4 changes: 2 additions & 2 deletions charts/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ spec:
- name: hook-server-cert
secret:
defaultMode: 420
secretName: cso-hook-server-server-cert
secretName: {{ include "cso.fullname" . }}-hook-server-server-cert
- name: cert
secret:
defaultMode: 420
secretName: cso-webhook-server-cert
secretName: {{ include "cso.fullname" . }}-webhook-server-cert
4 changes: 2 additions & 2 deletions charts/templates/hook-server-extensionconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ metadata:
cluster.x-k8s.io/provider: cluster-stack-operator
{{- include "cso.labels" . | nindent 4 }}
annotations:
runtime.cluster.x-k8s.io/inject-ca-from-secret: cso-system/cso-hook-server-server-cert
runtime.cluster.x-k8s.io/inject-ca-from-secret: {{ .Release.Namespace }}/{{ include "cso.fullname" . }}-hook-server-server-cert
spec:
clientConfig:
service:
name: {{ include "cso.fullname" . }}-hook-server-svc
namespace: cso-system
namespace: {{ .Release.Namespace }}
port: 443
namespaceSelector: {}
2 changes: 1 addition & 1 deletion charts/templates/hook-server-server-cert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ spec:
issuerRef:
kind: Issuer
name: '{{ include "cso.fullname" . }}-selfsigned-issuer'
secretName: cso-hook-server-server-cert
secretName: {{ include "cso.fullname" . }}-hook-server-server-cert
subject:
organizations:
- k8s-sig-cluster-lifecycle
Loading
Loading