clusterexternalsecret_controller_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  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 clusterexternalsecret
  13. import (
  14. "context"
  15. "fmt"
  16. "math/rand"
  17. "sort"
  18. "time"
  19. v1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/types"
  22. crclient "sigs.k8s.io/controller-runtime/pkg/client"
  23. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  24. "github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
  25. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  26. . "github.com/onsi/ginkgo/v2"
  27. . "github.com/onsi/gomega"
  28. )
  29. func init() {
  30. ctrlmetrics.SetUpLabelNames(false)
  31. cesmetrics.SetUpMetrics()
  32. }
  33. const (
  34. metadataLabelName = "kubernetes.io/metadata.name"
  35. testLabelKey = "test-label-key"
  36. testAnnotationKey = "test-annotation-key"
  37. testLabelValue = "test-label-value"
  38. testAnnotationValue = "test-annotation-value"
  39. updatedTestStore = "updated-test-store"
  40. noLongerMatchLabelKey = "no-longer-match-label-key"
  41. noLongerMatchLabelValue = "no-longer-match-label-value"
  42. )
  43. var (
  44. timeout = time.Second * 10
  45. interval = time.Millisecond * 250
  46. )
  47. type testCase struct {
  48. namespaces []v1.Namespace
  49. clusterExternalSecret func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret
  50. beforeCheck func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret)
  51. expectedClusterExternalSecret func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret
  52. expectedExternalSecrets func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret
  53. }
  54. var _ = Describe("ClusterExternalSecret controller", func() {
  55. defaultClusterExternalSecret := func() *esv1beta1.ClusterExternalSecret {
  56. return &esv1beta1.ClusterExternalSecret{
  57. ObjectMeta: metav1.ObjectMeta{
  58. Name: fmt.Sprintf("test-ces-%s", randString(10)),
  59. },
  60. Spec: esv1beta1.ClusterExternalSecretSpec{
  61. ExternalSecretSpec: esv1beta1.ExternalSecretSpec{
  62. SecretStoreRef: esv1beta1.SecretStoreRef{
  63. Name: "test-store",
  64. },
  65. Target: esv1beta1.ExternalSecretTarget{
  66. Name: "test-secret",
  67. },
  68. Data: []esv1beta1.ExternalSecretData{
  69. {
  70. SecretKey: "test-secret-key",
  71. RemoteRef: esv1beta1.ExternalSecretDataRemoteRef{
  72. Key: "test-remote-key",
  73. },
  74. },
  75. },
  76. },
  77. },
  78. }
  79. }
  80. DescribeTable("When reconciling a ClusterExternal Secret",
  81. func(tc testCase) {
  82. ctx := context.Background()
  83. By("creating namespaces")
  84. var namespaces []v1.Namespace
  85. for _, ns := range tc.namespaces {
  86. err := k8sClient.Create(ctx, &ns)
  87. Expect(err).ShouldNot(HaveOccurred())
  88. namespaces = append(namespaces, ns)
  89. }
  90. By("creating a cluster external secret")
  91. ces := tc.clusterExternalSecret(tc.namespaces)
  92. err := k8sClient.Create(ctx, &ces)
  93. Expect(err).ShouldNot(HaveOccurred())
  94. By("running before check")
  95. if tc.beforeCheck != nil {
  96. tc.beforeCheck(ctx, namespaces, ces)
  97. }
  98. // the before check above may have updated the namespaces, so refresh them
  99. for i, ns := range namespaces {
  100. err := k8sClient.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
  101. Expect(err).ShouldNot(HaveOccurred())
  102. namespaces[i] = ns
  103. }
  104. By("checking the cluster external secret")
  105. expectedCES := tc.expectedClusterExternalSecret(namespaces, ces)
  106. Eventually(func(g Gomega) {
  107. key := types.NamespacedName{Name: expectedCES.Name}
  108. var gotCes esv1beta1.ClusterExternalSecret
  109. err = k8sClient.Get(ctx, key, &gotCes)
  110. g.Expect(err).ShouldNot(HaveOccurred())
  111. g.Expect(gotCes.Labels).To(Equal(expectedCES.Labels))
  112. g.Expect(gotCes.Annotations).To(Equal(expectedCES.Annotations))
  113. g.Expect(gotCes.Spec).To(Equal(expectedCES.Spec))
  114. g.Expect(gotCes.Status).To(Equal(expectedCES.Status))
  115. }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
  116. By("checking the external secrets")
  117. expectedESs := tc.expectedExternalSecrets(namespaces, ces)
  118. Eventually(func(g Gomega) {
  119. var gotESs []esv1beta1.ExternalSecret
  120. for _, ns := range namespaces {
  121. var externalSecrets esv1beta1.ExternalSecretList
  122. err := k8sClient.List(ctx, &externalSecrets, crclient.InNamespace(ns.Name))
  123. g.Expect(err).ShouldNot(HaveOccurred())
  124. gotESs = append(gotESs, externalSecrets.Items...)
  125. }
  126. g.Expect(len(gotESs)).Should(Equal(len(expectedESs)))
  127. for _, gotES := range gotESs {
  128. found := false
  129. for _, expectedES := range expectedESs {
  130. if gotES.Namespace == expectedES.Namespace && gotES.Name == expectedES.Name {
  131. found = true
  132. g.Expect(gotES.Labels).To(Equal(expectedES.Labels))
  133. g.Expect(gotES.Annotations).To(Equal(expectedES.Annotations))
  134. g.Expect(gotES.Spec).To(Equal(expectedES.Spec))
  135. }
  136. }
  137. g.Expect(found).To(Equal(true))
  138. }
  139. }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
  140. },
  141. Entry("Should use cluster external secret name if external secret name isn't defined", testCase{
  142. namespaces: []v1.Namespace{
  143. {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
  144. },
  145. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  146. ces := defaultClusterExternalSecret()
  147. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  148. MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
  149. }
  150. return *ces
  151. },
  152. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  153. return esv1beta1.ClusterExternalSecret{
  154. ObjectMeta: metav1.ObjectMeta{
  155. Name: created.Name,
  156. },
  157. Spec: created.Spec,
  158. Status: esv1beta1.ClusterExternalSecretStatus{
  159. ExternalSecretName: created.Name,
  160. ProvisionedNamespaces: []string{namespaces[0].Name},
  161. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  162. {
  163. Type: esv1beta1.ClusterExternalSecretReady,
  164. Status: v1.ConditionTrue,
  165. },
  166. },
  167. },
  168. }
  169. },
  170. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  171. return []esv1beta1.ExternalSecret{
  172. {
  173. ObjectMeta: metav1.ObjectMeta{
  174. Namespace: namespaces[0].Name,
  175. Name: created.Name,
  176. },
  177. Spec: created.Spec.ExternalSecretSpec,
  178. },
  179. }
  180. },
  181. }),
  182. Entry("Should set external secret name and metadata if the fields are set", testCase{
  183. namespaces: []v1.Namespace{
  184. {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
  185. },
  186. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  187. ces := defaultClusterExternalSecret()
  188. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  189. MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
  190. }
  191. ces.Spec.ExternalSecretName = "test-es"
  192. ces.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
  193. Labels: map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
  194. Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
  195. }
  196. return *ces
  197. },
  198. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  199. return esv1beta1.ClusterExternalSecret{
  200. ObjectMeta: metav1.ObjectMeta{
  201. Name: created.Name,
  202. },
  203. Spec: created.Spec,
  204. Status: esv1beta1.ClusterExternalSecretStatus{
  205. ExternalSecretName: "test-es",
  206. ProvisionedNamespaces: []string{namespaces[0].Name},
  207. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  208. {
  209. Type: esv1beta1.ClusterExternalSecretReady,
  210. Status: v1.ConditionTrue,
  211. },
  212. },
  213. },
  214. }
  215. },
  216. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  217. return []esv1beta1.ExternalSecret{
  218. {
  219. ObjectMeta: metav1.ObjectMeta{
  220. Namespace: namespaces[0].Name,
  221. Name: "test-es",
  222. Labels: map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
  223. Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
  224. },
  225. Spec: created.Spec.ExternalSecretSpec,
  226. },
  227. }
  228. },
  229. }),
  230. Entry("Should delete old external secrets if name has changed", testCase{
  231. namespaces: []v1.Namespace{
  232. {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
  233. },
  234. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  235. ces := defaultClusterExternalSecret()
  236. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  237. MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
  238. }
  239. ces.Spec.ExternalSecretName = "old-es-name"
  240. return *ces
  241. },
  242. beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
  243. // Wait until the external secret is provisioned
  244. var es esv1beta1.ExternalSecret
  245. Eventually(func(g Gomega) {
  246. key := types.NamespacedName{Namespace: namespaces[0].Name, Name: "old-es-name"}
  247. g.Expect(k8sClient.Get(ctx, key, &es)).ShouldNot(HaveOccurred())
  248. }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
  249. copied := created.DeepCopy()
  250. copied.Spec.ExternalSecretName = "new-es-name"
  251. Expect(k8sClient.Patch(ctx, copied, crclient.MergeFrom(created.DeepCopy()))).ShouldNot(HaveOccurred())
  252. },
  253. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  254. updatedSpec := created.Spec.DeepCopy()
  255. updatedSpec.ExternalSecretName = "new-es-name"
  256. return esv1beta1.ClusterExternalSecret{
  257. ObjectMeta: metav1.ObjectMeta{
  258. Name: created.Name,
  259. },
  260. Spec: *updatedSpec,
  261. Status: esv1beta1.ClusterExternalSecretStatus{
  262. ExternalSecretName: "new-es-name",
  263. ProvisionedNamespaces: []string{namespaces[0].Name},
  264. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  265. {
  266. Type: esv1beta1.ClusterExternalSecretReady,
  267. Status: v1.ConditionTrue,
  268. },
  269. },
  270. },
  271. }
  272. },
  273. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  274. return []esv1beta1.ExternalSecret{
  275. {
  276. ObjectMeta: metav1.ObjectMeta{
  277. Namespace: namespaces[0].Name,
  278. Name: "new-es-name",
  279. },
  280. Spec: created.Spec.ExternalSecretSpec,
  281. },
  282. }
  283. },
  284. }),
  285. Entry("Should update external secret if the fields change", testCase{
  286. namespaces: []v1.Namespace{
  287. {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
  288. },
  289. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  290. ces := defaultClusterExternalSecret()
  291. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  292. MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
  293. }
  294. return *ces
  295. },
  296. beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
  297. // Wait until the external secret is provisioned
  298. var es esv1beta1.ExternalSecret
  299. Eventually(func(g Gomega) {
  300. key := types.NamespacedName{Namespace: namespaces[0].Name, Name: created.Name}
  301. g.Expect(k8sClient.Get(ctx, key, &es)).ShouldNot(HaveOccurred())
  302. g.Expect(len(es.Labels)).Should(Equal(0))
  303. g.Expect(len(es.Annotations)).Should(Equal(0))
  304. g.Expect(es.Spec).Should(Equal(created.Spec.ExternalSecretSpec))
  305. }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
  306. copied := created.DeepCopy()
  307. copied.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
  308. Labels: map[string]string{testLabelKey: testLabelValue},
  309. Annotations: map[string]string{testAnnotationKey: testAnnotationValue},
  310. }
  311. copied.Spec.ExternalSecretSpec.SecretStoreRef.Name = updatedTestStore
  312. Expect(k8sClient.Patch(ctx, copied, crclient.MergeFrom(created.DeepCopy()))).ShouldNot(HaveOccurred())
  313. },
  314. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  315. updatedSpec := created.Spec.DeepCopy()
  316. updatedSpec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
  317. Labels: map[string]string{testLabelKey: testLabelValue},
  318. Annotations: map[string]string{testAnnotationKey: testAnnotationValue},
  319. }
  320. updatedSpec.ExternalSecretSpec.SecretStoreRef.Name = updatedTestStore
  321. return esv1beta1.ClusterExternalSecret{
  322. ObjectMeta: metav1.ObjectMeta{
  323. Name: created.Name,
  324. },
  325. Spec: *updatedSpec,
  326. Status: esv1beta1.ClusterExternalSecretStatus{
  327. ExternalSecretName: created.Name,
  328. ProvisionedNamespaces: []string{namespaces[0].Name},
  329. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  330. {
  331. Type: esv1beta1.ClusterExternalSecretReady,
  332. Status: v1.ConditionTrue,
  333. },
  334. },
  335. },
  336. }
  337. },
  338. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  339. updatedSpec := created.Spec.ExternalSecretSpec.DeepCopy()
  340. updatedSpec.SecretStoreRef.Name = updatedTestStore
  341. return []esv1beta1.ExternalSecret{
  342. {
  343. ObjectMeta: metav1.ObjectMeta{
  344. Namespace: namespaces[0].Name,
  345. Name: created.Name,
  346. Labels: map[string]string{testLabelKey: testLabelValue},
  347. Annotations: map[string]string{testAnnotationKey: testAnnotationValue},
  348. },
  349. Spec: *updatedSpec,
  350. },
  351. }
  352. },
  353. }),
  354. Entry("Should not overwrite existing external secrets and error out if one is present", testCase{
  355. namespaces: []v1.Namespace{
  356. {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
  357. },
  358. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  359. ces := defaultClusterExternalSecret()
  360. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  361. MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
  362. }
  363. es := &esv1beta1.ExternalSecret{
  364. ObjectMeta: metav1.ObjectMeta{
  365. Name: ces.Name,
  366. Namespace: namespaces[0].Name,
  367. },
  368. }
  369. Expect(k8sClient.Create(context.Background(), es)).ShouldNot(HaveOccurred())
  370. return *ces
  371. },
  372. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  373. return esv1beta1.ClusterExternalSecret{
  374. ObjectMeta: metav1.ObjectMeta{
  375. Name: created.Name,
  376. },
  377. Spec: created.Spec,
  378. Status: esv1beta1.ClusterExternalSecretStatus{
  379. ExternalSecretName: created.Name,
  380. FailedNamespaces: []esv1beta1.ClusterExternalSecretNamespaceFailure{
  381. {
  382. Namespace: namespaces[0].Name,
  383. Reason: "external secret already exists in namespace",
  384. },
  385. },
  386. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  387. {
  388. Type: esv1beta1.ClusterExternalSecretReady,
  389. Status: v1.ConditionFalse,
  390. Message: "one or more namespaces failed",
  391. },
  392. },
  393. },
  394. }
  395. },
  396. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  397. return []esv1beta1.ExternalSecret{
  398. {
  399. ObjectMeta: metav1.ObjectMeta{
  400. Namespace: namespaces[0].Name,
  401. Name: created.Name,
  402. },
  403. Spec: esv1beta1.ExternalSecretSpec{
  404. Target: esv1beta1.ExternalSecretTarget{
  405. CreationPolicy: "Owner",
  406. DeletionPolicy: "Retain",
  407. },
  408. RefreshInterval: &metav1.Duration{Duration: time.Hour},
  409. },
  410. },
  411. }
  412. },
  413. }),
  414. Entry("Should crate an external secret if one with the same name has been deleted", testCase{
  415. namespaces: []v1.Namespace{
  416. {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
  417. },
  418. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  419. ces := defaultClusterExternalSecret()
  420. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  421. MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
  422. }
  423. es := &esv1beta1.ExternalSecret{
  424. ObjectMeta: metav1.ObjectMeta{
  425. Name: ces.Name,
  426. Namespace: namespaces[0].Name,
  427. },
  428. }
  429. Expect(k8sClient.Create(context.Background(), es)).ShouldNot(HaveOccurred())
  430. return *ces
  431. },
  432. beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
  433. ces := esv1beta1.ClusterExternalSecret{}
  434. Eventually(func(g Gomega) {
  435. key := types.NamespacedName{Namespace: created.Namespace, Name: created.Name}
  436. g.Expect(k8sClient.Get(ctx, key, &ces)).ShouldNot(HaveOccurred())
  437. g.Expect(len(ces.Status.FailedNamespaces)).Should(Equal(1))
  438. }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
  439. es := &esv1beta1.ExternalSecret{
  440. ObjectMeta: metav1.ObjectMeta{
  441. Name: ces.Name,
  442. Namespace: namespaces[0].Name,
  443. },
  444. }
  445. Expect(k8sClient.Delete(ctx, es)).ShouldNot(HaveOccurred())
  446. },
  447. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  448. return esv1beta1.ClusterExternalSecret{
  449. ObjectMeta: metav1.ObjectMeta{
  450. Name: created.Name,
  451. },
  452. Spec: created.Spec,
  453. Status: esv1beta1.ClusterExternalSecretStatus{
  454. ExternalSecretName: created.Name,
  455. ProvisionedNamespaces: []string{namespaces[0].Name},
  456. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  457. {
  458. Type: esv1beta1.ClusterExternalSecretReady,
  459. Status: v1.ConditionTrue,
  460. },
  461. },
  462. },
  463. }
  464. },
  465. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  466. return []esv1beta1.ExternalSecret{
  467. {
  468. ObjectMeta: metav1.ObjectMeta{
  469. Namespace: namespaces[0].Name,
  470. Name: created.Name,
  471. },
  472. Spec: created.Spec.ExternalSecretSpec,
  473. },
  474. }
  475. },
  476. }),
  477. Entry("Should delete external secrets when namespaces no longer match", testCase{
  478. namespaces: []v1.Namespace{
  479. {
  480. ObjectMeta: metav1.ObjectMeta{
  481. Name: randomNamespaceName(),
  482. Labels: map[string]string{noLongerMatchLabelKey: noLongerMatchLabelValue},
  483. },
  484. },
  485. {
  486. ObjectMeta: metav1.ObjectMeta{
  487. Name: randomNamespaceName(),
  488. Labels: map[string]string{noLongerMatchLabelKey: noLongerMatchLabelValue},
  489. },
  490. },
  491. },
  492. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  493. ces := defaultClusterExternalSecret()
  494. ces.Spec.RefreshInterval = &metav1.Duration{Duration: 100 * time.Millisecond}
  495. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  496. MatchLabels: map[string]string{noLongerMatchLabelKey: noLongerMatchLabelValue},
  497. }
  498. return *ces
  499. },
  500. beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
  501. // Wait until the target ESs have been created
  502. Eventually(func(g Gomega) {
  503. for _, ns := range namespaces {
  504. key := types.NamespacedName{Namespace: ns.Name, Name: created.Name}
  505. g.Expect(k8sClient.Get(ctx, key, &esv1beta1.ExternalSecret{})).ShouldNot(HaveOccurred())
  506. }
  507. }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
  508. namespaces[0].Labels = map[string]string{}
  509. Expect(k8sClient.Update(ctx, &namespaces[0])).ShouldNot(HaveOccurred())
  510. },
  511. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  512. return esv1beta1.ClusterExternalSecret{
  513. ObjectMeta: metav1.ObjectMeta{
  514. Name: created.Name,
  515. },
  516. Spec: created.Spec,
  517. Status: esv1beta1.ClusterExternalSecretStatus{
  518. ExternalSecretName: created.Name,
  519. ProvisionedNamespaces: []string{namespaces[1].Name},
  520. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  521. {
  522. Type: esv1beta1.ClusterExternalSecretReady,
  523. Status: v1.ConditionTrue,
  524. },
  525. },
  526. },
  527. }
  528. },
  529. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  530. return []esv1beta1.ExternalSecret{
  531. {
  532. ObjectMeta: metav1.ObjectMeta{
  533. Namespace: namespaces[1].Name,
  534. Name: created.Name,
  535. },
  536. Spec: created.Spec.ExternalSecretSpec,
  537. },
  538. }
  539. },
  540. }),
  541. Entry("Should sync with match expression", testCase{
  542. namespaces: []v1.Namespace{
  543. {
  544. ObjectMeta: metav1.ObjectMeta{
  545. Name: randomNamespaceName(),
  546. Labels: map[string]string{"prefix": "foo"},
  547. },
  548. },
  549. {
  550. ObjectMeta: metav1.ObjectMeta{
  551. Name: randomNamespaceName(),
  552. Labels: map[string]string{"prefix": "bar"},
  553. },
  554. },
  555. {
  556. ObjectMeta: metav1.ObjectMeta{
  557. Name: randomNamespaceName(),
  558. Labels: map[string]string{"prefix": "baz"},
  559. },
  560. },
  561. },
  562. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  563. ces := defaultClusterExternalSecret()
  564. ces.Spec.RefreshInterval = &metav1.Duration{Duration: 100 * time.Millisecond}
  565. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  566. MatchExpressions: []metav1.LabelSelectorRequirement{
  567. {
  568. Key: "prefix",
  569. Operator: metav1.LabelSelectorOpIn,
  570. Values: []string{"foo", "bar"}, // "baz" is excluded
  571. },
  572. },
  573. }
  574. return *ces
  575. },
  576. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  577. provisionedNamespaces := []string{namespaces[0].Name, namespaces[1].Name}
  578. sort.Strings(provisionedNamespaces)
  579. return esv1beta1.ClusterExternalSecret{
  580. ObjectMeta: metav1.ObjectMeta{
  581. Name: created.Name,
  582. },
  583. Spec: created.Spec,
  584. Status: esv1beta1.ClusterExternalSecretStatus{
  585. ExternalSecretName: created.Name,
  586. ProvisionedNamespaces: provisionedNamespaces,
  587. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  588. {
  589. Type: esv1beta1.ClusterExternalSecretReady,
  590. Status: v1.ConditionTrue,
  591. },
  592. },
  593. },
  594. }
  595. },
  596. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  597. return []esv1beta1.ExternalSecret{
  598. {
  599. ObjectMeta: metav1.ObjectMeta{
  600. Namespace: namespaces[0].Name,
  601. Name: created.Name,
  602. },
  603. Spec: created.Spec.ExternalSecretSpec,
  604. },
  605. {
  606. ObjectMeta: metav1.ObjectMeta{
  607. Namespace: namespaces[1].Name,
  608. Name: created.Name,
  609. },
  610. Spec: created.Spec.ExternalSecretSpec,
  611. },
  612. }
  613. },
  614. }),
  615. Entry("Should be ready if no namespace matches", testCase{
  616. namespaces: []v1.Namespace{
  617. {
  618. ObjectMeta: metav1.ObjectMeta{
  619. Name: randomNamespaceName(),
  620. },
  621. },
  622. },
  623. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  624. ces := defaultClusterExternalSecret()
  625. ces.Spec.NamespaceSelector = &metav1.LabelSelector{
  626. MatchLabels: map[string]string{metadataLabelName: "no-namespace-matches"},
  627. }
  628. return *ces
  629. },
  630. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  631. return esv1beta1.ClusterExternalSecret{
  632. ObjectMeta: metav1.ObjectMeta{
  633. Name: created.Name,
  634. },
  635. Spec: created.Spec,
  636. Status: esv1beta1.ClusterExternalSecretStatus{
  637. ExternalSecretName: created.Name,
  638. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  639. {
  640. Type: esv1beta1.ClusterExternalSecretReady,
  641. Status: v1.ConditionTrue,
  642. },
  643. },
  644. },
  645. }
  646. },
  647. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  648. return []esv1beta1.ExternalSecret{}
  649. },
  650. }),
  651. Entry("Should be ready if namespace is selected via the namespace selectors", testCase{
  652. namespaces: []v1.Namespace{
  653. {
  654. ObjectMeta: metav1.ObjectMeta{
  655. Name: "namespace1",
  656. Labels: map[string]string{
  657. "key": "value1",
  658. },
  659. },
  660. },
  661. {
  662. ObjectMeta: metav1.ObjectMeta{
  663. Name: "namespace2",
  664. Labels: map[string]string{
  665. "key": "value2",
  666. },
  667. },
  668. },
  669. {
  670. ObjectMeta: metav1.ObjectMeta{
  671. Name: "namespace3",
  672. Labels: map[string]string{
  673. "key": "value3",
  674. },
  675. },
  676. },
  677. },
  678. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  679. ces := defaultClusterExternalSecret()
  680. ces.Spec.NamespaceSelectors = []*metav1.LabelSelector{
  681. {
  682. MatchLabels: map[string]string{"key": "value1"},
  683. },
  684. {
  685. MatchLabels: map[string]string{"key": "value2"},
  686. },
  687. }
  688. return *ces
  689. },
  690. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  691. return esv1beta1.ClusterExternalSecret{
  692. ObjectMeta: metav1.ObjectMeta{
  693. Name: created.Name,
  694. },
  695. Spec: created.Spec,
  696. Status: esv1beta1.ClusterExternalSecretStatus{
  697. ExternalSecretName: created.Name,
  698. ProvisionedNamespaces: []string{
  699. "namespace1",
  700. "namespace2",
  701. },
  702. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  703. {
  704. Type: esv1beta1.ClusterExternalSecretReady,
  705. Status: v1.ConditionTrue,
  706. },
  707. },
  708. },
  709. }
  710. },
  711. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  712. return []esv1beta1.ExternalSecret{
  713. {
  714. ObjectMeta: metav1.ObjectMeta{
  715. Namespace: "namespace1",
  716. Name: created.Name,
  717. },
  718. Spec: created.Spec.ExternalSecretSpec,
  719. },
  720. {
  721. ObjectMeta: metav1.ObjectMeta{
  722. Namespace: "namespace2",
  723. Name: created.Name,
  724. },
  725. Spec: created.Spec.ExternalSecretSpec,
  726. },
  727. }
  728. },
  729. }),
  730. Entry("Should be ready if namespace is selected via namespaces", testCase{
  731. namespaces: []v1.Namespace{
  732. {
  733. ObjectMeta: metav1.ObjectMeta{
  734. Name: "not-matching-namespace",
  735. },
  736. },
  737. },
  738. clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
  739. ces := defaultClusterExternalSecret()
  740. // does-not-exists tests that we would continue on to the next and not stop if the
  741. // namespace hasn't been created yet.
  742. ces.Spec.Namespaces = []string{"does-not-exist", "not-matching-namespace"}
  743. return *ces
  744. },
  745. expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
  746. return esv1beta1.ClusterExternalSecret{
  747. ObjectMeta: metav1.ObjectMeta{
  748. Name: created.Name,
  749. },
  750. Spec: created.Spec,
  751. Status: esv1beta1.ClusterExternalSecretStatus{
  752. ExternalSecretName: created.Name,
  753. ProvisionedNamespaces: []string{
  754. "not-matching-namespace",
  755. },
  756. Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
  757. {
  758. Type: esv1beta1.ClusterExternalSecretReady,
  759. Status: v1.ConditionTrue,
  760. },
  761. },
  762. },
  763. }
  764. },
  765. expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
  766. return []esv1beta1.ExternalSecret{
  767. {
  768. ObjectMeta: metav1.ObjectMeta{
  769. Namespace: "not-matching-namespace",
  770. Name: created.Name,
  771. },
  772. Spec: created.Spec.ExternalSecretSpec,
  773. },
  774. }
  775. },
  776. }))
  777. })
  778. var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
  779. func randString(n int) string {
  780. b := make([]rune, n)
  781. for i := range b {
  782. b[i] = letterRunes[rand.Intn(len(letterRunes))]
  783. }
  784. return string(b)
  785. }
  786. func randomNamespaceName() string {
  787. return fmt.Sprintf("testns-%s", randString(10))
  788. }