Skip to content
Merged
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
6 changes: 5 additions & 1 deletion api/v1alpha1/pattern_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ type PatternSpec struct {
// Important: Run "make generate" to regenerate code after modifying this file

// +operator-sdk:csv:customresourcedefinitions:type=spec,order=3
ClusterGroupName string `json:"clusterGroupName"`
ClusterGroupName string `json:"clusterGroupName,omitempty"`

// Variant is an alias for ClusterGroupName. Only one of the two may be set.
// +operator-sdk:csv:customresourcedefinitions:type=spec,order=3
Variant string `json:"variant,omitempty"`

// +operator-sdk:csv:customresourcedefinitions:type=spec,order=4
GitConfig GitConfig `json:"gitSpec"`
Expand Down
23 changes: 22 additions & 1 deletion api/v1alpha1/pattern_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type PatternValidator struct {
}

//nolint:lll
// +kubebuilder:webhook:verbs=create;delete,path=/validate-gitops-hybrid-cloud-patterns-io-v1alpha1-pattern,mutating=false,failurePolicy=fail,groups=gitops.hybrid-cloud-patterns.io,resources=patterns,versions=v1alpha1,name=vpattern.gitops.hybrid-cloud-patterns.io,admissionReviewVersions=v1,sideEffects=none
// +kubebuilder:webhook:verbs=create;update;delete,path=/validate-gitops-hybrid-cloud-patterns-io-v1alpha1-pattern,mutating=false,failurePolicy=fail,groups=gitops.hybrid-cloud-patterns.io,resources=patterns,versions=v1alpha1,name=vpattern.gitops.hybrid-cloud-patterns.io,admissionReviewVersions=v1,sideEffects=none

var _ webhook.CustomValidator = &PatternValidator{}

Expand All @@ -61,6 +61,11 @@ func (r *PatternValidator) ValidateCreate(ctx context.Context, obj runtime.Objec
}
patternlog.Info("validate create", "name", p.Name)

if err := validateVariantAlias(p); err != nil {
patternlog.Error(err, "validate create failed", "name", p.Name)
return nil, err
}

var patterns PatternList
if err = r.Client.List(ctx, &patterns); err != nil {
return nil, fmt.Errorf("failed to list Pattern resources: %v", err)
Expand All @@ -79,6 +84,12 @@ func (r *PatternValidator) ValidateUpdate(_ context.Context, _, newObj runtime.O
return nil, err
}
patternlog.Info("validate update", "name", p.Name)

if err := validateVariantAlias(p); err != nil {
patternlog.Error(err, "validate update failed", "name", p.Name)
return nil, err
}

return nil, nil
}

Expand All @@ -105,3 +116,13 @@ func convertToPattern(obj runtime.Object) (*Pattern, error) {
}
return p, nil
}

func validateVariantAlias(p *Pattern) error {
if p.Spec.ClusterGroupName != "" && p.Spec.Variant != "" {
return fmt.Errorf("spec.variant and spec.clusterGroupName are mutually exclusive, set only one")
}
if p.Spec.ClusterGroupName == "" && p.Spec.Variant == "" {
return fmt.Errorf("one of spec.variant or spec.clusterGroupName must be set")
}
return nil
}
119 changes: 119 additions & 0 deletions api/v1alpha1/pattern_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,122 @@ func TestValidateCreate_DeniesSecondPattern(t *testing.T) {
}
}

func TestValidateCreate_AllowsVariantOnly(t *testing.T) {
scheme := runtime.NewScheme()
if err := AddToScheme(scheme); err != nil {
t.Fatalf("failed to add scheme: %v", err)
}

fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
validator := &PatternValidator{Client: fakeClient}

p := &Pattern{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pattern",
Namespace: "default",
},
Spec: PatternSpec{
Variant: "hub",
GitConfig: GitConfig{
TargetRepo: "https://github.com/example/repo",
TargetRevision: "main",
},
},
}

