mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: implement a cluster-wide generator (#4140)
* feat: implement a cluster-wide generator Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * remove unneeded function Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * check diff run output Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * alternative implementation of the Generator approach using specs only Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * refactor the extracting code Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * slight modification to the naming of the spec from generatorSpec to simply generator Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * write a unit test for the generator and register it in the scheme Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * add documentation for the cluster generator Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --------- Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
parent
40a698dafd
commit
fb9526f38a
24 changed files with 3220 additions and 20 deletions
|
@ -393,7 +393,7 @@ type GeneratorRef struct {
|
|||
// Specify the apiVersion of the generator resource
|
||||
// +kubebuilder:default="generators.external-secrets.io/v1alpha1"
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
// Specify the Kind of the resource, e.g. Password, ACRAccessToken, ClusterGenerator etc.
|
||||
Kind string `json:"kind"`
|
||||
// Specify the name of the generator resource
|
||||
Name string `json:"name"`
|
||||
|
|
|
@ -59,7 +59,7 @@ func GetGeneratorByName(kind string) (Generator, bool) {
|
|||
return f, ok
|
||||
}
|
||||
|
||||
// GetGenerator returns a implementation from a generator
|
||||
// GetGenerator returns an implementation from a generator
|
||||
// defined as json.
|
||||
func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
|
||||
type unknownGenerator struct {
|
||||
|
@ -75,7 +75,7 @@ func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
|
|||
defer buildlock.RUnlock()
|
||||
gen, ok := builder[res.Kind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to find registered generator for: %s", string(obj.Raw))
|
||||
return nil, fmt.Errorf("failed to find registered generator for: %s with kind: %s", string(obj.Raw), res.Kind)
|
||||
}
|
||||
return gen, nil
|
||||
}
|
||||
|
|
|
@ -14,8 +14,65 @@ limitations under the License.
|
|||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// A couple of constants to define the generator's keys for accessing via Resource map values.
|
||||
const (
|
||||
GeneratorGeneratorKey = "generator"
|
||||
GeneratorKindKey = "kind"
|
||||
GeneratorSpecKey = "spec"
|
||||
)
|
||||
|
||||
type ControllerClassResource struct {
|
||||
Spec struct {
|
||||
ControllerClass string `json:"controller"`
|
||||
} `json:"spec"`
|
||||
}
|
||||
|
||||
type GeneratorSpec struct {
|
||||
ACRAccessTokenSpec *ACRAccessTokenSpec `json:"acrAccessTokenSpec,omitempty"`
|
||||
ECRAuthorizationTokenSpec *ECRAuthorizationTokenSpec `json:"ecrRAuthorizationTokenSpec,omitempty"`
|
||||
FakeSpec *FakeSpec `json:"fakeSpec,omitempty"`
|
||||
GCRAccessTokenSpec *GCRAccessTokenSpec `json:"gcrAccessTokenSpec,omitempty"`
|
||||
GithubAccessTokenSpec *GithubAccessTokenSpec `json:"githubAccessTokenSpec,omitempty"`
|
||||
PasswordSpec *PasswordSpec `json:"passwordSpec,omitempty"`
|
||||
STSSessionTokenSpec *STSSessionTokenSpec `json:"stsSessionTokenSpec,omitempty"`
|
||||
UUIDSpec *UUIDSpec `json:"uuidSpec,omitempty"`
|
||||
VaultDynamicSecretSpec *VaultDynamicSecretSpec `json:"vaultDynamicSecretSpec,omitempty"`
|
||||
WebhookSpec *WebhookSpec `json:"webhookSpec,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterGeneratorSpec struct {
|
||||
Kind string `json:"kind"`
|
||||
Generator GeneratorSpec `json:"generator"`
|
||||
}
|
||||
|
||||
type ClusterGeneratorStatus struct{}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
|
||||
// ClusterGenerator represents a cluster-wide generator which can be referenced as part of `generatorRef` fields.
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:metadata:labels="external-secrets.io/component=controller"
|
||||
// +kubebuilder:resource:scope=Cluster,categories={external-secrets, external-secrets-generators},shortName=cg
|
||||
type ClusterGenerator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ClusterGeneratorSpec `json:"spec,omitempty"`
|
||||
Status ClusterGeneratorStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ClusterGeneratorList contains a list of ClusterGenerator resources.
|
||||
type ClusterGeneratorList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ClusterGenerator `json:"items"`
|
||||
}
|
||||
|
|
|
@ -116,6 +116,14 @@ var (
|
|||
UUIDGroupVersionKind = SchemeGroupVersion.WithKind(UUIDKind)
|
||||
)
|
||||
|
||||
// ClusterGenerator type metadata.
|
||||
var (
|
||||
ClusterGeneratorKind = reflect.TypeOf(ClusterGenerator{}).Name()
|
||||
ClusterGeneratorGroupKind = schema.GroupKind{Group: Group, Kind: ClusterGeneratorKind}.String()
|
||||
ClusterGeneratorKindAPIVersion = ClusterGeneratorKind + "." + SchemeGroupVersion.String()
|
||||
ClusterGeneratorGroupVersionKind = SchemeGroupVersion.WithKind(ClusterGeneratorKind)
|
||||
)
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&ECRAuthorizationToken{}, &ECRAuthorizationToken{})
|
||||
SchemeBuilder.Register(&GCRAccessToken{}, &GCRAccessTokenList{})
|
||||
|
@ -125,4 +133,5 @@ func init() {
|
|||
SchemeBuilder.Register(&VaultDynamicSecret{}, &VaultDynamicSecretList{})
|
||||
SchemeBuilder.Register(&Password{}, &PasswordList{})
|
||||
SchemeBuilder.Register(&Webhook{}, &WebhookList{})
|
||||
SchemeBuilder.Register(&ClusterGenerator{}, &ClusterGeneratorList{})
|
||||
}
|
||||
|
|
|
@ -265,6 +265,96 @@ func (in *AzureACRWorkloadIdentityAuth) DeepCopy() *AzureACRWorkloadIdentityAuth
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterGenerator) DeepCopyInto(out *ClusterGenerator) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGenerator.
|
||||
func (in *ClusterGenerator) DeepCopy() *ClusterGenerator {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterGenerator)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterGenerator) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterGeneratorList) DeepCopyInto(out *ClusterGeneratorList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ClusterGenerator, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorList.
|
||||
func (in *ClusterGeneratorList) DeepCopy() *ClusterGeneratorList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterGeneratorList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterGeneratorList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterGeneratorSpec) DeepCopyInto(out *ClusterGeneratorSpec) {
|
||||
*out = *in
|
||||
in.Generator.DeepCopyInto(&out.Generator)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorSpec.
|
||||
func (in *ClusterGeneratorSpec) DeepCopy() *ClusterGeneratorSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterGeneratorSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterGeneratorStatus) DeepCopyInto(out *ClusterGeneratorStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorStatus.
|
||||
func (in *ClusterGeneratorStatus) DeepCopy() *ClusterGeneratorStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterGeneratorStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ControllerClassResource) DeepCopyInto(out *ControllerClassResource) {
|
||||
*out = *in
|
||||
|
@ -566,6 +656,71 @@ func (in *GCRAccessTokenSpec) DeepCopy() *GCRAccessTokenSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GeneratorSpec) DeepCopyInto(out *GeneratorSpec) {
|
||||
*out = *in
|
||||
if in.ACRAccessTokenSpec != nil {
|
||||
in, out := &in.ACRAccessTokenSpec, &out.ACRAccessTokenSpec
|
||||
*out = new(ACRAccessTokenSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ECRAuthorizationTokenSpec != nil {
|
||||
in, out := &in.ECRAuthorizationTokenSpec, &out.ECRAuthorizationTokenSpec
|
||||
*out = new(ECRAuthorizationTokenSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.FakeSpec != nil {
|
||||
in, out := &in.FakeSpec, &out.FakeSpec
|
||||
*out = new(FakeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.GCRAccessTokenSpec != nil {
|
||||
in, out := &in.GCRAccessTokenSpec, &out.GCRAccessTokenSpec
|
||||
*out = new(GCRAccessTokenSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.GithubAccessTokenSpec != nil {
|
||||
in, out := &in.GithubAccessTokenSpec, &out.GithubAccessTokenSpec
|
||||
*out = new(GithubAccessTokenSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.PasswordSpec != nil {
|
||||
in, out := &in.PasswordSpec, &out.PasswordSpec
|
||||
*out = new(PasswordSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.STSSessionTokenSpec != nil {
|
||||
in, out := &in.STSSessionTokenSpec, &out.STSSessionTokenSpec
|
||||
*out = new(STSSessionTokenSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.UUIDSpec != nil {
|
||||
in, out := &in.UUIDSpec, &out.UUIDSpec
|
||||
*out = new(UUIDSpec)
|
||||
**out = **in
|
||||
}
|
||||
if in.VaultDynamicSecretSpec != nil {
|
||||
in, out := &in.VaultDynamicSecretSpec, &out.VaultDynamicSecretSpec
|
||||
*out = new(VaultDynamicSecretSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.WebhookSpec != nil {
|
||||
in, out := &in.WebhookSpec, &out.WebhookSpec
|
||||
*out = new(WebhookSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratorSpec.
|
||||
func (in *GeneratorSpec) DeepCopy() *GeneratorSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GeneratorSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GithubAccessToken) DeepCopyInto(out *GithubAccessToken) {
|
||||
*out = *in
|
||||
|
|
|
@ -151,7 +151,7 @@ spec:
|
|||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
Password, ACRAccessToken, ClusterGenerator etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
|
@ -327,7 +327,7 @@ spec:
|
|||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
Password, ACRAccessToken, ClusterGenerator etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
|
|
|
@ -416,7 +416,7 @@ spec:
|
|||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
Password, ACRAccessToken, ClusterGenerator etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
|
@ -591,7 +591,7 @@ spec:
|
|||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
Password, ACRAccessToken, ClusterGenerator etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
|
|
|
@ -177,7 +177,7 @@ spec:
|
|||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password,
|
||||
ACRAccessToken etc.
|
||||
ACRAccessToken, ClusterGenerator etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,7 @@ resources:
|
|||
- external-secrets.io_pushsecrets.yaml
|
||||
- external-secrets.io_secretstores.yaml
|
||||
- generators.external-secrets.io_acraccesstokens.yaml
|
||||
- generators.external-secrets.io_clustergenerators.yaml
|
||||
- generators.external-secrets.io_ecrauthorizationtokens.yaml
|
||||
- generators.external-secrets.io_fakes.yaml
|
||||
- generators.external-secrets.io_gcraccesstokens.yaml
|
||||
|
|
|
@ -89,6 +89,7 @@ The command removes all the Kubernetes components associated with the chart and
|
|||
| crds.annotations | object | `{}` | |
|
||||
| crds.conversion.enabled | bool | `true` | If webhook is set to false this also needs to be set to false otherwise the kubeapi will be hammered because the conversion is looking for a webhook endpoint. |
|
||||
| crds.createClusterExternalSecret | bool | `true` | If true, create CRDs for Cluster External Secret. |
|
||||
| crds.createClusterGenerator | bool | `true` | If true, create CRDs for Cluster Generator. |
|
||||
| crds.createClusterSecretStore | bool | `true` | If true, create CRDs for Cluster Secret Store. |
|
||||
| crds.createPushSecret | bool | `true` | If true, create CRDs for Push Secret. |
|
||||
| createOperator | bool | `true` | Specifies whether an external secret operator deployment be created. |
|
||||
|
|
|
@ -51,6 +51,7 @@ rules:
|
|||
- "generators.external-secrets.io"
|
||||
resources:
|
||||
- "acraccesstokens"
|
||||
- "clustergenerators"
|
||||
- "ecrauthorizationtokens"
|
||||
- "fakes"
|
||||
- "gcraccesstokens"
|
||||
|
@ -145,6 +146,7 @@ rules:
|
|||
- "generators.external-secrets.io"
|
||||
resources:
|
||||
- "acraccesstokens"
|
||||
- "clustergenerators"
|
||||
- "ecrauthorizationtokens"
|
||||
- "fakes"
|
||||
- "gcraccesstokens"
|
||||
|
@ -190,6 +192,7 @@ rules:
|
|||
- "generators.external-secrets.io"
|
||||
resources:
|
||||
- "acraccesstokens"
|
||||
- "clustergenerators"
|
||||
- "ecrauthorizationtokens"
|
||||
- "fakes"
|
||||
- "gcraccesstokens"
|
||||
|
|
|
@ -270,6 +270,9 @@
|
|||
"createClusterExternalSecret": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"createClusterGenerator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"createClusterSecretStore": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
@ -39,6 +39,8 @@ crds:
|
|||
createClusterExternalSecret: true
|
||||
# -- If true, create CRDs for Cluster Secret Store.
|
||||
createClusterSecretStore: true
|
||||
# -- If true, create CRDs for Cluster Generator.
|
||||
createClusterGenerator: true
|
||||
# -- If true, create CRDs for Push Secret.
|
||||
createPushSecret: true
|
||||
annotations: {}
|
||||
|
|
File diff suppressed because it is too large
Load diff
20
docs/api/generator/cluster.md
Normal file
20
docs/api/generator/cluster.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
`ClusterGenerator` is a generator wrapper that is available to configure a generator
|
||||
cluster-wide. The purpose of this generator is that the user doesn't have to redefine
|
||||
the generator in every namespace. They could define it once in the cluster and then reference that
|
||||
in the consuming `ExternalSecret`.
|
||||
|
||||
## Limitations
|
||||
|
||||
With this, the generator will still create objects in the namespace in which the referencing ES lives.
|
||||
That has not changed as of now. It will change in future modifications.
|
||||
|
||||
## Example Manifest
|
||||
|
||||
```yaml
|
||||
{% include 'generator-cluster.yaml' %}
|
||||
```
|
||||
|
||||
Example `ExternalSecret` that references the Cluster generator:
|
||||
```yaml
|
||||
{% include 'generator-cluster-example.yaml' %}
|
||||
```
|
|
@ -4569,7 +4569,7 @@ string
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.</p>
|
||||
<p>Specify the Kind of the resource, e.g. Password, ACRAccessToken, ClusterGenerator etc.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
Generators allow you to generate values. They are used through a ExternalSecret `spec.DataFrom`. They are referenced from a custom resource using `sourceRef.generatorRef`.
|
||||
|
||||
If the External Secret should be refreshed via `spec.refreshInterval` the generator produces a map of values with the `generator.spec` as input. The generator does not keep track of the produced values. Every invocation produces a new set of values.
|
||||
|
@ -24,4 +23,47 @@ spec:
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ECRAuthorizationToken
|
||||
name: "my-ecr"
|
||||
```
|
||||
```
|
||||
|
||||
## Cluster Generate Resource
|
||||
|
||||
It's possible to use a `Cluster` scoped generator. At the moment of this writing, this Generator
|
||||
will only help in locating the Generator cluster-wide. It doesn't mean that the generator can create resources in all
|
||||
namespaces. It will still only create a resource in the given namespace where the referencing `ExternalSecret` lives.
|
||||
|
||||
To define a `ClusterGenerator` use the following config:
|
||||
|
||||
```yaml
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ClusterGenerator
|
||||
metadata:
|
||||
name: my-generator
|
||||
spec:
|
||||
kind: Password
|
||||
generator:
|
||||
passwordSpec:
|
||||
length: 42
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
```
|
||||
|
||||
All the generators are available as a ClusterGenerator spec. The `kind` field MUST match the kind of the Generator
|
||||
exactly. The following Spec fields are available:
|
||||
|
||||
```go
|
||||
type GeneratorSpec struct {
|
||||
ACRAccessTokenSpec *ACRAccessTokenSpec `json:"acrAccessTokenSpec,omitempty"`
|
||||
ECRAuthorizationTokenSpec *ECRAuthorizationTokenSpec `json:"ecrRAuthorizationTokenSpec,omitempty"`
|
||||
FakeSpec *FakeSpec `json:"fakeSpec,omitempty"`
|
||||
GCRAccessTokenSpec *GCRAccessTokenSpec `json:"gcrAccessTokenSpec,omitempty"`
|
||||
GithubAccessTokenSpec *GithubAccessTokenSpec `json:"githubAccessTokenSpec,omitempty"`
|
||||
PasswordSpec *PasswordSpec `json:"passwordSpec,omitempty"`
|
||||
STSSessionTokenSpec *STSSessionTokenSpec `json:"stsSessionTokenSpec,omitempty"`
|
||||
UUIDSpec *UUIDSpec `json:"uuidSpec,omitempty"`
|
||||
VaultDynamicSecretSpec *VaultDynamicSecretSpec `json:"vaultDynamicSecretSpec,omitempty"`
|
||||
WebhookSpec *WebhookSpec `json:"webhookSpec,omitempty"`
|
||||
}
|
||||
```
|
||||
|
|
14
docs/snippets/generator-cluster-example.yaml
Normal file
14
docs/snippets/generator-cluster-example.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: "cluster-secret"
|
||||
spec:
|
||||
refreshInterval: "1h"
|
||||
target:
|
||||
name: cluster-secret
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ClusterGenerator
|
||||
name: "cluster-gen"
|
24
docs/snippets/generator-cluster.yaml
Normal file
24
docs/snippets/generator-cluster.yaml
Normal file
|
@ -0,0 +1,24 @@
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ClusterGenerator
|
||||
metadata:
|
||||
name: cluster-gen
|
||||
spec:
|
||||
kind: Password
|
||||
generator:
|
||||
# Further specs are available:
|
||||
# acrAccessTokenSpec:
|
||||
# ecrRAuthorizationTokenSpec:
|
||||
# fakeSpec:
|
||||
# gcrAccessTokenSpec:
|
||||
# githubAccessTokenSpec:
|
||||
# stsSessionTokenSpec:
|
||||
# uuidSpec:
|
||||
# vaultDynamicSecretSpec:
|
||||
# webhookSpec:
|
||||
passwordSpec:
|
||||
length: 42
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
|
@ -69,6 +69,7 @@ nav:
|
|||
- Azure Container Registry: api/generator/acr.md
|
||||
- AWS Elastic Container Registry: api/generator/ecr.md
|
||||
- AWS STS Session Token: api/generator/sts.md
|
||||
- Cluster Generator: api/generator/cluster.md
|
||||
- Google Container Registry: api/generator/gcr.md
|
||||
- Vault Dynamic Secret: api/generator/vault.md
|
||||
- Password: api/generator/password.md
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
// Loading registered providers.
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
|
@ -116,6 +115,8 @@ func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
||||
}
|
||||
// We still pass the namespace to the generate function because it needs to create
|
||||
// namespace based objects.
|
||||
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGenerate, i, err)
|
||||
|
|
|
@ -650,6 +650,45 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
|||
Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
|
||||
}
|
||||
}
|
||||
syncWithClusterGeneratorRef := func(tc *testCase) {
|
||||
const secretKey = "somekey2"
|
||||
const secretVal = "someValue2"
|
||||
Expect(k8sClient.Create(context.Background(), &genv1alpha1.ClusterGenerator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mytestfake",
|
||||
},
|
||||
Spec: genv1alpha1.ClusterGeneratorSpec{
|
||||
Kind: "Fake",
|
||||
Generator: genv1alpha1.GeneratorSpec{
|
||||
FakeSpec: &genv1alpha1.FakeSpec{
|
||||
Data: map[string]string{
|
||||
secretKey: secretVal,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})).To(Succeed())
|
||||
|
||||
// reset secretStoreRef
|
||||
tc.externalSecret.Spec.SecretStoreRef = esv1beta1.SecretStoreRef{}
|
||||
tc.externalSecret.Spec.Data = nil
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
SourceRef: &esv1beta1.StoreGeneratorSourceRef{
|
||||
GeneratorRef: &esv1beta1.GeneratorRef{
|
||||
APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
|
||||
Kind: "ClusterGenerator",
|
||||
Name: "mytestfake",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||
// check values
|
||||
Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
|
||||
}
|
||||
}
|
||||
|
||||
deleteOrphanedSecrets := func(tc *testCase) {
|
||||
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||
|
@ -2280,6 +2319,7 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
|||
Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict),
|
||||
Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange),
|
||||
Entry("should not delete pre-existing secret with creationPolicy=Orphan", createSecretPolicyOrphan),
|
||||
Entry("should sync cluster generator ref", syncWithClusterGeneratorRef),
|
||||
Entry("should sync with generatorRef", syncWithGeneratorRef),
|
||||
Entry("should not process generatorRef with mismatching controller field", ignoreMismatchControllerForGeneratorRef),
|
||||
Entry("should sync with multiple secret stores via sourceRef", syncWithMultipleSecretStores),
|
||||
|
|
|
@ -11,6 +11,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
|
@ -18,8 +19,10 @@ import (
|
|||
"fmt"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
|
@ -70,15 +73,90 @@ func getGeneratorDefinition(ctx context.Context, restConfig *rest.Config, namesp
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := d.Resource(mapping.Resource).
|
||||
Namespace(namespace).
|
||||
Get(ctx, generatorRef.Name, metav1.GetOptions{})
|
||||
|
||||
if generatorRef.Kind == "ClusterGenerator" {
|
||||
return extractGeneratorFromClusterGenerator(ctx, d, mapping, generatorRef)
|
||||
}
|
||||
|
||||
res, err := d.Resource(mapping.Resource).Namespace(namespace).Get(ctx, generatorRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsonRes, err := res.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiextensions.JSON{Raw: jsonRes}, nil
|
||||
}
|
||||
|
||||
func extractGeneratorFromClusterGenerator(
|
||||
ctx context.Context,
|
||||
d *dynamic.DynamicClient,
|
||||
mapping *meta.RESTMapping,
|
||||
generatorRef *esv1beta1.GeneratorRef,
|
||||
) (*apiextensions.JSON, error) {
|
||||
res, err := d.Resource(mapping.Resource).Get(ctx, generatorRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec, err := extractValue[map[string]any](res.Object, genv1alpha1.GeneratorSpecKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
generator, err := extractValue[map[string]any](spec, genv1alpha1.GeneratorGeneratorKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kind, err := extractValue[string](spec, genv1alpha1.GeneratorKindKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// find the first value and that's what we are going to take
|
||||
// this will be the generator that has been set by the user
|
||||
var result []byte
|
||||
for _, v := range generator {
|
||||
vMap, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("kind was not of object type for cluster generator %T", v)
|
||||
}
|
||||
|
||||
// Construct our generator object so it can be later unmarshalled into a valid Generator Spec.
|
||||
object := map[string]interface{}{}
|
||||
object["kind"] = kind
|
||||
object["spec"] = vMap
|
||||
result, err = json.Marshal(object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apiextensions.JSON{Raw: result}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no defined generators found for cluster generator spec: %v", spec)
|
||||
}
|
||||
|
||||
// extractValue fetches a specific key value that we are looking for in a map.
|
||||
func extractValue[T any](m any, k string) (T, error) {
|
||||
var result T
|
||||
v, ok := m.(map[string]any)
|
||||
if !ok {
|
||||
return result, fmt.Errorf("value was not of type map[string]any but: %T", m)
|
||||
}
|
||||
|
||||
vv, ok := v[k]
|
||||
if !ok {
|
||||
return result, fmt.Errorf("key %s was not found in map", k)
|
||||
}
|
||||
|
||||
vvv, ok := vv.(T)
|
||||
if !ok {
|
||||
return result, fmt.Errorf("value was not of type T but: %T", vvv)
|
||||
}
|
||||
|
||||
return vvv, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue