1
0
Fork 0
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:
Gergely Brautigam 2024-11-26 15:32:26 +01:00 committed by GitHub
parent 40a698dafd
commit fb9526f38a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 3220 additions and 20 deletions

View file

@ -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"`

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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{})
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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. |

View file

@ -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"

View file

@ -270,6 +270,9 @@
"createClusterExternalSecret": {
"type": "boolean"
},
"createClusterGenerator": {
"type": "boolean"
},
"createClusterSecretStore": {
"type": "boolean"
},

View file

@ -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

View 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' %}
```

View file

@ -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>

View file

@ -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"`
}
```

View 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"

View 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

View file

@ -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

View file

@ -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)

View file

@ -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),

View file

@ -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
}