warnings, err := validator.ValidateCreate(context.Background(), p)
if err != nil {
t.Errorf("expected no error when only variant is set, got: %v", err)
}
if warnings != nil {
t.Errorf("expected no warnings, got: %v", warnings)
}
}

func TestValidateCreate_RejectsBothSet(t *testing.T) {
scheme := runtime.NewScheme()
if err := AddToScheme(scheme); err != nil {
t.Fatalf("failed to add scheme: %v", err)
}

fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
validator := &PatternValidator{Client: fakeClient}

p := &Pattern{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pattern",
Namespace: "default",
},
Spec: PatternSpec{
ClusterGroupName: "hub",
Variant: "hub",
GitConfig: GitConfig{
TargetRepo: "https://github.com/example/repo",
TargetRevision: "main",
},
},
}

_, err := validator.ValidateCreate(context.Background(), p)
if err == nil {
t.Error("expected error when both variant and clusterGroupName are set, got nil")
}
}

func TestValidateCreate_RejectsBothEmpty(t *testing.T) {
scheme := runtime.NewScheme()
if err := AddToScheme(scheme); err != nil {
t.Fatalf("failed to add scheme: %v", err)
}

fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
validator := &PatternValidator{Client: fakeClient}

p := &Pattern{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pattern",
Namespace: "default",
},
Spec: PatternSpec{
GitConfig: GitConfig{
TargetRepo: "https://github.com/example/repo",
TargetRevision: "main",
},
},
}

_, err := validator.ValidateCreate(context.Background(), p)
if err == nil {
t.Error("expected error when neither variant nor clusterGroupName is set, got nil")
}
}

func TestValidateUpdate_RejectsBothSet(t *testing.T) {
scheme := runtime.NewScheme()
if err := AddToScheme(scheme); err != nil {
t.Fatalf("failed to add scheme: %v", err)
}

fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
validator := &PatternValidator{Client: fakeClient}

p := &Pattern{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pattern",
Namespace: "default",
},
Spec: PatternSpec{
ClusterGroupName: "hub",
Variant: "factory",
},
}

_, err := validator.ValidateUpdate(context.Background(), p, p)
if err == nil {
t.Error("expected error when both variant and clusterGroupName are set on update, got nil")
}
}

