From c505afa1952fdd99cc7ca0a966c23e897f6276de Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 16 Jul 2024 07:38:14 +0000 Subject: [PATCH 1/2] Support for Any Types in ExtraParams --- api/v1beta1/params.go | 49 +++++++++++++++++++ api/v1beta1/tenant_types.go | 3 +- api/v1beta1/zz_generated.deepcopy.go | 15 ++++-- charts/cattage/crds/tenant.yaml | 3 +- .../crd/bases/cattage.cybozu.io_tenants.yaml | 3 +- config/manager/configmap.yaml | 4 ++ config/samples/tenant.yaml | 4 +- controllers/tenant_controller.go | 12 ++--- controllers/tenant_controller_test.go | 20 ++++++-- controllers/testdata/appprojecttemplate.yaml | 4 ++ docs/crd_tenant.md | 2 +- 11 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 api/v1beta1/params.go diff --git a/api/v1beta1/params.go b/api/v1beta1/params.go new file mode 100644 index 0000000..25df872 --- /dev/null +++ b/api/v1beta1/params.go @@ -0,0 +1,49 @@ +package v1beta1 + +import ( + "encoding/json" +) + +// Params represents untyped configuration. +// +kubebuilder:validation:Type=object +type Params struct { + // Data holds the parameter keys and values. + Data map[string]interface{} `json:"-"` +} + +func (c *Params) ToMap() map[string]interface{} { + if c == nil { + return nil + } + return c.Data +} + +// MarshalJSON implements the Marshaler interface. +func (c *Params) MarshalJSON() ([]byte, error) { + return json.Marshal(c.Data) +} + +// UnmarshalJSON implements the Unmarshaler interface. +func (c *Params) UnmarshalJSON(data []byte) error { + var out map[string]interface{} + err := json.Unmarshal(data, &out) + if err != nil { + return err + } + c.Data = out + return nil +} + +// DeepCopyInto is a deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (c *Params) DeepCopyInto(out *Params) { + bytes, err := json.Marshal(c.Data) + if err != nil { + panic(err) + } + var clone map[string]interface{} + err = json.Unmarshal(bytes, &clone) + if err != nil { + panic(err) + } + out.Data = clone +} diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index ab3d83e..a3066c3 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -25,8 +25,9 @@ type TenantSpec struct { ControllerName string `json:"controllerName,omitempty"` // ExtraParams is a map of extra parameters that can be used in the templates. + // +kubebuilder:pruning:PreserveUnknownFields // +optional - ExtraParams map[string]string `json:"extraParams,omitempty"` + ExtraParams *Params `json:"extraParams,omitempty"` } // RootNamespaceSpec defines the desired state of Namespace. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 645dbe5..396c150 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -49,6 +49,16 @@ func (in *DelegateSpec) DeepCopy() *DelegateSpec { return out } +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Params. +func (in *Params) DeepCopy() *Params { + if in == nil { + return nil + } + out := new(Params) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RootNamespaceSpec) DeepCopyInto(out *RootNamespaceSpec) { *out = *in @@ -157,10 +167,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.ExtraParams != nil { in, out := &in.ExtraParams, &out.ExtraParams - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + *out = (*in).DeepCopy() } } diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index f4bd0b4..e12779d 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -78,10 +78,9 @@ spec: type: object type: array extraParams: - additionalProperties: - type: string description: ExtraParams is a map of extra parameters that can be used in the templates. type: object + x-kubernetes-preserve-unknown-fields: true rootNamespaces: description: RootNamespaces are the list of root namespaces that belong to this tenant. items: diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index 8760070..2576399 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -80,11 +80,10 @@ spec: type: object type: array extraParams: - additionalProperties: - type: string description: ExtraParams is a map of extra parameters that can be used in the templates. type: object + x-kubernetes-preserve-unknown-fields: true rootNamespaces: description: RootNamespaces are the list of root namespaces that belong to this tenant. diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index 9155862..ddb8308 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -41,6 +41,10 @@ data: - namespace: {{ . }} server: '*' {{- end }} + {{- range .ExtraParams.Destinations }} + - namespace: {{ . }} + server: '*' + {{- end }} namespaceResourceBlacklist: - group: "" kind: ResourceQuota diff --git a/config/samples/tenant.yaml b/config/samples/tenant.yaml index 397deef..f147995 100644 --- a/config/samples/tenant.yaml +++ b/config/samples/tenant.yaml @@ -24,4 +24,6 @@ spec: roles: - admin extraParams: - GitHubTeam: b-team-gh + Destinations: + - "extra-namespace-x" + - "extra-namespace-y" diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index e0d961e..46eb1ee 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -371,7 +371,7 @@ func (r *TenantReconciler) rolesMap(ctx context.Context, delegates []cattagev1be } result[role] = append(result[role], Role{ Name: delegatedTenant.Name, - ExtraParams: delegatedTenant.Spec.ExtraParams, + ExtraParams: delegatedTenant.Spec.ExtraParams.ToMap(), }) } } @@ -417,11 +417,11 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt err = tpl.Execute(&buf, struct { Name string Roles map[string][]Role - ExtraParams map[string]string + ExtraParams map[string]interface{} }{ Name: tenant.Name, Roles: roles, - ExtraParams: tenant.Spec.ExtraParams, + ExtraParams: tenant.Spec.ExtraParams.ToMap(), }) if err != nil { return err @@ -467,7 +467,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt type Role struct { Name string - ExtraParams map[string]string + ExtraParams map[string]interface{} } func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev1beta1.Tenant) error { @@ -505,13 +505,13 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev Namespaces []string Roles map[string][]Role Repositories []string - ExtraParams map[string]string + ExtraParams map[string]interface{} }{ Name: tenant.Name, Namespaces: namespaces, Roles: roles, Repositories: tenant.Spec.ArgoCD.Repositories, - ExtraParams: tenant.Spec.ExtraParams, + ExtraParams: tenant.Spec.ExtraParams.ToMap(), }) if err != nil { return err diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 93dc761..976c9d1 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -107,9 +107,9 @@ var _ = Describe("Tenant controller", Ordered, func() { "https://github.com/cybozu-go/*", }, }, - ExtraParams: map[string]string{ + ExtraParams: &cattagev1beta1.Params{Data: map[string]interface{}{ "GitHubTeam": "c-team-gh", - }, + }}, }, } err := k8sClient.Create(ctx, cTeam) @@ -143,9 +143,13 @@ var _ = Describe("Tenant controller", Ordered, func() { }, }, }, - ExtraParams: map[string]string{ + ExtraParams: &cattagev1beta1.Params{Data: map[string]interface{}{ "GitHubTeam": "x-team-gh", - }, + "Destinations": []string{ + "extra-namespace-x", + "extra-namespace-y", + }, + }}, }, } err = k8sClient.Create(ctx, xTeam) @@ -209,6 +213,14 @@ var _ = Describe("Tenant controller", Ordered, func() { "namespace": Equal("sub-4"), "server": Equal("*"), }), + MatchAllKeys(Keys{ + "namespace": Equal("extra-namespace-x"), + "server": Equal("*"), + }), + MatchAllKeys(Keys{ + "namespace": Equal("extra-namespace-y"), + "server": Equal("*"), + }), ), "namespaceResourceBlacklist": ConsistOf( MatchAllKeys(Keys{ diff --git a/controllers/testdata/appprojecttemplate.yaml b/controllers/testdata/appprojecttemplate.yaml index 360a7d5..2724b30 100644 --- a/controllers/testdata/appprojecttemplate.yaml +++ b/controllers/testdata/appprojecttemplate.yaml @@ -6,6 +6,10 @@ spec: - namespace: {{ . }} server: '*' {{- end }} + {{- range .ExtraParams.Destinations }} + - namespace: {{ . }} + server: '*' + {{- end }} namespaceResourceBlacklist: - group: "" kind: ResourceQuota diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 1475845..510fcec 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -78,7 +78,7 @@ TenantSpec defines the desired state of Tenant. | argocd | ArgoCD is the settings of Argo CD for this tenant. | [ArgoCDSpec](#argocdspec) | false | | delegates | Delegates is a list of other tenants that are delegated access to this tenant. | [][DelegateSpec](#delegatespec) | false | | controllerName | ControllerName is the name of the application-controller that manages this tenant's applications. If not specified, the default controller is used. | string | false | -| extraParams | ExtraParams is a map of extra parameters that can be used in the templates. | map[string]string | false | +| extraParams | ExtraParams is a map of extra parameters that can be used in the templates. | *Params | false | [Back to Custom Resources](#custom-resources) From d83b5578ab9a2192d576a3bd7f56663931291b75 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 17 Jul 2024 07:50:37 +0000 Subject: [PATCH 2/2] Reflect review comments --- api/v1beta1/params.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/api/v1beta1/params.go b/api/v1beta1/params.go index 25df872..065ab1a 100644 --- a/api/v1beta1/params.go +++ b/api/v1beta1/params.go @@ -5,38 +5,40 @@ import ( ) // Params represents untyped configuration. +// kubebuilder does not support interface{} member directly, so this struct is a workaround. // +kubebuilder:validation:Type=object type Params struct { // Data holds the parameter keys and values. Data map[string]interface{} `json:"-"` } -func (c *Params) ToMap() map[string]interface{} { - if c == nil { +// ToMap converts the Params to map[string]interface{}. If the receiver is nil, it returns nil. +func (p *Params) ToMap() map[string]interface{} { + if p == nil { return nil } - return c.Data + return p.Data } // MarshalJSON implements the Marshaler interface. -func (c *Params) MarshalJSON() ([]byte, error) { - return json.Marshal(c.Data) +func (p *Params) MarshalJSON() ([]byte, error) { + return json.Marshal(p.Data) } // UnmarshalJSON implements the Unmarshaler interface. -func (c *Params) UnmarshalJSON(data []byte) error { +func (p *Params) UnmarshalJSON(data []byte) error { var out map[string]interface{} err := json.Unmarshal(data, &out) if err != nil { return err } - c.Data = out + p.Data = out return nil } -// DeepCopyInto is a deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (c *Params) DeepCopyInto(out *Params) { - bytes, err := json.Marshal(c.Data) +// DeepCopyInto is a deep copy function, copying the receiver, writing into `out`. `p` must be non-nil. +func (p *Params) DeepCopyInto(out *Params) { + bytes, err := json.Marshal(p.Data) if err != nil { panic(err) }