server.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package generator
  13. import (
  14. "context"
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/runtime/schema"
  23. "k8s.io/apimachinery/pkg/types"
  24. "sigs.k8s.io/controller-runtime/pkg/client"
  25. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  26. genpb "github.com/external-secrets/external-secrets/proto/generator"
  27. )
  28. // Server wraps v1 providers and generators and exposes them as v2 gRPC services.
  29. // This allows existing v1 provider and generator implementations to be used in the v2 architecture.
  30. type Server struct {
  31. genpb.UnimplementedGeneratorProviderServer
  32. kubeClient client.Client
  33. scheme *runtime.Scheme
  34. // we support multiple v1 generators, so we need to map the v2 generator
  35. // with apiVersion+kind to the corresponding v1 generator
  36. generatorMapping GeneratorMapping
  37. }
  38. // GeneratorMapping maps Kubernetes resources to their generator implementations.
  39. type GeneratorMapping map[schema.GroupVersionKind]genv1alpha1.Generator
  40. // NewServer creates a new generator adapter server.
  41. func NewServer(kubeClient client.Client, scheme *runtime.Scheme, generatorMapping GeneratorMapping) *Server {
  42. return &Server{
  43. kubeClient: kubeClient,
  44. scheme: scheme,
  45. generatorMapping: generatorMapping,
  46. }
  47. }
  48. // Generate creates a new secret or set of secrets using a v1 generator.
  49. func (s *Server) Generate(ctx context.Context, req *genpb.GenerateRequest) (*genpb.GenerateResponse, error) {
  50. if req == nil || req.GeneratorRef == nil {
  51. return nil, fmt.Errorf("request or generator ref is nil")
  52. }
  53. // Resolve the generator implementation and resource
  54. generator, jsonObj, err := s.getGenerator(ctx, req.GeneratorRef, req.SourceNamespace)
  55. if err != nil {
  56. return nil, fmt.Errorf("failed to get generator: %w", err)
  57. }
  58. // Call the v1 generator's Generate method
  59. secretMap, state, err := generator.Generate(ctx, jsonObj, s.kubeClient, req.SourceNamespace)
  60. if err != nil {
  61. return nil, fmt.Errorf("failed to generate secrets: %w", err)
  62. }
  63. // Convert state to bytes
  64. var stateBytes []byte
  65. if state != nil {
  66. stateBytes = state.Raw
  67. }
  68. return &genpb.GenerateResponse{
  69. Secrets: secretMap,
  70. State: stateBytes,
  71. }, nil
  72. }
  73. // Cleanup deletes any resources created during the Generate phase.
  74. func (s *Server) Cleanup(ctx context.Context, req *genpb.CleanupRequest) (*genpb.CleanupResponse, error) {
  75. if req == nil || req.GeneratorRef == nil {
  76. return nil, fmt.Errorf("request or generator ref is nil")
  77. }
  78. // Resolve the generator implementation and resource
  79. generator, jsonObj, err := s.getGenerator(ctx, req.GeneratorRef, req.SourceNamespace)
  80. if err != nil {
  81. return nil, fmt.Errorf("failed to get generator: %w", err)
  82. }
  83. // Unmarshal the state
  84. var state genv1alpha1.GeneratorProviderState
  85. if len(req.State) > 0 {
  86. state = &apiextensions.JSON{Raw: req.State}
  87. }
  88. // Call the v1 generator's Cleanup method
  89. err = generator.Cleanup(ctx, jsonObj, state, s.kubeClient, req.SourceNamespace)
  90. if err != nil {
  91. return nil, fmt.Errorf("failed to cleanup generator resources: %w", err)
  92. }
  93. return &genpb.CleanupResponse{}, nil
  94. }
  95. // getGenerator retrieves a generator implementation and the generator resource.
  96. // This is similar to runtime/esutils/resolvers/generator.go getGenerator function.
  97. func (s *Server) getGenerator(ctx context.Context, generatorRef *genpb.GeneratorRef, namespace string) (genv1alpha1.Generator, *apiextensions.JSON, error) {
  98. if generatorRef == nil {
  99. return nil, nil, fmt.Errorf("generator reference is nil")
  100. }
  101. // Parse the API version
  102. splitted := strings.Split(generatorRef.ApiVersion, "/")
  103. if len(splitted) != 2 {
  104. return nil, nil, fmt.Errorf("invalid api version: %s", generatorRef.ApiVersion)
  105. }
  106. group := splitted[0]
  107. version := splitted[1]
  108. // Construct the GVK
  109. gvk := schema.GroupVersionKind{
  110. Group: group,
  111. Version: version,
  112. Kind: generatorRef.Kind,
  113. }
  114. // Fail if the GVK does not use the generator group
  115. if gvk.Group != genv1alpha1.Group {
  116. return nil, nil, fmt.Errorf("generatorRef may only reference the generators group, but got %s", gvk.Group)
  117. }
  118. // Lookup the generator implementation from the mapping
  119. generator, ok := s.generatorMapping[gvk]
  120. if !ok {
  121. return nil, nil, fmt.Errorf("generator mapping not found for %q", gvk)
  122. }
  123. // Get a client Object from the GVK
  124. t, exists := s.scheme.AllKnownTypes()[gvk]
  125. if !exists {
  126. return nil, nil, fmt.Errorf("generatorRef references unknown GVK %s", gvk)
  127. }
  128. obj := reflect.New(t).Interface().(client.Object)
  129. // Handle ClusterGenerator specially
  130. if gvk.Kind == genv1alpha1.ClusterGeneratorKind {
  131. clusterGenerator := obj.(*genv1alpha1.ClusterGenerator)
  132. // Get the cluster generator resource from the API
  133. err := s.kubeClient.Get(ctx, client.ObjectKey{Name: generatorRef.Name}, clusterGenerator)
  134. if err != nil {
  135. return nil, nil, fmt.Errorf("failed to get ClusterGenerator: %w", err)
  136. }
  137. // Convert the cluster generator to a virtual namespaced generator object
  138. obj, err = s.clusterGeneratorToVirtual(clusterGenerator)
  139. if err != nil {
  140. return nil, nil, fmt.Errorf("invalid ClusterGenerator: %w", err)
  141. }
  142. } else {
  143. // Get the generator resource from the API
  144. nsName := types.NamespacedName{
  145. Name: generatorRef.Name,
  146. Namespace: namespace,
  147. }
  148. // Use the namespace from the ref if provided (for flexibility)
  149. if generatorRef.Namespace != "" {
  150. nsName.Namespace = generatorRef.Namespace
  151. }
  152. err := s.kubeClient.Get(ctx, nsName, obj)
  153. if err != nil {
  154. return nil, nil, fmt.Errorf("failed to get generator resource: %w", err)
  155. }
  156. }
  157. // Convert the generator to unstructured object
  158. u := &unstructured.Unstructured{}
  159. var unstructuredObj map[string]interface{}
  160. var err error
  161. unstructuredObj, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  162. if err != nil {
  163. return nil, nil, fmt.Errorf("failed to convert to unstructured: %w", err)
  164. }
  165. u.Object = unstructuredObj
  166. // Convert the unstructured object to JSON
  167. jsonObj, err := u.MarshalJSON()
  168. if err != nil {
  169. return nil, nil, fmt.Errorf("failed to marshal JSON: %w", err)
  170. }
  171. return generator, &apiextensions.JSON{Raw: jsonObj}, nil
  172. }
  173. // clusterGeneratorToVirtual converts a ClusterGenerator to a virtual namespaced generator.
  174. // This is adapted from runtime/esutils/resolvers/generator.go.
  175. func (s *Server) clusterGeneratorToVirtual(gen *genv1alpha1.ClusterGenerator) (client.Object, error) {
  176. switch gen.Spec.Kind {
  177. case genv1alpha1.GeneratorKindFake:
  178. if gen.Spec.Generator.FakeSpec == nil {
  179. return nil, fmt.Errorf("when kind is %s, FakeSpec must be set", gen.Spec.Kind)
  180. }
  181. return &genv1alpha1.Fake{
  182. TypeMeta: metav1.TypeMeta{
  183. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  184. Kind: genv1alpha1.FakeKind,
  185. },
  186. ObjectMeta: metav1.ObjectMeta{
  187. Name: gen.Name,
  188. },
  189. Spec: *gen.Spec.Generator.FakeSpec,
  190. }, nil
  191. // Add more generator kinds here as needed
  192. default:
  193. return nil, fmt.Errorf("unsupported generator kind: %s", gen.Spec.Kind)
  194. }
  195. }