1
0
Fork 0
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:
Moritz Johner 2024-10-02 08:43:00 +02:00 committed by GitHub
parent f67b935116
commit 76cf8ad263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 289 additions and 83 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

@ -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,7 +353,8 @@ func secretKeyExists(key string, secret *v1.Secret) bool {
return key == "" || ok
}
func (r *Reconciler) GetSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
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)
@ -356,6 +363,29 @@ func (r *Reconciler) GetSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Se
}
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) {
stores := make(map[esapi.PushSecretStoreRef]v1beta1.GenericStore)

View file

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

View file

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

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