generator.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package resolvers
  14. import (
  15. "context"
  16. "fmt"
  17. "reflect"
  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. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  28. )
  29. // these errors are explicitly defined so we can detect them with `errors.Is()`.
  30. var (
  31. // ErrUnableToGetGenerator is returned when a generator reference cannot be resolved.
  32. ErrUnableToGetGenerator = fmt.Errorf("unable to get generator")
  33. )
  34. // GeneratorRef resolves a generator reference to a generator implementation.
  35. func GeneratorRef(ctx context.Context, cl client.Client, scheme *runtime.Scheme, namespace string, generatorRef *esv1.GeneratorRef) (genv1alpha1.Generator, *apiextensions.JSON, error) {
  36. generator, jsonObj, err := getGenerator(ctx, cl, scheme, namespace, generatorRef)
  37. if err != nil {
  38. return nil, nil, fmt.Errorf("%w: %w", ErrUnableToGetGenerator, err)
  39. }
  40. return generator, jsonObj, nil
  41. }
  42. func getGenerator(ctx context.Context, cl client.Client, scheme *runtime.Scheme, namespace string, generatorRef *esv1.GeneratorRef) (genv1alpha1.Generator, *apiextensions.JSON, error) {
  43. // get a GVK from the generatorRef
  44. gv, err := schema.ParseGroupVersion(generatorRef.APIVersion)
  45. if err != nil {
  46. return nil, nil, reconcile.TerminalError(fmt.Errorf("generatorRef has invalid APIVersion: %w", err))
  47. }
  48. gvk := schema.GroupVersionKind{
  49. Group: gv.Group,
  50. Version: gv.Version,
  51. Kind: generatorRef.Kind,
  52. }
  53. // fail if the GVK does not use the generator group
  54. if gvk.Group != genv1alpha1.Group {
  55. return nil, nil, reconcile.TerminalError(fmt.Errorf("generatorRef may only reference the generators group, but got %s", gvk.Group))
  56. }
  57. // get a client Object from the GVK
  58. t, exists := scheme.AllKnownTypes()[gvk]
  59. if !exists {
  60. return nil, nil, reconcile.TerminalError(fmt.Errorf("generatorRef references unknown GVK %s", gvk))
  61. }
  62. obj := reflect.New(t).Interface().(client.Object)
  63. // this interface provides the Generate() method used by the controller
  64. // NOTE: all instances of a generator kind use the same instance of this interface
  65. var generator genv1alpha1.Generator
  66. // ClusterGenerator is a special case because it's a cluster-scoped resource
  67. // to use it, we create a "virtual" namespaced generator for the current namespace, as if one existed in the API
  68. if gvk.Kind == genv1alpha1.ClusterGeneratorKind {
  69. clusterGenerator := obj.(*genv1alpha1.ClusterGenerator)
  70. // get the cluster generator resource from the API
  71. // NOTE: it's important that we use the structured client so we use the cache
  72. err = cl.Get(ctx, client.ObjectKey{Name: generatorRef.Name}, clusterGenerator)
  73. if err != nil {
  74. return nil, nil, err
  75. }
  76. // convert the cluster generator to a virtual namespaced generator object
  77. obj, err = clusterGeneratorToVirtual(clusterGenerator)
  78. if err != nil {
  79. return nil, nil, reconcile.TerminalError(fmt.Errorf("invalid ClusterGenerator: %w", err))
  80. }
  81. // get the generator interface
  82. var ok bool
  83. generator, ok = genv1alpha1.GetGeneratorByName(string(clusterGenerator.Spec.Kind))
  84. if !ok {
  85. return nil, nil, reconcile.TerminalError(fmt.Errorf("ClusterGenerator has unknown kind %s", clusterGenerator.Spec.Kind))
  86. }
  87. } else {
  88. // get the generator resource from the API
  89. // NOTE: it's important that we use the structured client so we use the cache
  90. err = cl.Get(ctx, types.NamespacedName{
  91. Name: generatorRef.Name,
  92. Namespace: namespace,
  93. }, obj)
  94. if err != nil {
  95. return nil, nil, err
  96. }
  97. // get the generator interface
  98. var ok bool
  99. generator, ok = genv1alpha1.GetGeneratorByName(gvk.Kind)
  100. if !ok {
  101. return nil, nil, reconcile.TerminalError(fmt.Errorf("generatorRef has unknown kind %s", gvk.Kind))
  102. }
  103. }
  104. // convert the generator to unstructured object
  105. u := &unstructured.Unstructured{}
  106. u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  107. if err != nil {
  108. return nil, nil, err
  109. }
  110. // convert the unstructured object to JSON
  111. // NOTE: we do this for backwards compatibility with how this API works, not because it's a good idea
  112. // we should refactor the generator API to use the normal typed objects
  113. jsonObj, err := u.MarshalJSON()
  114. if err != nil {
  115. return nil, nil, err
  116. }
  117. return generator, &apiextensions.JSON{Raw: jsonObj}, nil
  118. }
  119. // clusterGeneratorToVirtual converts a ClusterGenerator to a "virtual" namespaced generator that doesn't actually exist in the API.
  120. func clusterGeneratorToVirtual(gen *genv1alpha1.ClusterGenerator) (client.Object, error) {
  121. switch gen.Spec.Kind {
  122. case genv1alpha1.GeneratorKindACRAccessToken:
  123. if gen.Spec.Generator.ACRAccessTokenSpec == nil {
  124. return nil, fmt.Errorf("when kind is %s, ACRAccessTokenSpec must be set", gen.Spec.Kind)
  125. }
  126. return &genv1alpha1.ACRAccessToken{
  127. TypeMeta: metav1.TypeMeta{
  128. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  129. Kind: genv1alpha1.ACRAccessTokenKind,
  130. },
  131. Spec: *gen.Spec.Generator.ACRAccessTokenSpec,
  132. }, nil
  133. case genv1alpha1.GeneratorKindCloudsmithAccessToken:
  134. if gen.Spec.Generator.CloudsmithAccessTokenSpec == nil {
  135. return nil, fmt.Errorf("when kind is %s, CloudsmithAccessTokenSpec must be set", gen.Spec.Kind)
  136. }
  137. return &genv1alpha1.CloudsmithAccessToken{
  138. TypeMeta: metav1.TypeMeta{
  139. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  140. Kind: genv1alpha1.CloudsmithAccessTokenKind,
  141. },
  142. Spec: *gen.Spec.Generator.CloudsmithAccessTokenSpec,
  143. }, nil
  144. case genv1alpha1.GeneratorKindECRAuthorizationToken:
  145. if gen.Spec.Generator.ECRAuthorizationTokenSpec == nil {
  146. return nil, fmt.Errorf("when kind is %s, ECRAuthorizationTokenSpec must be set", gen.Spec.Kind)
  147. }
  148. return &genv1alpha1.ECRAuthorizationToken{
  149. TypeMeta: metav1.TypeMeta{
  150. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  151. Kind: genv1alpha1.ECRAuthorizationTokenKind,
  152. },
  153. Spec: *gen.Spec.Generator.ECRAuthorizationTokenSpec,
  154. }, nil
  155. case genv1alpha1.GeneratorKindFake:
  156. if gen.Spec.Generator.FakeSpec == nil {
  157. return nil, fmt.Errorf("when kind is %s, FakeSpec must be set", gen.Spec.Kind)
  158. }
  159. return &genv1alpha1.Fake{
  160. TypeMeta: metav1.TypeMeta{
  161. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  162. Kind: genv1alpha1.FakeKind,
  163. },
  164. Spec: *gen.Spec.Generator.FakeSpec,
  165. }, nil
  166. case genv1alpha1.GeneratorKindGCRAccessToken:
  167. if gen.Spec.Generator.GCRAccessTokenSpec == nil {
  168. return nil, fmt.Errorf("when kind is %s, GCRAccessTokenSpec must be set", gen.Spec.Kind)
  169. }
  170. return &genv1alpha1.GCRAccessToken{
  171. TypeMeta: metav1.TypeMeta{
  172. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  173. Kind: genv1alpha1.GCRAccessTokenKind,
  174. },
  175. Spec: *gen.Spec.Generator.GCRAccessTokenSpec,
  176. }, nil
  177. case genv1alpha1.GeneratorKindGithubAccessToken:
  178. if gen.Spec.Generator.GithubAccessTokenSpec == nil {
  179. return nil, fmt.Errorf("when kind is %s, GithubAccessTokenSpec must be set", gen.Spec.Kind)
  180. }
  181. return &genv1alpha1.GithubAccessToken{
  182. TypeMeta: metav1.TypeMeta{
  183. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  184. Kind: genv1alpha1.GithubAccessTokenKind,
  185. },
  186. Spec: *gen.Spec.Generator.GithubAccessTokenSpec,
  187. }, nil
  188. case genv1alpha1.GeneratorKindQuayAccessToken:
  189. if gen.Spec.Generator.QuayAccessTokenSpec == nil {
  190. return nil, fmt.Errorf("when kind is %s, QuayAccessTokenSpec must be set", gen.Spec.Kind)
  191. }
  192. return &genv1alpha1.QuayAccessToken{
  193. Spec: *gen.Spec.Generator.QuayAccessTokenSpec,
  194. }, nil
  195. case genv1alpha1.GeneratorKindPassword:
  196. if gen.Spec.Generator.PasswordSpec == nil {
  197. return nil, fmt.Errorf("when kind is %s, PasswordSpec must be set", gen.Spec.Kind)
  198. }
  199. return &genv1alpha1.Password{
  200. TypeMeta: metav1.TypeMeta{
  201. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  202. Kind: genv1alpha1.PasswordKind,
  203. },
  204. Spec: *gen.Spec.Generator.PasswordSpec,
  205. }, nil
  206. case genv1alpha1.GeneratorKindSSHKey:
  207. if gen.Spec.Generator.SSHKeySpec == nil {
  208. return nil, fmt.Errorf("when kind is %s, SSHKeySpec must be set", gen.Spec.Kind)
  209. }
  210. return &genv1alpha1.SSHKey{
  211. TypeMeta: metav1.TypeMeta{
  212. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  213. Kind: genv1alpha1.SSHKeyKind,
  214. },
  215. Spec: *gen.Spec.Generator.SSHKeySpec,
  216. }, nil
  217. case genv1alpha1.GeneratorKindSTSSessionToken:
  218. if gen.Spec.Generator.STSSessionTokenSpec == nil {
  219. return nil, fmt.Errorf("when kind is %s, STSSessionTokenSpec must be set", gen.Spec.Kind)
  220. }
  221. return &genv1alpha1.STSSessionToken{
  222. TypeMeta: metav1.TypeMeta{
  223. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  224. Kind: genv1alpha1.STSSessionTokenKind,
  225. },
  226. Spec: *gen.Spec.Generator.STSSessionTokenSpec,
  227. }, nil
  228. case genv1alpha1.GeneratorKindUUID:
  229. if gen.Spec.Generator.UUIDSpec == nil {
  230. return nil, fmt.Errorf("when kind is %s, UUIDSpec must be set", gen.Spec.Kind)
  231. }
  232. return &genv1alpha1.UUID{
  233. TypeMeta: metav1.TypeMeta{
  234. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  235. Kind: genv1alpha1.UUIDKind,
  236. },
  237. Spec: *gen.Spec.Generator.UUIDSpec,
  238. }, nil
  239. case genv1alpha1.GeneratorKindVaultDynamicSecret:
  240. if gen.Spec.Generator.VaultDynamicSecretSpec == nil {
  241. return nil, fmt.Errorf("when kind is %s, VaultDynamicSecretSpec must be set", gen.Spec.Kind)
  242. }
  243. return &genv1alpha1.VaultDynamicSecret{
  244. TypeMeta: metav1.TypeMeta{
  245. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  246. Kind: genv1alpha1.VaultDynamicSecretKind,
  247. },
  248. Spec: *gen.Spec.Generator.VaultDynamicSecretSpec,
  249. }, nil
  250. case genv1alpha1.GeneratorKindWebhook:
  251. if gen.Spec.Generator.WebhookSpec == nil {
  252. return nil, fmt.Errorf("when kind is %s, WebhookSpec must be set", gen.Spec.Kind)
  253. }
  254. return &genv1alpha1.Webhook{
  255. TypeMeta: metav1.TypeMeta{
  256. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  257. Kind: genv1alpha1.WebhookKind,
  258. },
  259. Spec: *gen.Spec.Generator.WebhookSpec,
  260. }, nil
  261. case genv1alpha1.GeneratorKindGrafana:
  262. if gen.Spec.Generator.GrafanaSpec == nil {
  263. return nil, fmt.Errorf("when kind is %s, GrafanaSpec must be set", gen.Spec.Kind)
  264. }
  265. return &genv1alpha1.Grafana{
  266. TypeMeta: metav1.TypeMeta{
  267. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  268. Kind: genv1alpha1.GrafanaKind,
  269. },
  270. Spec: *gen.Spec.Generator.GrafanaSpec,
  271. }, nil
  272. case genv1alpha1.GeneratorKindMFA:
  273. if gen.Spec.Generator.MFASpec == nil {
  274. return nil, fmt.Errorf("when kind is %s, MFASpec must be set", gen.Spec.Kind)
  275. }
  276. return &genv1alpha1.MFA{
  277. TypeMeta: metav1.TypeMeta{
  278. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  279. Kind: genv1alpha1.MFAKind,
  280. },
  281. Spec: *gen.Spec.Generator.MFASpec,
  282. }, nil
  283. default:
  284. return nil, fmt.Errorf("unknown kind %s", gen.Spec.Kind)
  285. }
  286. }