diff --git a/Justfile b/Justfile index 487412a9..3e34dded 100644 --- a/Justfile +++ b/Justfile @@ -30,11 +30,11 @@ release: helm-publish goreleaser release --clean manifests: - go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 \ + go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0 \ rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases generate: - go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 \ + go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0 \ object:headerFile="hack/boilerplate.go.txt" paths="./..." generate-mock: diff --git a/api/formance.com/v1beta1/settings_types.go b/api/formance.com/v1beta1/settings_types.go index 29999daf..04f52511 100644 --- a/api/formance.com/v1beta1/settings_types.go +++ b/api/formance.com/v1beta1/settings_types.go @@ -17,9 +17,11 @@ limitations under the License. package v1beta1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:ExactlyOneOf=value;valueFrom type SettingsSpec struct { //+optional // Stacks on which the setting is applied. Can contain `*` to indicate a wildcard. @@ -27,7 +29,23 @@ type SettingsSpec struct { // The setting Key. See the documentation of each module or [global settings](#global-settings) to discover them. Key string `json:"key"` // The value. It must have a specific format following the Key. - Value string `json:"value"` + // Either Value or ValueFrom must be set, but not both. + //+optional + Value string `json:"value,omitempty"` + // Source for the value. Can be used to reference a secret or configmap key. + // Either Value or ValueFrom must be set, but not both. + //+optional + ValueFrom *ValueFrom `json:"valueFrom,omitempty"` +} + +// +kubebuilder:validation:ExactlyOneOf=secretKeyRef;configMapKeyRef +type ValueFrom struct { + // Selects a key of a Secret. + //+optional + SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` + // Selects a key of a ConfigMap. + //+optional + ConfigMapKeyRef *corev1.ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` } // Settings represents a configurable piece of the stacks. @@ -91,6 +109,52 @@ type SettingsSpec struct { // // ``` // +// You can also fetch values from secrets or configmaps using `valueFrom`: +// ```yaml +// apiVersion: formance.com/v1beta1 +// kind: Settings +// metadata: +// +// name: postgres-uri-from-secret +// +// spec: +// +// key: postgres.ledger.uri +// stacks: +// - stack0 +// valueFrom: +// secretKeyRef: +// name: postgres-credentials +// key: connection-string +// optional: false +// +// ``` +// +// Or from a configmap: +// ```yaml +// apiVersion: formance.com/v1beta1 +// kind: Settings +// metadata: +// +// name: postgres-uri-from-configmap +// +// spec: +// +// key: postgres.ledger.uri +// stacks: +// - stack0 +// valueFrom: +// configMapKeyRef: +// name: postgres-config +// key: uri +// optional: false +// +// ``` +// +// Note: Secrets and configmaps are resolved from the stack's namespace (which has the same name as the stack) +// and then from the formance-system namespace if not found in the stack namespace. +// Either `value` or `valueFrom` must be set, but not both. +// // Some settings are really global, while some are used by specific module. // // Refer to the documentation of each module and resource to discover available Settings. diff --git a/api/formance.com/v1beta1/zz_generated.deepcopy.go b/api/formance.com/v1beta1/zz_generated.deepcopy.go index 40751643..36c49c47 100644 --- a/api/formance.com/v1beta1/zz_generated.deepcopy.go +++ b/api/formance.com/v1beta1/zz_generated.deepcopy.go @@ -1920,6 +1920,11 @@ func (in *SettingsSpec) DeepCopyInto(out *SettingsSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(ValueFrom) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsSpec. @@ -2199,6 +2204,31 @@ func (in *URI) DeepCopy() *URI { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValueFrom) DeepCopyInto(out *ValueFrom) { + *out = *in + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.ConfigMapKeyRef != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(v1.ConfigMapKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValueFrom. +func (in *ValueFrom) DeepCopy() *ValueFrom { + if in == nil { + return nil + } + out := new(ValueFrom) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Versions) DeepCopyInto(out *Versions) { *out = *in diff --git a/config/crd/bases/formance.com_authclients.yaml b/config/crd/bases/formance.com_authclients.yaml index f2c91a4b..5cd52292 100644 --- a/config/crd/bases/formance.com_authclients.yaml +++ b/config/crd/bases/formance.com_authclients.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: authclients.formance.com spec: group: formance.com @@ -101,7 +101,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret or its key must be defined @@ -120,16 +119,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -169,12 +160,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_auths.yaml b/config/crd/bases/formance.com_auths.yaml index 3cf512fc..24291757 100644 --- a/config/crd/bases/formance.com_auths.yaml +++ b/config/crd/bases/formance.com_auths.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/kind: module name: auths.formance.com @@ -43,13 +43,10 @@ spec: description: |- Auth represent the authentication module of a stack. - It is an OIDC compliant server. - Creating it for a stack automatically add authentication on all supported modules. - The auth service is basically a proxy to another OIDC compliant server. properties: apiVersion: @@ -97,7 +94,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret or its key must be @@ -122,7 +118,6 @@ spec: description: |- Allow to enable scopes usage on authentication. - If not enabled, each service will check the authentication but will not restrict access following scopes. in this case, if authenticated, it is ok. type: boolean @@ -142,7 +137,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret or its key must be defined @@ -168,16 +162,8 @@ spec: type: array conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -217,12 +203,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_benthos.yaml b/config/crd/bases/formance.com_benthos.yaml index 207c3c4d..d3a49804 100644 --- a/config/crd/bases/formance.com_benthos.yaml +++ b/config/crd/bases/formance.com_benthos.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: benthos.formance.com spec: group: formance.com @@ -86,7 +86,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object x-kubernetes-map-type: atomic @@ -160,7 +159,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the ConfigMap or @@ -223,7 +221,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret or its @@ -257,7 +254,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the ConfigMap must be @@ -276,7 +272,6 @@ spec: description: |- Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret must be defined @@ -555,11 +550,11 @@ spec: format: int32 type: integer service: + default: "" description: |- Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - If this is not specified, the default behavior is defined by gRPC. type: string required: @@ -765,11 +760,11 @@ spec: format: int32 type: integer service: + default: "" description: |- Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - If this is not specified, the default behavior is defined by gRPC. type: string required: @@ -916,11 +911,9 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. items: description: ResourceClaim references one entry in PodSpec.ResourceClaims. @@ -1110,7 +1103,6 @@ spec: type indicates which kind of seccomp profile will be applied. Valid options are: - Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied. @@ -1190,11 +1182,11 @@ spec: format: int32 type: integer service: + default: "" description: |- Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - If this is not specified, the default behavior is defined by gRPC. type: string required: @@ -1433,11 +1425,9 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. items: description: ResourceClaim references one entry in PodSpec.ResourceClaims. @@ -1488,16 +1478,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -1537,12 +1519,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_benthosstreams.yaml b/config/crd/bases/formance.com_benthosstreams.yaml index f6ca538f..5504e5b6 100644 --- a/config/crd/bases/formance.com_benthosstreams.yaml +++ b/config/crd/bases/formance.com_benthosstreams.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: benthosstreams.formance.com spec: group: formance.com @@ -58,16 +58,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -107,12 +99,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_brokerconsumers.yaml b/config/crd/bases/formance.com_brokerconsumers.yaml index a2f22aa9..1d8970cd 100644 --- a/config/crd/bases/formance.com_brokerconsumers.yaml +++ b/config/crd/bases/formance.com_brokerconsumers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: brokerconsumers.formance.com spec: group: formance.com @@ -73,16 +73,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -122,12 +114,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_brokers.yaml b/config/crd/bases/formance.com_brokers.yaml index 7fd222c5..2e44002a 100644 --- a/config/crd/bases/formance.com_brokers.yaml +++ b/config/crd/bases/formance.com_brokers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: brokers.formance.com spec: group: formance.com @@ -59,16 +59,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -108,12 +100,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_brokertopics.yaml b/config/crd/bases/formance.com_brokertopics.yaml index 888286f3..0cb66bb7 100644 --- a/config/crd/bases/formance.com_brokertopics.yaml +++ b/config/crd/bases/formance.com_brokertopics.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: brokertopics.formance.com spec: group: formance.com @@ -63,16 +63,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -112,12 +104,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_databases.yaml b/config/crd/bases/formance.com_databases.yaml index 1d99490c..d40a31dc 100644 --- a/config/crd/bases/formance.com_databases.yaml +++ b/config/crd/bases/formance.com_databases.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: databases.formance.com spec: group: formance.com @@ -37,27 +37,22 @@ spec: description: |- Database represent a concrete database on a PostgreSQL server, it is created by modules requiring a database ([Ledger](#ledger) for example). - It uses the settings `postgres..uri` which must have the following uri format: `postgresql://[@]@/` Additionally, the uri can define a query param `secret` indicating a k8s secret, than must be used to retrieve database credentials. - On creation, the reconciler behind the Database object will create the database on the postgresql server using a k8s job. On Deletion, by default, the reconciler will let the database untouched. You can allow the reconciler to drop the database on the server by using the [Settings](#settings) `clear-database` with the value `true`. If you use that setting, the reconciler will use another job to drop the database. Be careful, no backup are performed! - Database resource honors `aws.service-account` setting, so, you can create databases on an AWS server if you need. See [AWS accounts](#aws-account) - Once a database is fully configured, it retains the postgres uri used. If the setting indicating the server uri changed, the Database object will set the field `.status.outOfSync` to true and will not change anything. - Therefore, to switch to a new server, you must change the setting value, then drop the Database object. It will be recreated with correct uri. properties: @@ -99,16 +94,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -148,12 +135,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_gatewayhttpapis.yaml b/config/crd/bases/formance.com_gatewayhttpapis.yaml index 90267331..d6838df6 100644 --- a/config/crd/bases/formance.com_gatewayhttpapis.yaml +++ b/config/crd/bases/formance.com_gatewayhttpapis.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: gatewayhttpapis.formance.com spec: group: formance.com @@ -81,16 +81,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -130,12 +122,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_gateways.yaml b/config/crd/bases/formance.com_gateways.yaml index 37bad1a2..c700be4e 100644 --- a/config/crd/bases/formance.com_gateways.yaml +++ b/config/crd/bases/formance.com_gateways.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/kind: module name: gateways.formance.com @@ -92,7 +92,6 @@ spec: description: |- Indicate the scheme. - Actually, It should be `https` unless you know what you are doing. type: string tls: @@ -121,16 +120,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -170,12 +161,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_ledgers.yaml b/config/crd/bases/formance.com_ledgers.yaml index f54870df..2fb2b8a3 100644 --- a/config/crd/bases/formance.com_ledgers.yaml +++ b/config/crd/bases/formance.com_ledgers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/kind: module name: ledgers.formance.com @@ -39,16 +39,13 @@ spec: description: |- Ledger is the module allowing to install a ledger instance. - The ledger is actually a stateful application on the writer part. So we cannot scale the ledger as we want without prior configuration. - So, the ledger can run in two modes : * single instance: Only one instance will be deployed. We cannot scale in that mode. * single writer / multiple reader: In this mode, we will have a single writer and multiple readers if needed. - Use setting `ledger.deployment-strategy` with either the value : - single : For the single instance mode. - single-writer: For the single writer / multiple reader mode. @@ -139,16 +136,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -188,12 +177,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_orchestrations.yaml b/config/crd/bases/formance.com_orchestrations.yaml index 066bb3e3..63d5eafa 100644 --- a/config/crd/bases/formance.com_orchestrations.yaml +++ b/config/crd/bases/formance.com_orchestrations.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/is-ee: "true" formance.com/kind: module @@ -87,16 +87,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -136,12 +128,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_payments.yaml b/config/crd/bases/formance.com_payments.yaml index 1ddf3239..35ac53a1 100644 --- a/config/crd/bases/formance.com_payments.yaml +++ b/config/crd/bases/formance.com_payments.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/kind: module name: payments.formance.com @@ -88,16 +88,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -137,12 +129,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_reconciliations.yaml b/config/crd/bases/formance.com_reconciliations.yaml index 3be054bd..80f413c8 100644 --- a/config/crd/bases/formance.com_reconciliations.yaml +++ b/config/crd/bases/formance.com_reconciliations.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/is-ee: "true" formance.com/kind: module @@ -87,16 +87,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -136,12 +128,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_resourcereferences.yaml b/config/crd/bases/formance.com_resourcereferences.yaml index 8edb2bc0..ab43513e 100644 --- a/config/crd/bases/formance.com_resourcereferences.yaml +++ b/config/crd/bases/formance.com_resourcereferences.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: resourcereferences.formance.com spec: group: formance.com @@ -27,25 +27,25 @@ spec: schema: openAPIV3Schema: description: "ResourceReference is a special resources used to refer to externally - created resources.\n\n\nIt includes k8s service accounts and secrets.\n\n\nWhy? + created resources.\n\nIt includes k8s service accounts and secrets.\n\nWhy? Because the operator create a namespace by stack, so, a stack does not have - access to secrets and service\naccounts created externally.\n\n\nA ResourceReference + access to secrets and service\naccounts created externally.\n\nA ResourceReference is created by other resource who need to use a specific secret or service account.\nFor example, if you want to use a secret for your database connection (see [Database](#database), you will\ncreate a setting indicating a secret name. You will need to create this secret yourself, and you will put this\nsecret - inside the namespace you want (`default` maybe).\n\n\nThe Database reconciler + inside the namespace you want (`default` maybe).\n\nThe Database reconciler will create a ResourceReference looking like that :\n```\napiVersion: formance.com/v1beta1\nkind: - ResourceReference\nmetadata:\n\n\n\tname: jqkuffjxcezj-qlii-auth-postgres\n\townerReferences:\n\t- + ResourceReference\nmetadata:\n\n\tname: jqkuffjxcezj-qlii-auth-postgres\n\townerReferences:\n\t- apiVersion: formance.com/v1beta1\n\t blockOwnerDeletion: true\n\t controller: - true\n\t kind: Database\n\t name: jqkuffjxcezj-qlii-auth\n\t uid: 2cc4b788-3ffb-4e3d-8a30-07ed3941c8d2\n\n\nspec:\n\n\n\tgvk:\n\t + true\n\t kind: Database\n\t name: jqkuffjxcezj-qlii-auth\n\t uid: 2cc4b788-3ffb-4e3d-8a30-07ed3941c8d2\n\nspec:\n\n\tgvk:\n\t \ group: \"\"\n\t kind: Secret\n\t version: v1\n\tname: postgres\n\tstack: - jqkuffjxcezj-qlii\n\n\nstatus:\n\n\n\t...\n\n\n```\nThis reconciler behind - this ResourceReference will search, in all namespaces, for a secret named - \"postgres\".\nThe secret must have a label `formance.com/stack` with the - value matching either a specific stack or `any` to target any stack.\n\n\nOnce - the reconciler has found the secret, it will copy it inside the stack namespace, - allowing the ResourceReconciler owner to use it." + jqkuffjxcezj-qlii\n\nstatus:\n\n\t...\n\n```\nThis reconciler behind this + ResourceReference will search, in all namespaces, for a secret named \"postgres\".\nThe + secret must have a label `formance.com/stack` with the value matching either + a specific stack or `any` to target any stack.\n\nOnce the reconciler has + found the secret, it will copy it inside the stack namespace, allowing the + ResourceReconciler owner to use it." properties: apiVersion: description: |- @@ -95,16 +95,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -144,12 +136,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_searches.yaml b/config/crd/bases/formance.com_searches.yaml index 0371d2e1..be991255 100644 --- a/config/crd/bases/formance.com_searches.yaml +++ b/config/crd/bases/formance.com_searches.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/is-ee: "true" formance.com/kind: module @@ -102,16 +102,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -151,12 +143,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_settings.yaml b/config/crd/bases/formance.com_settings.yaml index 93af2f0b..936a37db 100644 --- a/config/crd/bases/formance.com_settings.yaml +++ b/config/crd/bases/formance.com_settings.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: settings.formance.com spec: group: formance.com @@ -26,60 +26,69 @@ spec: name: v1beta1 schema: openAPIV3Schema: - description: "Settings represents a configurable piece of the stacks.\n\n\nThe + description: "Settings represents a configurable piece of the stacks.\n\nThe purpose of this resource is to be able to configure some common settings - between a set of stacks.\n\n\nExample :\n```yaml\napiVersion: formance.com/v1beta1\nkind: - Settings\nmetadata:\n\n\n\tname: postgres-uri\n\n\nspec:\n\n\n\tkey: postgres.ledger.uri\n\tstacks:\n\t- - stack0\n\tvalue: postgresql://postgresql.formance.svc.cluster.local:5432\n\n\n```\n\n\nThis + between a set of stacks.\n\nExample :\n```yaml\napiVersion: formance.com/v1beta1\nkind: + Settings\nmetadata:\n\n\tname: postgres-uri\n\nspec:\n\n\tkey: postgres.ledger.uri\n\tstacks:\n\t- + stack0\n\tvalue: postgresql://postgresql.formance.svc.cluster.local:5432\n\n```\n\nThis example create a setting named `postgres-uri` targeting the stack named - `stack0` and the service `ledger` (see the key `postgres.ledger.uri`).\n\n\nTherefore, + `stack0` and the service `ledger` (see the key `postgres.ledger.uri`).\n\nTherefore, a [Database](#database) created for the stack `stack0` and the service named - 'ledger' will use the uri `postgresql://postgresql.formance.svc.cluster.local:5432`.\n\n\nSettings - allow to use wildcards in keys and in stacks list.\n\n\nFor example, if - you want to use the same database server for all the modules of a specific - stack, you can write :\n```yaml\napiVersion: formance.com/v1beta1\nkind: - Settings\nmetadata:\n\n\n\tname: postgres-uri\n\n\nspec:\n\n\n\tkey: postgres.*.uri - # There, we use a wildcard to indicate we want to use that setting of all - services of the stack `stack0`\n\tstacks:\n\t- stack0\n\tvalue: postgresql://postgresql.formance.svc.cluster.local:5432\n\n\n```\n\n\nAlso, + 'ledger' will use the uri `postgresql://postgresql.formance.svc.cluster.local:5432`.\n\nSettings + allow to use wildcards in keys and in stacks list.\n\nFor example, if you + want to use the same database server for all the modules of a specific stack, + you can write :\n```yaml\napiVersion: formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\tname: + postgres-uri\n\nspec:\n\n\tkey: postgres.*.uri # There, we use a wildcard + to indicate we want to use that setting of all services of the stack `stack0`\n\tstacks:\n\t- + stack0\n\tvalue: postgresql://postgresql.formance.svc.cluster.local:5432\n\n```\n\nAlso, we could use that setting for all of our stacks using :\n```yaml\napiVersion: - formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\n\tname: postgres-uri\n\n\nspec:\n\n\n\tkey: + formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\tname: postgres-uri\n\nspec:\n\n\tkey: postgres.*.uri # There, we use a wildcard to indicate we want to use that setting for all services of all stacks\n\tstacks:\n\t- * # There we select - all the stacks\n\tvalue: postgresql://postgresql.formance.svc.cluster.local:5432\n\n\n```\n\n\nSome - settings are really global, while some are used by specific module.\n\n\nRefer - to the documentation of each module and resource to discover available Settings.\n\n\n##### - Global settings\n###### AWS account\n\n\nA stack can use an AWS account - for authentication.\n\n\nIt can be used to connect to any AWS service we - could use.\n\n\nIt includes RDS, OpenSearch and MSK. To do so, you can create - the following setting:\n```yaml\napiVersion: formance.com/v1beta1\nkind: - Settings\nmetadata:\n\n\n\tname: aws-service-account\n\n\nspec:\n\n\n\tkey: - aws.service-account\n\tstacks:\n\t- '*'\n\tvalue: aws-access\n\n\n```\nThis - setting instruct the operator than there is somewhere on the cluster a service - account named `aws-access`.\n\n\nSo, each time a service has the capability - to use AWS, the operator will use this service account.\n\n\nThe service - account could look like that :\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n\n\n\tannotations:\n\t - \ eks.amazonaws.com/role-arn: arn:aws:iam::************:role/staging-eu-west-1-hosting-stack-access\n\tlabels:\n\t - \ formance.com/stack: any\n\tname: aws-access\n\n\n```\nYou can note two - things :\n 1. We have an annotation indicating the role arn used to connect - to AWS. Refer to the AWS documentation to create this role\n 2. We have - a label `formance.com/stack=any` indicating we are targeting all stacks.\n - \ Refer to the documentation of [ResourceReference](#resourcereference) - for further information.\n\n\n###### JSON logging\n\n\nYou can use the setting - `logging.json` with the value `true` to configure elligible service to log - as json.\nExample:\n```yaml\napiVersion: formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\n\tname: - json-logging\n\n\nspec:\n\n\n\tkey: logging.json\n\tstacks:\n\t- '*'\n\tvalue: - \"true\"\n\n\n```\n\n\n###### Authentication scopes\n\n\nYou can enable - scope verification for modules using the setting `auth..check-scopes` - or `auth.*.check-scopes` for all modules.\nWhen enabled, modules will verify - that authenticated requests include the required scopes for the requested - operation.\n\n\nExample to enable scope verification for all modules:\n```yaml\napiVersion: - formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\n\tname: enable-scopes-all\n\n\nspec:\n\n\n\tkey: - auth.*.check-scopes\n\tstacks:\n\t- '*'\n\tvalue: \"true\"\n\n\n```\n\n\nExample - to enable scope verification only for the ledger module:\n```yaml\napiVersion: - formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\n\tname: enable-scopes-ledger\n\n\nspec:\n\n\n\tkey: - auth.ledger.check-scopes\n\tstacks:\n\t- production\n\tvalue: \"true\"\n\n\n```\n\n\nNote: - The `auth.checkScopes` field in module specifications takes priority over - Settings when specified." + all the stacks\n\tvalue: postgresql://postgresql.formance.svc.cluster.local:5432\n\n```\n\nYou + can also fetch values from secrets or configmaps using `valueFrom`:\n```yaml\napiVersion: + formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\tname: postgres-uri-from-secret\n\nspec:\n\n\tkey: + postgres.ledger.uri\n\tstacks:\n\t- stack0\n\tvalueFrom:\n\t secretKeyRef:\n\t + \ name: postgres-credentials\n\t key: connection-string\n\t optional: + false\n\n```\n\nOr from a configmap:\n```yaml\napiVersion: formance.com/v1beta1\nkind: + Settings\nmetadata:\n\n\tname: postgres-uri-from-configmap\n\nspec:\n\n\tkey: + postgres.ledger.uri\n\tstacks:\n\t- stack0\n\tvalueFrom:\n\t configMapKeyRef:\n\t + \ name: postgres-config\n\t key: uri\n\t optional: false\n\n```\n\nNote: + Secrets and configmaps are resolved from the stack's namespace (which has + the same name as the stack).\nEither `value` or `valueFrom` must be set, + but not both.\n\nSome settings are really global, while some are used by + specific module.\n\nRefer to the documentation of each module and resource + to discover available Settings.\n\n##### Global settings\n###### AWS account\n\nA + stack can use an AWS account for authentication.\n\nIt can be used to connect + to any AWS service we could use.\n\nIt includes RDS, OpenSearch and MSK. + To do so, you can create the following setting:\n```yaml\napiVersion: formance.com/v1beta1\nkind: + Settings\nmetadata:\n\n\tname: aws-service-account\n\nspec:\n\n\tkey: aws.service-account\n\tstacks:\n\t- + '*'\n\tvalue: aws-access\n\n```\nThis setting instruct the operator than + there is somewhere on the cluster a service account named `aws-access`.\n\nSo, + each time a service has the capability to use AWS, the operator will use + this service account.\n\nThe service account could look like that :\n```yaml\napiVersion: + v1\nkind: ServiceAccount\nmetadata:\n\n\tannotations:\n\t eks.amazonaws.com/role-arn: + arn:aws:iam::************:role/staging-eu-west-1-hosting-stack-access\n\tlabels:\n\t + \ formance.com/stack: any\n\tname: aws-access\n\n```\nYou can note two things + :\n 1. We have an annotation indicating the role arn used to connect to + AWS. Refer to the AWS documentation to create this role\n 2. We have a label + `formance.com/stack=any` indicating we are targeting all stacks.\n Refer + to the documentation of [ResourceReference](#resourcereference) for further + information.\n\n###### JSON logging\n\nYou can use the setting `logging.json` + with the value `true` to configure elligible service to log as json.\nExample:\n```yaml\napiVersion: + formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\tname: json-logging\n\nspec:\n\n\tkey: + logging.json\n\tstacks:\n\t- '*'\n\tvalue: \"true\"\n\n```\n\n###### Authentication + scopes\n\nYou can enable scope verification for modules using the setting + `auth..check-scopes` or `auth.*.check-scopes` for all modules.\nWhen + enabled, modules will verify that authenticated requests include the required + scopes for the requested operation.\n\nExample to enable scope verification + for all modules:\n```yaml\napiVersion: formance.com/v1beta1\nkind: Settings\nmetadata:\n\n\tname: + enable-scopes-all\n\nspec:\n\n\tkey: auth.*.check-scopes\n\tstacks:\n\t- + '*'\n\tvalue: \"true\"\n\n```\n\nExample to enable scope verification only + for the ledger module:\n```yaml\napiVersion: formance.com/v1beta1\nkind: + Settings\nmetadata:\n\n\tname: enable-scopes-ledger\n\nspec:\n\n\tkey: auth.ledger.check-scopes\n\tstacks:\n\t- + production\n\tvalue: \"true\"\n\n```\n\nNote: The `auth.checkScopes` field + in module specifications takes priority over Settings when specified." properties: apiVersion: description: |- @@ -111,13 +120,67 @@ spec: type: string type: array value: - description: The value. It must have a specific format following the - Key. + description: |- + The value. It must have a specific format following the Key. + Either Value or ValueFrom must be set, but not both. type: string + valueFrom: + description: |- + Source for the value. Can be used to reference a secret or configmap key. + Either Value or ValueFrom must be set, but not both. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be + a valid secret key. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must be + defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: exactly one of the fields in [secretKeyRef configMapKeyRef] + must be set + rule: '[has(self.secretKeyRef),has(self.configMapKeyRef)].filter(x,x==true).size() + == 1' required: - key - - value type: object + x-kubernetes-validations: + - message: exactly one of the fields in [value valueFrom] must be set + rule: '[has(self.value),has(self.valueFrom)].filter(x,x==true).size() + == 1' type: object served: true storage: true diff --git a/config/crd/bases/formance.com_stacks.yaml b/config/crd/bases/formance.com_stacks.yaml index c08deb3c..54c7a1a4 100644 --- a/config/crd/bases/formance.com_stacks.yaml +++ b/config/crd/bases/formance.com_stacks.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: stacks.formance.com spec: group: formance.com @@ -51,19 +51,14 @@ spec: A Stack is basically a container. It holds some global properties and creates a namespace if not already existing. - To do more, you need to create some [modules](#modules). - The Stack resource allow to specify the version of the stack. - It can be specified using either the field `.spec.version` or the `.spec.versionsFromFile` field (Refer to the documentation of [Versions](#versions) resource. - The `version` field will have priority over `versionFromFile`. - If `versions` and `versionsFromFile` are not specified, "latest" will be used. properties: apiVersion: @@ -124,16 +119,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -173,12 +160,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_stargates.yaml b/config/crd/bases/formance.com_stargates.yaml index ba3968b9..b9e58c1f 100644 --- a/config/crd/bases/formance.com_stargates.yaml +++ b/config/crd/bases/formance.com_stargates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/kind: module name: stargates.formance.com @@ -111,16 +111,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -160,12 +152,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_versions.yaml b/config/crd/bases/formance.com_versions.yaml index 6e59f41f..aaabad7a 100644 --- a/config/crd/bases/formance.com_versions.yaml +++ b/config/crd/bases/formance.com_versions.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: versions.formance.com spec: group: formance.com diff --git a/config/crd/bases/formance.com_wallets.yaml b/config/crd/bases/formance.com_wallets.yaml index b4af0120..23023532 100644 --- a/config/crd/bases/formance.com_wallets.yaml +++ b/config/crd/bases/formance.com_wallets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/is-ee: "true" formance.com/kind: module @@ -88,16 +88,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -137,12 +129,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/formance.com_webhooks.yaml b/config/crd/bases/formance.com_webhooks.yaml index d80aad81..9b40d108 100644 --- a/config/crd/bases/formance.com_webhooks.yaml +++ b/config/crd/bases/formance.com_webhooks.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 labels: formance.com/is-ee: "true" formance.com/kind: module @@ -87,16 +87,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Status []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -136,12 +128,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 718b68ba..48076c80 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,119 +4,16 @@ kind: ClusterRole metadata: name: manager-role rules: -- apiGroups: - - apps - resources: - - deployments - verbs: - - create - - delete - - deletecollection - - get - - list - - patch - - update - - watch -- apiGroups: - - batch - resources: - - cronjobs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - batch - resources: - - jobs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - cert-manager.io - resources: - - certificates - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - "" resources: - configmaps - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - events - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - namespaces - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - pods - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - serviceaccounts + - services verbs: - create - delete @@ -126,21 +23,23 @@ rules: - update - watch - apiGroups: - - "" + - apps resources: - - services + - deployments verbs: - create - delete + - deletecollection - get - list - patch - update - watch - apiGroups: - - formance.com + - batch resources: - - authclients + - cronjobs + - jobs verbs: - create - delete @@ -150,23 +49,9 @@ rules: - update - watch - apiGroups: - - formance.com - resources: - - authclients/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - authclients/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com + - cert-manager.io resources: - - auths + - certificates verbs: - create - delete @@ -178,21 +63,27 @@ rules: - apiGroups: - formance.com resources: - - auths/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - auths/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: + - authclients + - auths - benthos + - brokerconsumers + - brokers + - brokertopics + - databases + - gatewayhttpapis + - gateways + - ledgers + - orchestrations + - payments + - reconciliations + - resourcereferences + - searches + - settings + - stacks + - stargates + - versions + - wallets + - webhooks verbs: - create - delete @@ -204,13 +95,55 @@ rules: - apiGroups: - formance.com resources: + - authclients/finalizers + - auths/finalizers - benthos/finalizers + - benthosstreams/finalizers + - brokerconsumers/finalizers + - brokers/finalizers + - brokertopics/finalizers + - databases/finalizers + - gatewayhttpapis/finalizers + - gateways/finalizers + - ledgers/finalizers + - orchestrations/finalizers + - payments/finalizers + - reconciliations/finalizers + - resourcereferences/finalizers + - searches/finalizers + - settings/finalizers + - stacks/finalizers + - stargates/finalizers + - versions/finalizers + - wallets/finalizers + - webhooks/finalizers verbs: - update - apiGroups: - formance.com resources: + - authclients/status + - auths/status - benthos/status + - benthosstreams/status + - brokerconsumers/status + - brokers/status + - brokertopics/status + - databases/status + - gatewayhttpapis/status + - gateways/status + - ledgers/status + - orchestrations/status + - payments/status + - reconciliations/status + - resourcereferences/status + - searches/status + - settings/status + - stacks/status + - stargates/status + - versions/status + - wallets/status + - webhooks/status verbs: - get - patch @@ -228,488 +161,6 @@ rules: - patch - update - watch -- apiGroups: - - formance.com - resources: - - benthosstreams/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - benthosstreams/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - brokerconsumers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - brokerconsumers/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - brokerconsumers/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - brokers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - brokers/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - brokers/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - brokertopics - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - brokertopics/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - brokertopics/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - databases - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - databases/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - databases/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - gatewayhttpapis - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - gatewayhttpapis/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - gatewayhttpapis/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - gateways - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - gateways/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - gateways/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - ledgers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - ledgers/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - ledgers/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - orchestrations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - orchestrations/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - orchestrations/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - payments - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - payments/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - payments/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - reconciliations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - reconciliations/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - reconciliations/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - resourcereferences - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - resourcereferences/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - resourcereferences/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - searches - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - searches/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - searches/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - settings - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - settings/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - settings/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - stacks - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - stacks/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - stacks/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - stargates - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - stargates/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - stargates/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - versions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - versions/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - versions/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - wallets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - wallets/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - wallets/status - verbs: - - get - - patch - - update -- apiGroups: - - formance.com - resources: - - webhooks - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formance.com - resources: - - webhooks/finalizers - verbs: - - update -- apiGroups: - - formance.com - resources: - - webhooks/status - verbs: - - get - - patch - - update - apiGroups: - networking.k8s.io resources: diff --git a/go.mod b/go.mod index 799fb65e..c6bc76ef 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( ) require ( + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect diff --git a/internal/resources/settings/helpers.go b/internal/resources/settings/helpers.go index 57989747..611047c7 100644 --- a/internal/resources/settings/helpers.go +++ b/internal/resources/settings/helpers.go @@ -3,6 +3,7 @@ package settings import ( "bytes" "encoding/json" + "errors" "fmt" "io" "reflect" @@ -13,17 +14,23 @@ import ( "github.com/formancehq/go-libs/v2/pointer" "github.com/formancehq/operator/api/formance.com/v1beta1" "github.com/formancehq/operator/internal/core" - "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) +var ( + ErrResourceNotFound = errors.New("resource not found") + ErrOptionalResourceNotFound = errors.New("optional resource not found") +) + func Get(ctx core.Context, stack string, keys ...string) (*string, error) { allSettingsTargetingStack := &v1beta1.SettingsList{} if err := ctx.GetClient().List(ctx, allSettingsTargetingStack, client.MatchingFields{ "stack": stack, "keylen": fmt.Sprint(len(keys)), }); err != nil { - return nil, errors.Wrap(err, "listings settings") + return nil, fmt.Errorf("listings settings: %w", err) } allSettingsTargetingAllStacks := &v1beta1.SettingsList{} @@ -31,10 +38,10 @@ func Get(ctx core.Context, stack string, keys ...string) (*string, error) { "stack": "*", "keylen": fmt.Sprint(len(keys)), }); err != nil { - return nil, errors.Wrap(err, "listings settings") + return nil, fmt.Errorf("listings settings: %w", err) } - return findMatchingSettings(append(allSettingsTargetingStack.Items, allSettingsTargetingAllStacks.Items...), keys...) + return findMatchingSettings(ctx, stack, append(allSettingsTargetingStack.Items, allSettingsTargetingAllStacks.Items...), keys...) } func GetString(ctx core.Context, stack string, keys ...string) (*string, error) { @@ -383,8 +390,7 @@ func GetMapOrEmpty(ctx core.Context, stack string, keys ...string) (map[string]s return value, nil } -func findMatchingSettings(settings []v1beta1.Settings, flattenKeys ...string) (*string, error) { - +func findMatchingSettings(ctx core.Context, stack string, settings []v1beta1.Settings, flattenKeys ...string) (*string, error) { // Keys can be passed as "a.b.c", instead of "a", "b", "c" // Keys can be passed as "a.b.*", instead of "a", "b", "*" // Keys can be passed as "a.*.c", instead of "a", "*", "c" @@ -393,6 +399,30 @@ func findMatchingSettings(settings []v1beta1.Settings, flattenKeys ...string) (* for _, setting := range settings { if matchSetting(setting, flattenKeys...) { + // Check valueFrom first since CEL validation ensures exactly one of value/valueFrom is set + if setting.Spec.ValueFrom != nil { + // Resolve value from secret or configmap + value, err := resolveValueFrom(ctx, stack, setting.Spec.ValueFrom) + if err != nil { + if errors.Is(err, ErrResourceNotFound) || errors.Is(err, ErrOptionalResourceNotFound) { + // Try fallback to formance-system namespace + value, err2 := resolveValueFrom(ctx, "formance-system", setting.Spec.ValueFrom) + if err2 == nil { + return &value, nil + } + + // If resource is optional and not found in either namespace, return empty string + if errors.Is(err2, ErrOptionalResourceNotFound) { + empty := "" + return &empty, nil + } + err = fmt.Errorf("%w: %w", err, err2) + } + return nil, fmt.Errorf("resolving valueFrom for setting '%s': %w", setting.Name, err) + } + return &value, nil + } + // Value is set (CEL validation ensures one of value/valueFrom is present) return &setting.Spec.Value, nil } } @@ -400,6 +430,75 @@ func findMatchingSettings(settings []v1beta1.Settings, flattenKeys ...string) (* return nil, nil } +func resolveValueFrom(ctx core.Context, namespace string, valueFrom *v1beta1.ValueFrom) (string, error) { + if valueFrom == nil { + return "", errors.New("valueFrom is nil") + } + + if valueFrom.SecretKeyRef != nil { + secret := &corev1.Secret{} + secretName := valueFrom.SecretKeyRef.Name + key := valueFrom.SecretKeyRef.Key + tSec := types.NamespacedName{ + Namespace: namespace, + Name: secretName, + } + + err := ctx.GetClient().Get(ctx, tSec, secret) + if err != nil { + if valueFrom.SecretKeyRef.Optional != nil && *valueFrom.SecretKeyRef.Optional && client.IgnoreNotFound(err) == nil { + return "", ErrOptionalResourceNotFound + } + return "", fmt.Errorf("%w: namespace '%s': %w", ErrResourceNotFound, namespace, err) + } + + value, ok := secret.Data[key] + if !ok { + if valueFrom.SecretKeyRef.Optional != nil && *valueFrom.SecretKeyRef.Optional { + return "", ErrOptionalResourceNotFound + } + return "", fmt.Errorf("key '%s' not found in secret '%s/%s'", key, namespace, secretName) + } + + return string(value), nil + } + + if valueFrom.ConfigMapKeyRef != nil { + configMap := &corev1.ConfigMap{} + configMapName := valueFrom.ConfigMapKeyRef.Name + key := valueFrom.ConfigMapKeyRef.Key + tConf := types.NamespacedName{ + Namespace: namespace, + Name: configMapName, + } + + err := ctx.GetClient().Get(ctx, tConf, configMap) + if err != nil { + if valueFrom.ConfigMapKeyRef.Optional != nil && *valueFrom.ConfigMapKeyRef.Optional && client.IgnoreNotFound(err) == nil { + return "", ErrOptionalResourceNotFound + } + return "", fmt.Errorf("%w: namespace '%s': %w", ErrResourceNotFound, namespace, err) + } + + value, ok := configMap.Data[key] + if !ok { + // Try binary data as well + valueBytes, ok := configMap.BinaryData[key] + if ok { + return string(valueBytes), nil + } + if valueFrom.ConfigMapKeyRef.Optional != nil && *valueFrom.ConfigMapKeyRef.Optional { + return "", ErrOptionalResourceNotFound + } + return "", fmt.Errorf("key '%s' not found in configmap '%s/%s'", key, namespace, configMapName) + } + + return value, nil + } + + return "", errors.New("valueFrom must specify either secretKeyRef or configMapKeyRef") +} + func matchSetting(setting v1beta1.Settings, keys ...string) bool { settingKeyParts := SplitKeywordWithDot(setting.Spec.Key) for i, settingKeyPart := range settingKeyParts { diff --git a/internal/resources/settings/helpers_test.go b/internal/resources/settings/helpers_test.go index 7fc93c6e..69694755 100644 --- a/internal/resources/settings/helpers_test.go +++ b/internal/resources/settings/helpers_test.go @@ -1,12 +1,30 @@ package settings import ( + "context" "fmt" "testing" + "net/http" + . "github.com/formancehq/go-libs/v2/collectionutils" "github.com/formancehq/operator/api/formance.com/v1beta1" + "github.com/formancehq/operator/internal/core" + "github.com/go-logr/logr" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/config" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook" ) func TestSplitKeywordWithDot(t *testing.T) { @@ -189,7 +207,7 @@ func TestFindMatchingSettings(t *testing.T) { tc := tc t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() - value, err := findMatchingSettings(Map(tc.settings, func(from settings) v1beta1.Settings { + value, err := findMatchingSettings(core.NewContext(nil, context.Background()), "test", Map(tc.settings, func(from settings) v1beta1.Settings { ret := v1beta1.Settings{ Spec: v1beta1.SettingsSpec{ Key: from.key, @@ -219,3 +237,344 @@ func TestParseKeyValuePair(t *testing.T) { "h": "i,j", }, ret) } + +func TestFindMatchingSettingsWithValueFrom(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + + tests := []struct { + name string + stack string + setting v1beta1.Settings + secrets []*corev1.Secret + configMaps []*corev1.ConfigMap + expectedResult string + expectedError string + }{ + { + name: "resolve from secret in stack namespace", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "postgres-secret", + }, + Key: "connection-string", + }, + }, + }, + }, + secrets: []*corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-secret", + Namespace: "test-stack", + }, + Data: map[string][]byte{ + "connection-string": []byte("postgresql://localhost:5432/test"), + }, + }, + }, + expectedResult: "postgresql://localhost:5432/test", + }, + { + name: "resolve from configmap in stack namespace", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "postgres-config", + }, + Key: "uri", + }, + }, + }, + }, + configMaps: []*corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-config", + Namespace: "test-stack", + }, + Data: map[string]string{ + "uri": "postgresql://localhost:5432/test", + }, + }, + }, + expectedResult: "postgresql://localhost:5432/test", + }, + { + name: "fallback to formance-system namespace when not found in stack namespace", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "postgres-secret", + }, + Key: "connection-string", + }, + }, + }, + }, + secrets: []*corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-secret", + Namespace: "formance-system", + }, + Data: map[string][]byte{ + "connection-string": []byte("postgresql://localhost:5432/test"), + }, + }, + }, + expectedResult: "postgresql://localhost:5432/test", + }, + // Note: Test case "value takes precedence over valueFrom" removed because + // CEL validation now enforces exactly one of value/valueFrom to be set. + // Having both set is an impossible state at runtime. + { + name: "optional secret not found returns empty string", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "missing-secret", + }, + Key: "connection-string", + Optional: func() *bool { b := true; return &b }(), + }, + }, + }, + }, + expectedResult: "", // Optional resources return empty string when not found + }, + { + name: "optional configmap not found returns empty string", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "missing-configmap", + }, + Key: "uri", + Optional: func() *bool { b := true; return &b }(), + }, + }, + }, + }, + expectedResult: "", // Optional resources return empty string when not found + }, + { + name: "secret not found returns error", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "missing-secret", + }, + Key: "connection-string", + }, + }, + }, + }, + expectedError: "resource not found", + }, + { + name: "key not found in secret returns error", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "postgres-secret", + }, + Key: "missing-key", + }, + }, + }, + }, + secrets: []*corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-secret", + Namespace: "test-stack", + }, + Data: map[string][]byte{ + "connection-string": []byte("postgresql://localhost:5432/test"), + }, + }, + }, + expectedError: "not found in secret", + }, + { + name: "key not found in configmap returns error", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "postgres-config", + }, + Key: "missing-key", + }, + }, + }, + }, + configMaps: []*corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-config", + Namespace: "test-stack", + }, + Data: map[string]string{ + "uri": "postgresql://localhost:5432/test", + }, + }, + }, + expectedError: "not found in configmap", + }, + { + name: "configmap binary data", + stack: "test-stack", + setting: v1beta1.Settings{ + ObjectMeta: metav1.ObjectMeta{Name: "test-setting"}, + Spec: v1beta1.SettingsSpec{ + Key: "postgres.ledger.uri", + ValueFrom: &v1beta1.ValueFrom{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "postgres-config", + }, + Key: "uri", + }, + }, + }, + }, + configMaps: []*corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-config", + Namespace: "test-stack", + }, + BinaryData: map[string][]byte{ + "uri": []byte("postgresql://localhost:5432/test"), + }, + }, + }, + expectedResult: "postgresql://localhost:5432/test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Build list of objects for fake client + objects := make([]client.Object, 0) + for _, secret := range tt.secrets { + objects = append(objects, secret) + } + for _, cm := range tt.configMaps { + objects = append(objects, cm) + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(objects...). + Build() + + // Create a mock manager + mockMgr := &mockManager{ + client: fakeClient, + scheme: scheme, + } + + coreMgr := core.NewDefaultManager(mockMgr, core.Platform{ + Region: "test", + Environment: "test", + }) + + ctx := core.NewContext(coreMgr, context.Background()) + + value, err := findMatchingSettings(ctx, tt.stack, []v1beta1.Settings{tt.setting}, SplitKeywordWithDot(tt.setting.Spec.Key)...) + + if tt.expectedError != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedError) + require.Nil(t, value) + } else { + require.NoError(t, err) + require.NotNil(t, value) + require.Equal(t, tt.expectedResult, *value) + } + }) + } +} + +// mockManager implements ctrl.Manager for testing +type mockManager struct { + client client.Client + scheme *runtime.Scheme +} + +func (m *mockManager) GetClient() client.Client { + return m.client +} + +func (m *mockManager) GetScheme() *runtime.Scheme { + return m.scheme +} + +func (m *mockManager) GetAPIReader() client.Reader { + return m.client +} + +// Implement other required methods with no-ops or panics +func (m *mockManager) Add(_ manager.Runnable) error { return nil } +func (m *mockManager) SetFields(_ interface{}) error { return nil } +func (m *mockManager) AddMetricsExtraHandler(_ string, _ http.Handler) error { return nil } +func (m *mockManager) AddHealthzCheck(_ string, _ healthz.Checker) error { return nil } +func (m *mockManager) AddReadyzCheck(_ string, _ healthz.Checker) error { return nil } +func (m *mockManager) Start(_ context.Context) error { return nil } +func (m *mockManager) GetWebhookServer() webhook.Server { return nil } +func (m *mockManager) GetLogger() logr.Logger { return logr.Discard() } +func (m *mockManager) GetControllerOptions() config.Controller { return config.Controller{} } +func (m *mockManager) GetCache() cache.Cache { return nil } +func (m *mockManager) GetEventRecorderFor(_ string) record.EventRecorder { return nil } +func (m *mockManager) GetRESTMapper() meta.RESTMapper { return nil } +func (m *mockManager) GetHTTPClient() *http.Client { return nil } +func (m *mockManager) GetConfig() *rest.Config { return nil } +func (m *mockManager) Elected() <-chan struct{} { return nil } +func (m *mockManager) GetFieldIndexer() client.FieldIndexer { return nil }