| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /*
- Copyright © The ESO Authors
- 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
- https://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 generator
- import (
- "context"
- "fmt"
- "reflect"
- "strings"
- apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/types"
- "sigs.k8s.io/controller-runtime/pkg/client"
- genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
- genpb "github.com/external-secrets/external-secrets/proto/generator"
- )
- // Server wraps v1 providers and generators and exposes them as v2 gRPC services.
- // This allows existing v1 provider and generator implementations to be used in the v2 architecture.
- type Server struct {
- genpb.UnimplementedGeneratorProviderServer
- kubeClient client.Client
- scheme *runtime.Scheme
- // we support multiple v1 generators, so we need to map the v2 generator
- // with apiVersion+kind to the corresponding v1 generator
- generatorMapping Mapping
- }
- // Mapping maps Kubernetes resources to their generator implementations.
- type Mapping map[schema.GroupVersionKind]genv1alpha1.Generator
- // NewServer creates a new generator adapter server.
- func NewServer(kubeClient client.Client, scheme *runtime.Scheme, generatorMapping Mapping) *Server {
- return &Server{
- kubeClient: kubeClient,
- scheme: scheme,
- generatorMapping: generatorMapping,
- }
- }
- // Generate creates a new secret or set of secrets using a v1 generator.
- func (s *Server) Generate(ctx context.Context, req *genpb.GenerateRequest) (*genpb.GenerateResponse, error) {
- if req == nil || req.GeneratorRef == nil {
- return nil, fmt.Errorf("request or generator ref is nil")
- }
- // Resolve the generator implementation and resource
- generator, jsonObj, err := s.getGenerator(ctx, req.GeneratorRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get generator: %w", err)
- }
- // Call the v1 generator's Generate method
- secretMap, state, err := generator.Generate(ctx, jsonObj, s.kubeClient, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to generate secrets: %w", err)
- }
- // Convert state to bytes
- var stateBytes []byte
- if state != nil {
- stateBytes = state.Raw
- }
- return &genpb.GenerateResponse{
- Secrets: secretMap,
- State: stateBytes,
- }, nil
- }
- // Cleanup deletes any resources created during the Generate phase.
- func (s *Server) Cleanup(ctx context.Context, req *genpb.CleanupRequest) (*genpb.CleanupResponse, error) {
- if req == nil || req.GeneratorRef == nil {
- return nil, fmt.Errorf("request or generator ref is nil")
- }
- // Resolve the generator implementation and resource
- generator, jsonObj, err := s.getGenerator(ctx, req.GeneratorRef, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to get generator: %w", err)
- }
- // Unmarshal the state
- var state genv1alpha1.GeneratorProviderState
- if len(req.State) > 0 {
- state = &apiextensions.JSON{Raw: req.State}
- }
- // Call the v1 generator's Cleanup method
- err = generator.Cleanup(ctx, jsonObj, state, s.kubeClient, req.SourceNamespace)
- if err != nil {
- return nil, fmt.Errorf("failed to cleanup generator resources: %w", err)
- }
- return &genpb.CleanupResponse{}, nil
- }
- // getGenerator retrieves a generator implementation and the generator resource.
- // This is similar to runtime/esutils/resolvers/generator.go getGenerator function.
- func (s *Server) getGenerator(ctx context.Context, generatorRef *genpb.GeneratorRef, namespace string) (genv1alpha1.Generator, *apiextensions.JSON, error) {
- if generatorRef == nil {
- return nil, nil, fmt.Errorf("generator reference is nil")
- }
- // Parse the API version
- splitted := strings.Split(generatorRef.ApiVersion, "/")
- if len(splitted) != 2 {
- return nil, nil, fmt.Errorf("invalid api version: %s", generatorRef.ApiVersion)
- }
- group := splitted[0]
- version := splitted[1]
- // Construct the GVK
- gvk := schema.GroupVersionKind{
- Group: group,
- Version: version,
- Kind: generatorRef.Kind,
- }
- // Fail if the GVK does not use the generator group
- if gvk.Group != genv1alpha1.Group {
- return nil, nil, fmt.Errorf("generatorRef may only reference the generators group, but got %s", gvk.Group)
- }
- // Lookup the generator implementation from the mapping
- generator, ok := s.generatorMapping[gvk]
- if !ok {
- return nil, nil, fmt.Errorf("generator mapping not found for %q", gvk)
- }
- // Get a client Object from the GVK
- t, exists := s.scheme.AllKnownTypes()[gvk]
- if !exists {
- return nil, nil, fmt.Errorf("generatorRef references unknown GVK %s", gvk)
- }
- obj := reflect.New(t).Interface().(client.Object)
- // Handle ClusterGenerator specially
- if gvk.Kind == genv1alpha1.ClusterGeneratorKind {
- clusterGenerator := obj.(*genv1alpha1.ClusterGenerator)
- // Get the cluster generator resource from the API
- err := s.kubeClient.Get(ctx, client.ObjectKey{Name: generatorRef.Name}, clusterGenerator)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to get ClusterGenerator: %w", err)
- }
- // Convert the cluster generator to a virtual namespaced generator object
- obj, err = s.clusterGeneratorToVirtual(clusterGenerator)
- if err != nil {
- return nil, nil, fmt.Errorf("invalid ClusterGenerator: %w", err)
- }
- } else {
- // Get the generator resource from the API
- nsName := types.NamespacedName{
- Name: generatorRef.Name,
- Namespace: namespace,
- }
- // Use the namespace from the ref if provided (for flexibility)
- if generatorRef.Namespace != "" {
- nsName.Namespace = generatorRef.Namespace
- }
- err := s.kubeClient.Get(ctx, nsName, obj)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to get generator resource: %w", err)
- }
- }
- // Convert the generator to unstructured object
- u := &unstructured.Unstructured{}
- var unstructuredObj map[string]any
- var err error
- unstructuredObj, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to convert to unstructured: %w", err)
- }
- u.Object = unstructuredObj
- // Convert the unstructured object to JSON
- jsonObj, err := u.MarshalJSON()
- if err != nil {
- return nil, nil, fmt.Errorf("failed to marshal JSON: %w", err)
- }
- return generator, &apiextensions.JSON{Raw: jsonObj}, nil
- }
- // clusterGeneratorToVirtual converts a ClusterGenerator to a virtual namespaced generator.
- // This is adapted from runtime/esutils/resolvers/generator.go.
- func (s *Server) clusterGeneratorToVirtual(gen *genv1alpha1.ClusterGenerator) (client.Object, error) {
- switch gen.Spec.Kind {
- case genv1alpha1.GeneratorKindFake:
- if gen.Spec.Generator.FakeSpec == nil {
- return nil, fmt.Errorf("when kind is %s, FakeSpec must be set", gen.Spec.Kind)
- }
- return &genv1alpha1.Fake{
- TypeMeta: metav1.TypeMeta{
- APIVersion: genv1alpha1.SchemeGroupVersion.String(),
- Kind: genv1alpha1.FakeKind,
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: gen.Name,
- },
- Spec: *gen.Spec.Generator.FakeSpec,
- }, nil
- case genv1alpha1.GeneratorKindACRAccessToken,
- genv1alpha1.GeneratorKindCloudsmithAccessToken,
- genv1alpha1.GeneratorKindECRAuthorizationToken,
- genv1alpha1.GeneratorKindGCRAccessToken,
- genv1alpha1.GeneratorKindGithubAccessToken,
- genv1alpha1.GeneratorKindGrafana,
- genv1alpha1.GeneratorKindMFA,
- genv1alpha1.GeneratorKindPassword,
- genv1alpha1.GeneratorKindQuayAccessToken,
- genv1alpha1.GeneratorKindSSHKey,
- genv1alpha1.GeneratorKindSTSSessionToken,
- genv1alpha1.GeneratorKindUUID,
- genv1alpha1.GeneratorKindVaultDynamicSecret,
- genv1alpha1.GeneratorKindWebhook:
- return nil, fmt.Errorf("unsupported generator kind: %s", gen.Spec.Kind)
- default:
- return nil, fmt.Errorf("unsupported generator kind: %s", gen.Spec.Kind)
- }
- }
|