func TestValidateCreate_RejectsNonPatternObject(t *testing.T) {
scheme := runtime.NewScheme()
if err := AddToScheme(scheme); err != nil {
Expand Down Expand Up @@ -131,6 +247,9 @@ func TestValidateUpdate_Allows(t *testing.T) {
Name: "test-pattern",
Namespace: "default",
},
Spec: PatternSpec{
Variant: "hub",
},
}

warnings, err := validator.ValidateUpdate(context.Background(), p, p)
Expand Down
13 changes: 5 additions & 8 deletions bundle/manifests/gitops.hybrid-cloud-patterns.io_patterns.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.4
creationTimestamp: null
name: patterns.gitops.hybrid-cloud-patterns.io
spec:
group: gitops.hybrid-cloud-patterns.io
Expand Down Expand Up @@ -152,8 +152,11 @@ spec:
in order to deploy the pattern. Defaults to https://charts.validatedpatterns.io/
type: string
type: object
variant:
description: Variant is an alias for ClusterGroupName. If both are
set, they must match.
type: string
required:
- clusterGroupName
- gitSpec
type: object
status:
Expand Down Expand Up @@ -247,9 +250,3 @@ spec:
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: null
storedVersions: null
5 changes: 5 additions & 0 deletions bundle/manifests/patterns-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ metadata:
},
"spec": {
"clusterGroupName": "hub",
"variant": "hub",
"gitSpec": {
"targetRepo": "https://github.com/validatedpatterns/multicloud-gitops",
"targetRevision": "main"
Expand Down Expand Up @@ -67,6 +68,9 @@ spec:
path: extraParameters[0].value
- displayName: Cluster Group Name
path: clusterGroupName
- description: Alias for Cluster Group Name
displayName: Variant
path: variant
- displayName: Git Config
path: gitSpec
- displayName: Multi Source Config
Expand Down Expand Up @@ -627,6 +631,7 @@ spec:
- v1alpha1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- patterns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,11 @@ spec:
in order to deploy the pattern. Defaults to https://charts.validatedpatterns.io/
type: string
type: object
variant:
description: Variant is an alias for ClusterGroupName. Only one of
the two may be set.
type: string
required:
- clusterGroupName
- gitSpec
type: object
status:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ spec:
path: extraParameters[0].value
- displayName: Cluster Group Name
path: clusterGroupName
- description: Alias for Cluster Group Name
displayName: Variant
path: variant
- displayName: Git Config
path: gitSpec
- displayName: Multi Source Config
Expand Down
1 change: 1 addition & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ webhooks:
- v1alpha1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- patterns
Expand Down
5 changes: 3 additions & 2 deletions console/src/components/InstallPatternPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ export default function InstallPatternPage() {
kind: string;
metadata: { name: string; namespace: string };
spec: {
clusterGroupName: string;
clusterGroupName?: string;
variant: string;
gitSpec: { targetRepo: string; targetRevision: string };
secretsConfig?: { template: string; values: string };
};
Expand All @@ -316,7 +317,7 @@ export default function InstallPatternPage() {
namespace: PATTERN_OPERATOR_NS,
},
spec: {
clusterGroupName: clusterGroupName,
variant: clusterGroupName,
gitSpec: {
targetRepo,
targetRevision,
Expand Down
1 change: 1 addition & 0 deletions console/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface Pattern {
owners: string[];
org: string;
clustergroupname: string;
variant?: string;
description?: string;
docs_repo_url?: string;
spoke?: unknown;
Expand Down
7 changes: 5 additions & 2 deletions internal/controller/pattern_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,11 @@ func (r *PatternReconciler) applyDefaults(input *api.Pattern) (*api.Pattern, err
multiSourceBool := true
output.Spec.MultiSourceConfig.Enabled = &multiSourceBool
}
if output.Spec.ClusterGroupName == "" {
output.Spec.ClusterGroupName = "default" //nolint:goconst
switch {
case output.Spec.ClusterGroupName == "" && output.Spec.Variant != "":
output.Spec.ClusterGroupName = output.Spec.Variant
case output.Spec.ClusterGroupName != "" && output.Spec.Variant == "":
output.Spec.Variant = output.Spec.ClusterGroupName
}
if output.Spec.MultiSourceConfig.HelmRepoUrl == "" {
output.Spec.MultiSourceConfig.HelmRepoUrl = GiteaHelmRepoUrl
Expand Down
16 changes: 14 additions & 2 deletions internal/controller/pattern_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,24 @@ var _ = Describe("pattern controller - applyDefaults", func() {
Expect(*output.Spec.MultiSourceConfig.Enabled).To(BeTrue())
})

It("should default ClusterGroupName to 'default' when empty", func() {
It("should sync Variant from ClusterGroupName when Variant is empty", func() {
p := buildPatternManifest()
p.Spec.ClusterGroupName = "hub"
p.Spec.Variant = ""
output, err := reconciler.applyDefaults(p)
Expect(err).ToNot(HaveOccurred())
Expect(output.Spec.ClusterGroupName).To(Equal("hub"))
Expect(output.Spec.Variant).To(Equal("hub"))
})

It("should sync ClusterGroupName from Variant when ClusterGroupName is empty", func() {
p := buildPatternManifest()
p.Spec.ClusterGroupName = ""
p.Spec.Variant = "factory"
output, err := reconciler.applyDefaults(p)
Expect(err).ToNot(HaveOccurred())
Expect(output.Spec.ClusterGroupName).To(Equal("default"))
Expect(output.Spec.ClusterGroupName).To(Equal("factory"))
Expect(output.Spec.Variant).To(Equal("factory"))
})

It("should default HelmRepoUrl when empty", func() {
Expand Down