mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: allow generators to be referenced from a PushSecret (#3965)
This removes the need for an intermediary Kind=ExternalSecret and Kind=Secret when using a generator. Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
parent
f67b935116
commit
76cf8ad263
15 changed files with 289 additions and 83 deletions
|
@ -92,9 +92,16 @@ type PushSecretSecret struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:MinProperties=1
|
||||
// +kubebuilder:validation:MaxProperties=1
|
||||
type PushSecretSelector struct {
|
||||
// Select a Secret to Push.
|
||||
Secret PushSecretSecret `json:"secret"`
|
||||
// +optional
|
||||
Secret *PushSecretSecret `json:"secret,omitempty"`
|
||||
|
||||
// Point to a generator to create a Secret.
|
||||
// +optional
|
||||
GeneratorRef *esv1beta1.GeneratorRef `json:"generatorRef,omitempty"`
|
||||
}
|
||||
|
||||
type PushSecretRemoteRef struct {
|
||||
|
|
|
@ -1208,7 +1208,16 @@ func (in *PushSecretSecret) DeepCopy() *PushSecretSecret {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretSelector) DeepCopyInto(out *PushSecretSelector) {
|
||||
*out = *in
|
||||
out.Secret = in.Secret
|
||||
if in.Secret != nil {
|
||||
in, out := &in.Secret, &out.Secret
|
||||
*out = new(PushSecretSecret)
|
||||
**out = **in
|
||||
}
|
||||
if in.GeneratorRef != nil {
|
||||
in, out := &in.GeneratorRef, &out.GeneratorRef
|
||||
*out = new(v1beta1.GeneratorRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretSelector.
|
||||
|
@ -1236,7 +1245,7 @@ func (in *PushSecretSpec) DeepCopyInto(out *PushSecretSpec) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
out.Selector = in.Selector
|
||||
in.Selector.DeepCopyInto(&out.Selector)
|
||||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make([]PushSecretData, len(*in))
|
||||
|
|
|
@ -216,6 +216,7 @@ var rootCmd = &cobra.Command{
|
|||
Log: ctrl.Log.WithName("controllers").WithName("PushSecret"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
ControllerClass: controllerClass,
|
||||
RestConfig: mgr.GetConfig(),
|
||||
RequeueInterval: time.Hour,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, errCreateController, "controller", "PushSecret")
|
||||
|
|
|
@ -165,7 +165,27 @@ spec:
|
|||
type: array
|
||||
selector:
|
||||
description: The Secret Selector (k8s source) for the Push Secret
|
||||
maxProperties: 1
|
||||
minProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: Point to a generator to create a Secret.
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password,
|
||||
ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
secret:
|
||||
description: Select a Secret to Push.
|
||||
properties:
|
||||
|
@ -176,8 +196,6 @@ spec:
|
|||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- secret
|
||||
type: object
|
||||
template:
|
||||
description: Template defines a blueprint for the created Secret resource.
|
||||
|
|
|
@ -6258,7 +6258,26 @@ spec:
|
|||
type: array
|
||||
selector:
|
||||
description: The Secret Selector (k8s source) for the Push Secret
|
||||
maxProperties: 1
|
||||
minProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: Point to a generator to create a Secret.
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
secret:
|
||||
description: Select a Secret to Push.
|
||||
properties:
|
||||
|
@ -6268,8 +6287,6 @@ spec:
|
|||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- secret
|
||||
type: object
|
||||
template:
|
||||
description: Template defines a blueprint for the created Secret resource.
|
||||
|
|
|
@ -43,3 +43,12 @@ This will _marshal_ the entire secret data and push it into this single property
|
|||
|
||||
### Key conversion strategy
|
||||
You can also set `data[*].conversionStrategy: ReverseUnicode` to reverse the invalid character replaced by the `conversionStrategy: Unicode` configuration in the `ExternalSecret` object as [documented here](../guides/getallsecrets.md#avoiding-name-conflicts).
|
||||
|
||||
## Rotate Secrets
|
||||
|
||||
You can use ESO to rotate secrets by using the PushSecret and Generator resources. ESO will consult the `Kind=Generator` to generate a new secret and then ESO will store it.
|
||||
Every `spec.refreshInterval` the secret will be rotated and the value will be replaced in the store unless `spec.updatePolicy=IfNotExist` is set. Then ESO will generate the secret once and won't rotate it.
|
||||
|
||||
```yaml
|
||||
{% include 'pushsecret-generator-rotation-example.yaml' %}
|
||||
```
|
||||
|
|
|
@ -14,6 +14,11 @@ spec:
|
|||
selector:
|
||||
secret:
|
||||
name: pokedex-credentials # Source Kubernetes secret to be pushed
|
||||
# Alternatively, you can point to a generator that produces values to be pushed
|
||||
generatorRef:
|
||||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: ECRAuthorizationToken
|
||||
name: prod-registry-credentials
|
||||
template:
|
||||
metadata:
|
||||
annotations: { }
|
||||
|
|
33
docs/snippets/pushsecret-generator-rotation-example.yaml
Normal file
33
docs/snippets/pushsecret-generator-rotation-example.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
{% raw %}
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: strong-password
|
||||
spec:
|
||||
length: 128
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: PushSecret
|
||||
metadata:
|
||||
name: pushsecret-example
|
||||
spec:
|
||||
refreshInterval: 6h
|
||||
secretStoreRefs:
|
||||
- name: aws-parameter-store
|
||||
kind: SecretStore
|
||||
selector:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: strong-password
|
||||
data:
|
||||
- match:
|
||||
secretKey: password # property in the generator output
|
||||
remoteRef:
|
||||
remoteKey: prod/myql/password
|
||||
{% endraw %}
|
|
@ -133,7 +133,7 @@ func genericPushSecretTemplate(f *framework.Framework) (string, func(*framework.
|
|||
Type: v1.SecretTypeOpaque,
|
||||
}
|
||||
tc.PushSecret.Spec.Selector = esv1alpha1.PushSecretSelector{
|
||||
Secret: esv1alpha1.PushSecretSecret{
|
||||
Secret: &esv1alpha1.PushSecretSecret{
|
||||
Name: secretKey1,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import (
|
|||
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
|
||||
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
|
||||
// Loading registered generators.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||
|
@ -549,11 +550,11 @@ func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconcil
|
|||
|
||||
// verify that generator's controllerClass matches
|
||||
if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
|
||||
genDef, err := r.getGeneratorDefinition(ctx, namespace, ref.SourceRef.GeneratorRef)
|
||||
_, obj, err := resolvers.GeneratorRef(ctx, r.RestConfig, namespace, ref.SourceRef.GeneratorRef)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
skipGenerator, err := shouldSkipGenerator(r, genDef)
|
||||
skipGenerator, err := shouldSkipGenerator(r, obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -22,17 +22,13 @@ import (
|
|||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/restmapper"
|
||||
|
||||
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"
|
||||
|
||||
// Loading registered generators.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||
|
@ -116,15 +112,11 @@ func toStoreGenSourceRef(ref *esv1beta1.StoreSourceRef) *esv1beta1.StoreGenerato
|
|||
}
|
||||
|
||||
func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
|
||||
genDef, err := r.getGeneratorDefinition(ctx, namespace, remoteRef.SourceRef.GeneratorRef)
|
||||
gen, obj, err := resolvers.GeneratorRef(ctx, r.RestConfig, namespace, remoteRef.SourceRef.GeneratorRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
||||
}
|
||||
gen, err := genv1alpha1.GetGenerator(genDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err := gen.Generate(ctx, genDef, r.Client, namespace)
|
||||
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGenerate, i, err)
|
||||
}
|
||||
|
@ -138,49 +130,6 @@ func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string
|
|||
return secretMap, err
|
||||
}
|
||||
|
||||
// getGeneratorDefinition returns the generator JSON for a given sourceRef
|
||||
// when it uses a generatorRef it fetches the resource and returns the JSON.
|
||||
func (r *Reconciler) getGeneratorDefinition(ctx context.Context, namespace string, generatorRef *esv1beta1.GeneratorRef) (*apiextensions.JSON, error) {
|
||||
// client-go dynamic client needs a GVR to fetch the resource
|
||||
// But we only have the GVK in our generatorRef.
|
||||
//
|
||||
// TODO: there is no need to discover the GroupVersionResource
|
||||
// this should be cached.
|
||||
c := discovery.NewDiscoveryClientForConfigOrDie(r.RestConfig)
|
||||
groupResources, err := restmapper.GetAPIGroupResources(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gv, err := schema.ParseGroupVersion(generatorRef.APIVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||
mapping, err := mapper.RESTMapping(schema.GroupKind{
|
||||
Group: gv.Group,
|
||||
Kind: generatorRef.Kind,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := dynamic.NewForConfig(r.RestConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 (r *Reconciler) handleExtractSecrets(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, cmgr *secretstore.Manager, i int) (map[string][]byte, error) {
|
||||
client, err := cmgr.Get(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
|
||||
if err != nil {
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
@ -40,6 +41,10 @@ import (
|
|||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/util/locks"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
|
||||
// load generators.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -59,6 +64,7 @@ type Reconciler struct {
|
|||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
recorder record.EventRecorder
|
||||
RestConfig *rest.Config
|
||||
RequeueInterval time.Duration
|
||||
ControllerClass string
|
||||
}
|
||||
|
@ -148,7 +154,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
default:
|
||||
}
|
||||
|
||||
secret, err := r.GetSecret(ctx, ps)
|
||||
secret, err := r.resolveSecret(ctx, ps)
|
||||
if err != nil {
|
||||
r.markAsFailed(errFailedGetSecret, &ps, nil)
|
||||
|
||||
|
@ -347,14 +353,38 @@ func secretKeyExists(key string, secret *v1.Secret) bool {
|
|||
return key == "" || ok
|
||||
}
|
||||
|
||||
func (r *Reconciler) GetSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
|
||||
secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
|
||||
secret := &v1.Secret{}
|
||||
err := r.Client.Get(ctx, secretName, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (r *Reconciler) resolveSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
|
||||
if ps.Spec.Selector.Secret != nil {
|
||||
secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
|
||||
secret := &v1.Secret{}
|
||||
err := r.Client.Get(ctx, secretName, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
return secret, nil
|
||||
if ps.Spec.Selector.GeneratorRef != nil {
|
||||
return r.resolveSecretFromGenerator(ctx, ps.Namespace, ps.Spec.Selector.GeneratorRef)
|
||||
}
|
||||
return nil, errors.New("no secret selector provided")
|
||||
}
|
||||
|
||||
func (r *Reconciler) resolveSecretFromGenerator(ctx context.Context, namespace string, generatorRef *v1beta1.GeneratorRef) (*v1.Secret, error) {
|
||||
gen, obj, err := resolvers.GeneratorRef(ctx, r.RestConfig, namespace, generatorRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
||||
}
|
||||
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate: %w", err)
|
||||
}
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "___generated-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: secretMap,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) (map[esapi.PushSecretStoreRef]v1beta1.GenericStore, error) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/psmetrics"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
||||
|
@ -99,6 +100,21 @@ var _ = Describe("PushSecret controller", func() {
|
|||
PushSecretNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
fakeProvider.Reset()
|
||||
|
||||
Expect(k8sClient.Create(context.Background(), &genv1alpha1.Fake{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Fake",
|
||||
APIVersion: "generators.external-secrets.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: PushSecretNamespace,
|
||||
},
|
||||
Spec: genv1alpha1.FakeSpec{
|
||||
Data: map[string]string{
|
||||
"key": "foo-bar-from-generator",
|
||||
},
|
||||
}})).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -162,7 +178,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -395,7 +411,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -459,7 +475,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -515,7 +531,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -570,7 +586,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -716,7 +732,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -782,7 +798,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -861,6 +877,28 @@ var _ = Describe("PushSecret controller", func() {
|
|||
return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
|
||||
}
|
||||
}
|
||||
|
||||
syncWithGenerator := func(tc *testCase) {
|
||||
fakeProvider.SetSecretFn = func() error {
|
||||
return nil
|
||||
}
|
||||
tc.pushsecret.Spec.Selector.Secret = nil
|
||||
tc.pushsecret.Spec.Selector.GeneratorRef = &v1beta1.GeneratorRef{
|
||||
APIVersion: "generators.external-secrets.io/v1alpha1",
|
||||
Kind: "Fake",
|
||||
Name: "test",
|
||||
}
|
||||
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
||||
providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
|
||||
expected := v1alpha1.PushSecretStatusCondition{
|
||||
Type: v1alpha1.PushSecretReady,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: v1alpha1.ReasonSynced,
|
||||
Message: "PushSecret synced successfully",
|
||||
}
|
||||
return bytes.Equal([]byte("foo-bar-from-generator"), providerValue) && checkCondition(ps.Status, expected)
|
||||
}
|
||||
}
|
||||
// if target Secret name is not specified it should use the ExternalSecret name.
|
||||
syncWithClusterStoreMatchingLabels := func(tc *testCase) {
|
||||
fakeProvider.SetSecretFn = func() error {
|
||||
|
@ -884,7 +922,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
@ -1069,6 +1107,7 @@ var _ = Describe("PushSecret controller", func() {
|
|||
Entry("should sync to stores matching labels", syncMatchingLabels),
|
||||
Entry("should sync with ClusterStore", syncWithClusterStore),
|
||||
Entry("should sync with ClusterStore matching labels", syncWithClusterStoreMatchingLabels),
|
||||
Entry("should sync with Generator", syncWithGenerator),
|
||||
Entry("should fail if Secret is not created", failNoSecret),
|
||||
Entry("should fail if Secret Key does not exist", failNoSecretKey),
|
||||
Entry("should fail if SetSecret fails", setSecretFail),
|
||||
|
@ -1168,7 +1207,7 @@ var _ = Describe("PushSecret Controller Un/Managed Stores", func() {
|
|||
},
|
||||
},
|
||||
Selector: v1alpha1.PushSecretSelector{
|
||||
Secret: v1alpha1.PushSecretSecret{
|
||||
Secret: &v1alpha1.PushSecretSecret{
|
||||
Name: SecretName,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -72,6 +73,8 @@ var _ = BeforeSuite(func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
err = esv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = genv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
|
@ -90,7 +93,8 @@ var _ = BeforeSuite(func() {
|
|||
err = (&Reconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ExternalSecrets"),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("PushSecret"),
|
||||
RestConfig: cfg,
|
||||
RequeueInterval: time.Second,
|
||||
}).SetupWithManager(k8sManager)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
|
84
pkg/utils/resolvers/generator.go
Normal file
84
pkg/utils/resolvers/generator.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
)
|
||||
|
||||
// GeneratorRef resolves a generator reference to a generator implementation.
|
||||
func GeneratorRef(ctx context.Context, restConfig *rest.Config, namespace string, generatorRef *esv1beta1.GeneratorRef) (genv1alpha1.Generator, *apiextensions.JSON, error) {
|
||||
obj, err := getGeneratorDefinition(ctx, restConfig, namespace, generatorRef)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get generator definition: %w", err)
|
||||
}
|
||||
generator, err := genv1alpha1.GetGenerator(obj)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get generator: %w", err)
|
||||
}
|
||||
return generator, obj, nil
|
||||
}
|
||||
|
||||
func getGeneratorDefinition(ctx context.Context, restConfig *rest.Config, namespace string, generatorRef *esv1beta1.GeneratorRef) (*apiextensions.JSON, error) {
|
||||
// client-go dynamic client needs a GVR to fetch the resource
|
||||
// But we only have the GVK in our generatorRef.
|
||||
//
|
||||
// TODO: there is no need to discover the GroupVersionResource
|
||||
// this should be cached.
|
||||
c := discovery.NewDiscoveryClientForConfigOrDie(restConfig)
|
||||
groupResources, err := restmapper.GetAPIGroupResources(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gv, err := schema.ParseGroupVersion(generatorRef.APIVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||
mapping, err := mapper.RESTMapping(schema.GroupKind{
|
||||
Group: gv.Group,
|
||||
Kind: generatorRef.Kind,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := dynamic.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
Loading…
Reference in a new issue