client_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  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 ngrok
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "github.com/ngrok/ngrok-api-go/v9"
  19. corev1 "k8s.io/api/core/v1"
  20. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  24. "github.com/external-secrets/external-secrets/providers/v1/ngrok/fake"
  25. . "github.com/onsi/ginkgo/v2"
  26. . "github.com/onsi/gomega"
  27. )
  28. type pushSecretRemoteRef struct {
  29. remoteKey string
  30. property string
  31. }
  32. func (p pushSecretRemoteRef) GetRemoteKey() string {
  33. return p.remoteKey
  34. }
  35. func (p pushSecretRemoteRef) GetProperty() string {
  36. return p.property
  37. }
  38. var _ = Describe("client", func() {
  39. var (
  40. c *client
  41. vaultsAPI *fake.VaultClient
  42. secretsAPI *fake.SecretsClient
  43. vaultName string
  44. )
  45. BeforeEach(func() {
  46. vaultName = "test-vault"
  47. vaultsAPI = &fake.VaultClient{}
  48. secretsAPI = &fake.SecretsClient{}
  49. })
  50. JustBeforeEach(func() {
  51. c = &client{
  52. vaultClient: vaultsAPI,
  53. secretsClient: secretsAPI,
  54. vaultName: vaultName,
  55. }
  56. })
  57. Describe("PushSecret", func() {
  58. var (
  59. k8Secret *corev1.Secret
  60. pushData v1alpha1.PushSecretData
  61. vault *ngrok.Vault
  62. ngrokSecretName string
  63. pushErr error
  64. createCalledWith *ngrok.SecretCreate
  65. updateCalledWith *ngrok.SecretUpdate
  66. )
  67. BeforeEach(func() {
  68. ngrokSecretName = "secret-" + fake.GenerateRandomString(10)
  69. k8Secret = &corev1.Secret{
  70. ObjectMeta: metav1.ObjectMeta{
  71. Name: "my-secret",
  72. },
  73. Data: map[string][]byte{
  74. "key": []byte("new value"),
  75. "foo": []byte("bar"),
  76. },
  77. }
  78. pushData = v1alpha1.PushSecretData{
  79. Match: v1alpha1.PushSecretMatch{
  80. SecretKey: "key",
  81. RemoteRef: v1alpha1.PushSecretRemoteRef{
  82. RemoteKey: ngrokSecretName,
  83. },
  84. },
  85. }
  86. vault = nil
  87. createCalledWith = nil
  88. updateCalledWith = nil
  89. })
  90. JustBeforeEach(func(ctx SpecContext) {
  91. if vault != nil {
  92. c.setVaultID(vault.ID)
  93. }
  94. pushErr = c.PushSecret(ctx, k8Secret, pushData)
  95. })
  96. When("the vault does not exist", func() {
  97. BeforeEach(func() {
  98. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  99. return fake.NewIter([]*ngrok.Vault{}, nil)
  100. }
  101. })
  102. It("should return an error", func(ctx SpecContext) {
  103. Expect(pushErr).To(HaveOccurred())
  104. Expect(pushErr.Error()).To(ContainSubstring("failed to verify vault name still matches ID: failed to refresh vault ID: vault does not exist"))
  105. })
  106. })
  107. When("an error occurs listing vaults", func() {
  108. BeforeEach(func() {
  109. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  110. return fake.NewIter([]*ngrok.Vault{}, errors.New("failed to list vaults"))
  111. }
  112. })
  113. It("should return an error", func(ctx SpecContext) {
  114. Expect(pushErr).To(HaveOccurred())
  115. Expect(pushErr.Error()).To(ContainSubstring("failed to list vaults"))
  116. })
  117. })
  118. When("the vault exists", func() {
  119. BeforeEach(func(ctx SpecContext) {
  120. vault = &ngrok.Vault{ID: "vault-123", Name: vaultName}
  121. vaultsAPI.GetFn = func(ctx context.Context, id string) (*ngrok.Vault, error) {
  122. if id == vault.ID {
  123. return vault, nil
  124. }
  125. return nil, errors.New("not found")
  126. }
  127. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  128. return fake.NewIter([]*ngrok.Vault{vault}, nil)
  129. }
  130. })
  131. When("an error occurs listing secrets", func() {
  132. BeforeEach(func() {
  133. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  134. return fake.NewIter([]*ngrok.Secret{}, errors.New("failed to list secrets"))
  135. }
  136. })
  137. It("should return an error", func(ctx SpecContext) {
  138. Expect(pushErr).To(HaveOccurred())
  139. Expect(pushErr.Error()).To(ContainSubstring("failed to get secret: failed to list secrets"))
  140. })
  141. })
  142. When("the secret does not exist", func() {
  143. BeforeEach(func() {
  144. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  145. return fake.NewIter([]*ngrok.Secret{}, nil)
  146. }
  147. secretsAPI.CreateFn = func(ctx context.Context, req *ngrok.SecretCreate) (*ngrok.Secret, error) {
  148. createCalledWith = req
  149. return &ngrok.Secret{ID: "sec-123", Name: req.Name, Description: req.Description}, nil
  150. }
  151. })
  152. It("should not return an error", func(ctx SpecContext) {
  153. Expect(pushErr).ToNot(HaveOccurred())
  154. })
  155. It("should create the ngrok secret", func(ctx SpecContext) {
  156. Expect(createCalledWith).ToNot(BeNil())
  157. Expect(createCalledWith.Name).To(Equal(ngrokSecretName))
  158. Expect(createCalledWith.Description).To(Equal(defaultDescription))
  159. Expect(createCalledWith.VaultID).To(Equal(vault.ID))
  160. Expect(createCalledWith.Value).To(Equal("new value"))
  161. })
  162. When("secret creation fails", func() {
  163. BeforeEach(func() {
  164. secretsAPI.CreateFn = func(ctx context.Context, req *ngrok.SecretCreate) (*ngrok.Secret, error) {
  165. return nil, errors.New("failed to create secret")
  166. }
  167. })
  168. It("should return an error", func(ctx SpecContext) {
  169. Expect(pushErr).To(HaveOccurred())
  170. Expect(pushErr.Error()).To(ContainSubstring("failed to create secret"))
  171. })
  172. })
  173. })
  174. When("the secret exists", func() {
  175. var existingSecret *ngrok.Secret
  176. BeforeEach(func(ctx SpecContext) {
  177. existingSecret = &ngrok.Secret{
  178. ID: "sec-123",
  179. Name: ngrokSecretName,
  180. Vault: ngrok.Ref{
  181. ID: vault.ID,
  182. },
  183. }
  184. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  185. return fake.NewIter([]*ngrok.Secret{existingSecret}, nil)
  186. }
  187. secretsAPI.UpdateFn = func(ctx context.Context, req *ngrok.SecretUpdate) (*ngrok.Secret, error) {
  188. updateCalledWith = req
  189. return &ngrok.Secret{ID: req.ID}, nil
  190. }
  191. })
  192. It("should not return an error", func(ctx SpecContext) {
  193. Expect(pushErr).ToNot(HaveOccurred())
  194. })
  195. It("should update the ngrok secret description", func(ctx SpecContext) {
  196. Expect(updateCalledWith).ToNot(BeNil())
  197. Expect(updateCalledWith.Description).ToNot(BeNil())
  198. Expect(*updateCalledWith.Description).To(Equal(defaultDescription))
  199. })
  200. It("should update the ngrok secret metadata", func(ctx SpecContext) {
  201. Expect(updateCalledWith).ToNot(BeNil())
  202. Expect(updateCalledWith.Metadata).ToNot(BeNil())
  203. // sha256sum "new value" = 9c51d0b0f64dfb3662ed85ce945dd1e8f6130665c289754e4e9257a58013e61d
  204. Expect(*updateCalledWith.Metadata).To(Equal(`{"_sha256":"9c51d0b0f64dfb3662ed85ce945dd1e8f6130665c289754e4e9257a58013e61d"}`))
  205. })
  206. When("secret update fails", func() {
  207. BeforeEach(func() {
  208. secretsAPI.UpdateFn = func(ctx context.Context, req *ngrok.SecretUpdate) (*ngrok.Secret, error) {
  209. return nil, errors.New("failed to update secret")
  210. }
  211. })
  212. It("should return an error", func(ctx SpecContext) {
  213. Expect(pushErr).To(HaveOccurred())
  214. Expect(pushErr.Error()).To(ContainSubstring("failed to update secret"))
  215. })
  216. })
  217. When("The secret key is not specified on the push data", func() {
  218. BeforeEach(func() {
  219. pushData.Match.SecretKey = ""
  220. })
  221. It("should marshal the entire secret data as JSON", func(ctx SpecContext) {
  222. Expect(updateCalledWith).ToNot(BeNil())
  223. Expect(updateCalledWith.Metadata).ToNot(BeNil())
  224. data := map[string]string{}
  225. err := json.Unmarshal([]byte(*updateCalledWith.Metadata), &data)
  226. Expect(err).ToNot(HaveOccurred())
  227. Expect(data).To(HaveKeyWithValue("_sha256", "146ed8bb7a977ee78ee11cf262924e3ae93423c413ab6d612a8d159a0ae4e1ad"))
  228. })
  229. })
  230. When("the secret key does not exist in the k8s secret", func() {
  231. BeforeEach(func() {
  232. pushData.Match.SecretKey = "nonexistent-key"
  233. })
  234. It("should return an error", func(ctx SpecContext) {
  235. Expect(pushErr).To(HaveOccurred())
  236. Expect(pushErr.Error()).To(ContainSubstring("key nonexistent-key not found in secret"))
  237. })
  238. })
  239. When("push metadata is provided", func() {
  240. When("the metadata is valid", func() {
  241. BeforeEach(func() {
  242. pushData.Metadata = &apiextensionsv1.JSON{
  243. Raw: []byte(`
  244. apiVersion: kubernetes.external-secrets.io/v1alpha1
  245. kind: PushSecretMetadata
  246. spec:
  247. metadata:
  248. environment: production
  249. team: frontend
  250. description: "my custom description"`),
  251. }
  252. })
  253. It("should update the ngrok secret description", func(ctx SpecContext) {
  254. Expect(updateCalledWith).ToNot(BeNil())
  255. Expect(updateCalledWith.Description).ToNot(BeNil())
  256. Expect(*updateCalledWith.Description).To(Equal("my custom description"))
  257. })
  258. It("should update the ngrok secret metadata", func(ctx SpecContext) {
  259. Expect(updateCalledWith).ToNot(BeNil())
  260. Expect(updateCalledWith.Metadata).ToNot(BeNil())
  261. data := map[string]string{}
  262. err := json.Unmarshal([]byte(*updateCalledWith.Metadata), &data)
  263. Expect(err).ToNot(HaveOccurred())
  264. Expect(data).To(HaveKeyWithValue("environment", "production"))
  265. Expect(data).To(HaveKeyWithValue("team", "frontend"))
  266. Expect(data).To(HaveKeyWithValue("_sha256", "9c51d0b0f64dfb3662ed85ce945dd1e8f6130665c289754e4e9257a58013e61d"))
  267. })
  268. })
  269. When("the metadata is invalid", func() {
  270. BeforeEach(func() {
  271. pushData.Metadata = &apiextensionsv1.JSON{
  272. Raw: []byte(`{ this is not valid json`),
  273. }
  274. })
  275. It("should return an error", func(ctx SpecContext) {
  276. Expect(pushErr).To(HaveOccurred())
  277. Expect(pushErr.Error()).To(ContainSubstring("failed to parse push secret metadata"))
  278. })
  279. })
  280. })
  281. })
  282. })
  283. When("the cached vault ID no longer matches the vault name", func() {
  284. BeforeEach(func() {
  285. // Seed a stale cached vault ID. The vault it points to has since
  286. // been renamed, so the client must refresh the ID before pushing.
  287. vault = &ngrok.Vault{ID: "stale-vault-id", Name: vaultName}
  288. vaultsAPI.GetFn = func(ctx context.Context, id string) (*ngrok.Vault, error) {
  289. return &ngrok.Vault{ID: id, Name: "renamed-out-from-under-us"}, nil
  290. }
  291. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  292. return fake.NewIter([]*ngrok.Vault{{ID: "fresh-vault-id", Name: vaultName}}, nil)
  293. }
  294. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  295. return fake.NewIter([]*ngrok.Secret{}, nil)
  296. }
  297. secretsAPI.CreateFn = func(ctx context.Context, req *ngrok.SecretCreate) (*ngrok.Secret, error) {
  298. createCalledWith = req
  299. return &ngrok.Secret{ID: "sec-123"}, nil
  300. }
  301. })
  302. It("should refresh the vault ID and push using the refreshed ID", func(ctx SpecContext) {
  303. Expect(pushErr).ToNot(HaveOccurred())
  304. Expect(createCalledWith).ToNot(BeNil())
  305. Expect(createCalledWith.VaultID).To(Equal("fresh-vault-id"))
  306. })
  307. })
  308. })
  309. Describe("SecretExists", func() {
  310. var (
  311. secretName string
  312. exists bool
  313. err error
  314. )
  315. BeforeEach(func() {
  316. secretName = "my-secret"
  317. })
  318. JustBeforeEach(func(ctx SpecContext) {
  319. exists, err = c.SecretExists(ctx, pushSecretRemoteRef{
  320. remoteKey: secretName,
  321. })
  322. })
  323. When("the vault does not exist", func() {
  324. BeforeEach(func() {
  325. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  326. return fake.NewIter([]*ngrok.Vault{}, nil)
  327. }
  328. })
  329. It("should return exists as false without an error", func(ctx SpecContext) {
  330. Expect(err).ToNot(HaveOccurred())
  331. Expect(exists).To(BeFalse())
  332. })
  333. })
  334. When("the vault exists", func() {
  335. var vault *ngrok.Vault
  336. BeforeEach(func(ctx SpecContext) {
  337. vault = &ngrok.Vault{ID: "vault-123", Name: vaultName}
  338. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  339. return fake.NewIter([]*ngrok.Vault{vault}, nil)
  340. }
  341. })
  342. When("the secret does not exist", func() {
  343. BeforeEach(func() {
  344. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  345. return fake.NewIter([]*ngrok.Secret{}, nil)
  346. }
  347. })
  348. It("should return exists as false without an error", func(ctx SpecContext) {
  349. Expect(err).ToNot(HaveOccurred())
  350. Expect(exists).To(BeFalse())
  351. })
  352. })
  353. When("the secret exists", func() {
  354. BeforeEach(func(ctx SpecContext) {
  355. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  356. return fake.NewIter([]*ngrok.Secret{
  357. {ID: "sec-123", Name: secretName, Vault: ngrok.Ref{ID: vault.ID}},
  358. }, nil)
  359. }
  360. })
  361. It("should return exists as true without an error", func(ctx SpecContext) {
  362. Expect(err).ToNot(HaveOccurred())
  363. Expect(exists).To(BeTrue())
  364. })
  365. })
  366. When("an error occurs listing secrets", func() {
  367. BeforeEach(func(ctx SpecContext) {
  368. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  369. return fake.NewIter([]*ngrok.Secret{}, errors.New("failed to list secrets"))
  370. }
  371. })
  372. It("should return an error", func(ctx SpecContext) {
  373. Expect(err).To(HaveOccurred())
  374. Expect(err.Error()).To(ContainSubstring("error fetching secret: failed to list secrets"))
  375. })
  376. })
  377. })
  378. When("an error occurs listing vaults", func() {
  379. BeforeEach(func() {
  380. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  381. return fake.NewIter([]*ngrok.Vault{}, errors.New("failed to list vaults"))
  382. }
  383. })
  384. It("should return exists as false", func() {
  385. Expect(exists).To(BeFalse())
  386. })
  387. It("should return the listing error", func() {
  388. Expect(err).To(HaveOccurred())
  389. Expect(err.Error()).To(ContainSubstring("failed to list vaults"))
  390. })
  391. })
  392. })
  393. Describe("getVaultByName", func() {
  394. var (
  395. vault *ngrok.Vault
  396. fetched *ngrok.Vault
  397. err error
  398. listPaging *ngrok.FilteredPaging
  399. )
  400. BeforeEach(func(ctx SpecContext) {
  401. vault = &ngrok.Vault{ID: "vault-123", Name: vaultName}
  402. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  403. listPaging = paging
  404. return fake.NewIter([]*ngrok.Vault{vault}, nil)
  405. }
  406. })
  407. JustBeforeEach(func(ctx SpecContext) {
  408. fetched, err = c.getVaultByName(ctx, vaultName)
  409. })
  410. It("should return the matching vault", func() {
  411. Expect(err).ToNot(HaveOccurred())
  412. Expect(fetched).ToNot(BeNil())
  413. Expect(fetched.ID).To(Equal(vault.ID))
  414. })
  415. It("should filter vaults by name", func() {
  416. Expect(listPaging).ToNot(BeNil())
  417. Expect(listPaging.Filter).ToNot(BeNil())
  418. Expect(*listPaging.Filter).To(Equal(`obj.name == "test-vault"`))
  419. })
  420. })
  421. Describe("getSecretByVaultIDAndName", func() {
  422. var (
  423. targetVaultID string
  424. found *ngrok.Secret
  425. err error
  426. secretName string
  427. listPaging *ngrok.FilteredPaging
  428. )
  429. BeforeEach(func(ctx SpecContext) {
  430. secretName = "shared-name"
  431. targetVaultID = "vault-target"
  432. })
  433. JustBeforeEach(func(ctx SpecContext) {
  434. found, err = c.getSecretByVaultIDAndName(ctx, targetVaultID, secretName)
  435. })
  436. When("a secret with the same name exists in multiple vaults", func() {
  437. BeforeEach(func(ctx SpecContext) {
  438. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  439. listPaging = paging
  440. return fake.NewIter([]*ngrok.Secret{
  441. {ID: "sec-1", Name: secretName, Vault: ngrok.Ref{ID: "vault-other"}},
  442. {ID: "sec-2", Name: secretName, Vault: ngrok.Ref{ID: targetVaultID}},
  443. }, nil)
  444. }
  445. })
  446. It("should return the secret from the target vault", func() {
  447. Expect(err).ToNot(HaveOccurred())
  448. Expect(found).ToNot(BeNil())
  449. Expect(found.ID).To(Equal("sec-2"))
  450. Expect(found.Vault.ID).To(Equal(targetVaultID))
  451. })
  452. It("should filter secrets by name before checking the vault", func() {
  453. Expect(listPaging).ToNot(BeNil())
  454. Expect(listPaging.Filter).ToNot(BeNil())
  455. Expect(*listPaging.Filter).To(Equal(`obj.name == "shared-name"`))
  456. })
  457. })
  458. When("only another vault has a secret with that name", func() {
  459. BeforeEach(func(ctx SpecContext) {
  460. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  461. return fake.NewIter([]*ngrok.Secret{
  462. {ID: "sec-1", Name: secretName, Vault: ngrok.Ref{ID: "vault-other"}},
  463. }, nil)
  464. }
  465. })
  466. It("should report the secret as missing from the target vault", func() {
  467. Expect(found).To(BeNil())
  468. Expect(err).To(HaveOccurred())
  469. Expect(err).To(MatchError(ContainSubstring("secret 'shared-name' does not exist")))
  470. Expect(err).To(MatchError(ContainSubstring(errVaultSecretDoesNotExist.Error())))
  471. })
  472. })
  473. })
  474. Describe("DeleteSecret", func() {
  475. var (
  476. secretName string
  477. err error
  478. deletedID string
  479. )
  480. BeforeEach(func() {
  481. secretName = "my-secret"
  482. deletedID = ""
  483. })
  484. JustBeforeEach(func(ctx SpecContext) {
  485. err = c.DeleteSecret(ctx, pushSecretRemoteRef{
  486. remoteKey: secretName,
  487. })
  488. })
  489. When("the vault does not exist", func() {
  490. BeforeEach(func() {
  491. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  492. return fake.NewIter([]*ngrok.Vault{}, nil)
  493. }
  494. })
  495. It("should not return an error", func(ctx SpecContext) {
  496. Expect(err).ToNot(HaveOccurred())
  497. })
  498. })
  499. When("the vault exists but the secret does not", func() {
  500. BeforeEach(func(ctx SpecContext) {
  501. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  502. return fake.NewIter([]*ngrok.Vault{{ID: "vault-123", Name: vaultName}}, nil)
  503. }
  504. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  505. return fake.NewIter([]*ngrok.Secret{}, nil)
  506. }
  507. })
  508. It("should not return an error", func(ctx SpecContext) {
  509. Expect(err).ToNot(HaveOccurred())
  510. })
  511. })
  512. When("the vault and secret both exist", func() {
  513. BeforeEach(func(ctx SpecContext) {
  514. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  515. return fake.NewIter([]*ngrok.Vault{{ID: "vault-123", Name: vaultName}}, nil)
  516. }
  517. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  518. return fake.NewIter([]*ngrok.Secret{
  519. {ID: "sec-123", Name: secretName, Vault: ngrok.Ref{ID: "vault-123"}},
  520. }, nil)
  521. }
  522. secretsAPI.DeleteFn = func(ctx context.Context, id string) error {
  523. deletedID = id
  524. return nil
  525. }
  526. })
  527. It("should not return an error and delete is called", func(ctx SpecContext) {
  528. Expect(err).ToNot(HaveOccurred())
  529. Expect(deletedID).To(Equal("sec-123"))
  530. })
  531. When("secret deletion fails", func() {
  532. BeforeEach(func(ctx SpecContext) {
  533. secretsAPI.DeleteFn = func(ctx context.Context, id string) error {
  534. return errors.New("failed to delete secret")
  535. }
  536. })
  537. It("should return an error", func(ctx SpecContext) {
  538. Expect(err).To(HaveOccurred())
  539. Expect(err.Error()).To(ContainSubstring("failed to delete secret"))
  540. })
  541. })
  542. })
  543. When("an error occurs listing secrets", func() {
  544. BeforeEach(func(ctx SpecContext) {
  545. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  546. return fake.NewIter([]*ngrok.Vault{{ID: "vault-123", Name: vaultName}}, nil)
  547. }
  548. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  549. return fake.NewIter([]*ngrok.Secret{}, errors.New("failed to list secrets"))
  550. }
  551. })
  552. It("should return an error", func(ctx SpecContext) {
  553. Expect(err).To(HaveOccurred())
  554. Expect(err.Error()).To(ContainSubstring("failed to list secrets"))
  555. })
  556. })
  557. When("an error occurs listing vaults", func() {
  558. BeforeEach(func() {
  559. vaultsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Vault] {
  560. return fake.NewIter([]*ngrok.Vault{}, errors.New("failed to list vaults"))
  561. }
  562. })
  563. It("should return the listing error", func() {
  564. Expect(err).To(HaveOccurred())
  565. Expect(err.Error()).To(ContainSubstring("failed to list vaults"))
  566. })
  567. })
  568. })
  569. Describe("Validate", func() {
  570. var (
  571. result esv1.ValidationResult
  572. err error
  573. )
  574. JustBeforeEach(func(ctx SpecContext) {
  575. result, err = c.Validate()
  576. })
  577. When("the client can list secrets", func() {
  578. When("there are no secrets", func() {
  579. BeforeEach(func() {
  580. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  581. return fake.NewIter([]*ngrok.Secret{}, nil)
  582. }
  583. })
  584. It("should return ValidationResultReady without an error", func() {
  585. Expect(err).To(BeNil())
  586. Expect(result).To(Equal(esv1.ValidationResultReady))
  587. })
  588. })
  589. When("there are some secrets", func() {
  590. BeforeEach(func(ctx SpecContext) {
  591. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  592. return fake.NewIter([]*ngrok.Secret{{ID: "sec-1"}}, nil)
  593. }
  594. })
  595. It("should return ValidationResultReady without an error", func() {
  596. Expect(err).To(BeNil())
  597. Expect(result).To(Equal(esv1.ValidationResultReady))
  598. })
  599. })
  600. })
  601. When("the client cannot list secrets", func() {
  602. BeforeEach(func() {
  603. secretsAPI.ListFn = func(paging *ngrok.FilteredPaging) ngrok.Iter[*ngrok.Secret] {
  604. return fake.NewIter([]*ngrok.Secret{}, errors.New("failed to list secrets"))
  605. }
  606. })
  607. It("should return ValidationResultError with the listing error", func() {
  608. Expect(err).ToNot(BeNil())
  609. Expect(err.Error()).To(ContainSubstring("failed to list secrets"))
  610. Expect(result).To(Equal(esv1.ValidationResultError))
  611. })
  612. })
  613. })
  614. Describe("GetSecret", func() {
  615. var (
  616. ref esv1.ExternalSecretDataRemoteRef
  617. secret []byte
  618. err error
  619. )
  620. JustBeforeEach(func(ctx SpecContext) {
  621. secret, err = c.GetSecret(ctx, ref)
  622. })
  623. It("should always return an error indicating write-only operations", func(ctx SpecContext) {
  624. Expect(secret).To(BeNil())
  625. Expect(err).To(HaveOccurred())
  626. Expect(err).To(Equal(errWriteOnlyOperations))
  627. })
  628. })
  629. Describe("GetSecretMap", func() {
  630. var (
  631. ref esv1.ExternalSecretDataRemoteRef
  632. secretMap map[string][]byte
  633. err error
  634. )
  635. JustBeforeEach(func(ctx SpecContext) {
  636. secretMap, err = c.GetSecretMap(ctx, ref)
  637. })
  638. It("should always return an error indicating write-only operations", func(ctx SpecContext) {
  639. Expect(secretMap).To(BeNil())
  640. Expect(err).To(HaveOccurred())
  641. Expect(err).To(Equal(errWriteOnlyOperations))
  642. })
  643. })
  644. Describe("GetAllSecrets", func() {
  645. var (
  646. find esv1.ExternalSecretFind
  647. secrets map[string][]byte
  648. err error
  649. )
  650. JustBeforeEach(func(ctx SpecContext) {
  651. secrets, err = c.GetAllSecrets(ctx, find)
  652. })
  653. It("should always return an error indicating write-only operations", func(ctx SpecContext) {
  654. Expect(secrets).To(BeNil())
  655. Expect(err).To(HaveOccurred())
  656. Expect(err).To(Equal(errWriteOnlyOperations))
  657. })
  658. })
  659. Describe("Close", func() {
  660. It("should not return an error", func(ctx SpecContext) {
  661. Expect(c.Close(ctx)).To(BeNil())
  662. })
  663. })
  664. })