cluster_scope.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. Copyright © The ESO Authors
  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 crd
  14. import (
  15. "time"
  16. // nolint
  17. . "github.com/onsi/ginkgo/v2"
  18. // nolint
  19. . "github.com/onsi/gomega"
  20. corev1 "k8s.io/api/core/v1"
  21. rbac "k8s.io/api/rbac/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "github.com/external-secrets/external-secrets-e2e/framework"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. )
  27. // A cluster-scoped target kind takes different branches all the way down: the
  28. // RESTMapper reports a cluster scope, getObject drops the namespace (and
  29. // rejects a '/' in the key), and GetAllSecrets lists without a namespace
  30. // selector and keys results by bare object name even for a ClusterSecretStore.
  31. // None of that is reachable with the namespaced kind, so it gets its own suite.
  32. var clusterCRDGVK = schema.GroupVersionKind{Group: crdGroup, Version: crdVersion, Kind: clusterCRDKind}
  33. var _ = Describe("[crd] cluster-scoped kind ", Label("crd"), func() {
  34. f := framework.New("eso-crd-cluster")
  35. prov := NewClusterScopedProvider(f)
  36. DescribeTable("sync secrets",
  37. framework.TableFuncWithExternalSecret(f, prov),
  38. Entry(syncClusterScopedViaSecretStore(f, prov)),
  39. Entry(syncClusterScopedViaClusterStore(f, prov)),
  40. Entry(findClusterScoped(f, prov)),
  41. )
  42. })
  43. // ClusterScopedProvider drives the cluster-scoped test kind. Object names are
  44. // derived from the test namespace because the objects share one cluster-wide
  45. // name space with every parallel spec.
  46. type ClusterScopedProvider struct {
  47. framework *framework.Framework
  48. }
  49. func NewClusterScopedProvider(f *framework.Framework) *ClusterScopedProvider {
  50. prov := &ClusterScopedProvider{framework: f}
  51. BeforeEach(prov.BeforeEach)
  52. AfterEach(prov.AfterEach)
  53. return prov
  54. }
  55. func (s *ClusterScopedProvider) BeforeEach() {
  56. ensureCRD(s.framework, clusterScopedTestCRD())
  57. grantClusterRead(s.framework, crdGroup, clusterCRDPlural, "default", []string{"get", "list", "watch"})
  58. s.createStores()
  59. }
  60. func (s *ClusterScopedProvider) AfterEach() {
  61. ctx := GinkgoT().Context()
  62. ns := s.framework.Namespace.Name
  63. _ = s.framework.CRClient.Delete(ctx, &esv1.ClusterSecretStore{
  64. ObjectMeta: metav1.ObjectMeta{Name: referentStoreName(s.framework)},
  65. })
  66. _ = s.framework.CRClient.Delete(ctx, &rbac.ClusterRoleBinding{
  67. ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName(ns)},
  68. })
  69. _ = s.framework.CRClient.Delete(ctx, &rbac.ClusterRole{
  70. ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName(ns)},
  71. })
  72. }
  73. // objectName qualifies a base name with the test namespace. Cluster-scoped
  74. // objects have no namespace to isolate them, so without this two parallel specs
  75. // would fight over the same object.
  76. func (s *ClusterScopedProvider) objectName(base string) string {
  77. return s.framework.Namespace.Name + "-" + base
  78. }
  79. // CreateSecret seeds a cluster-scoped CR. The framework passes the key through
  80. // verbatim, so specs build it with objectName.
  81. func (s *ClusterScopedProvider) CreateSecret(key string, val framework.SecretEntry) {
  82. createTestResource(s.framework, clusterCRDGVK, "", key, val)
  83. }
  84. func (s *ClusterScopedProvider) DeleteSecret(key string) {
  85. deleteTestResource(s.framework, clusterCRDGVK, "", key)
  86. }
  87. func (s *ClusterScopedProvider) createStores() {
  88. ctx := GinkgoT().Context()
  89. ns := s.framework.Namespace.Name
  90. res := esv1.CRDProviderResource{Group: crdGroup, Version: crdVersion, Kind: clusterCRDKind}
  91. store := &esv1.SecretStore{
  92. ObjectMeta: metav1.ObjectMeta{Name: ns, Namespace: ns},
  93. Spec: esv1.SecretStoreSpec{
  94. Provider: &esv1.SecretStoreProvider{CRD: inClusterProviderSpec("default", res)},
  95. },
  96. }
  97. Expect(s.framework.CRClient.Create(ctx, store)).To(Succeed())
  98. prov := inClusterProviderSpec("default", res)
  99. prov.Server.CAProvider.Namespace = &ns
  100. css := &esv1.ClusterSecretStore{
  101. ObjectMeta: metav1.ObjectMeta{Name: referentStoreName(s.framework)},
  102. Spec: esv1.SecretStoreSpec{
  103. Provider: &esv1.SecretStoreProvider{CRD: prov},
  104. },
  105. }
  106. Expect(s.framework.CRClient.Create(ctx, css)).To(Succeed())
  107. }
  108. // syncClusterScopedViaSecretStore reads a cluster-scoped CR through a
  109. // SecretStore. The store's own namespace is irrelevant here: the RESTMapper
  110. // reports a cluster scope, so the read must not be namespaced.
  111. func syncClusterScopedViaSecretStore(_ *framework.Framework, prov *ClusterScopedProvider) (string, func(*framework.TestCase)) {
  112. return "[crd] should sync a property from a cluster-scoped CR", func(tc *framework.TestCase) {
  113. key := prov.objectName("e2e-crd-cluster-a")
  114. tc.Secrets = map[string]framework.SecretEntry{
  115. key: {Value: `{"password":"cluster-pass"}`},
  116. }
  117. tc.ExpectedSecret = &corev1.Secret{
  118. Type: corev1.SecretTypeOpaque,
  119. Data: map[string][]byte{"pw": []byte("cluster-pass")},
  120. }
  121. tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{
  122. {
  123. SecretKey: "pw",
  124. RemoteRef: esv1.ExternalSecretDataRemoteRef{Key: key, Property: "spec.password"},
  125. },
  126. }
  127. }
  128. }
  129. // syncClusterScopedViaClusterStore covers the ClusterSecretStore key form for a
  130. // cluster-scoped kind: a bare object name with no '/' separator, which is only
  131. // legal because the resource has no namespace.
  132. func syncClusterScopedViaClusterStore(f *framework.Framework, prov *ClusterScopedProvider) (string, func(*framework.TestCase)) {
  133. return "[crd] should sync a cluster-scoped CR via a ClusterSecretStore with a bare key", func(tc *framework.TestCase) {
  134. key := prov.objectName("e2e-crd-cluster-b")
  135. tc.Secrets = map[string]framework.SecretEntry{
  136. key: {Value: `{"token":"cluster-token"}`},
  137. }
  138. tc.ExpectedSecret = &corev1.Secret{
  139. Type: corev1.SecretTypeOpaque,
  140. Data: map[string][]byte{"token": []byte("cluster-token")},
  141. }
  142. tc.ExternalSecret.Spec.SecretStoreRef.Name = referentStoreName(f)
  143. tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1.ClusterSecretStoreKind
  144. tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{
  145. {
  146. SecretKey: "token",
  147. RemoteRef: esv1.ExternalSecretDataRemoteRef{Key: key, Property: "spec.token"},
  148. },
  149. }
  150. }
  151. }
  152. // findClusterScoped lists cluster-scoped CRs. Keys stay bare object names even
  153. // through a ClusterSecretStore, because there is no namespace to prefix.
  154. func findClusterScoped(f *framework.Framework, prov *ClusterScopedProvider) (string, func(*framework.TestCase)) {
  155. return "[crd] should find cluster-scoped CRs via dataFrom.find", func(tc *framework.TestCase) {
  156. hit := prov.objectName("e2e-crd-cluster-find")
  157. miss := prov.objectName("e2e-crd-cluster-skip")
  158. tc.Secrets = map[string]framework.SecretEntry{
  159. hit: {Value: `{"marker":"hit"}`},
  160. miss: {Value: `{"marker":"miss"}`},
  161. }
  162. tc.ExternalSecret.Spec.SecretStoreRef.Name = referentStoreName(f)
  163. tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1.ClusterSecretStoreKind
  164. tc.ExternalSecret.Spec.DataFrom = []esv1.ExternalSecretDataFromRemoteRef{
  165. {Find: &esv1.ExternalSecretFind{Name: &esv1.FindName{RegExp: "^" + hit + "$"}}},
  166. }
  167. tc.ExpectedSecret = nil
  168. tc.AfterSync = func(_ framework.SecretStoreProvider, _ *corev1.Secret) {
  169. Eventually(func(g Gomega) {
  170. sec := targetSecret(g, f)
  171. g.Expect(sec.Data).To(HaveKey(hit))
  172. g.Expect(sec.Data).ToNot(HaveKey(miss))
  173. g.Expect(string(sec.Data[hit])).To(ContainSubstring(`"marker":"hit"`))
  174. }, time.Minute, time.Second).Should(Succeed())
  175. }
  176. }
  177. }