client_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  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 kubernetes
  14. import (
  15. "context"
  16. "errors"
  17. "reflect"
  18. "strings"
  19. "testing"
  20. "github.com/google/go-cmp/cmp"
  21. "github.com/stretchr/testify/assert"
  22. v1 "k8s.io/api/core/v1"
  23. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  24. apierrors "k8s.io/apimachinery/pkg/api/errors"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/runtime/schema"
  27. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  28. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  29. testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
  30. )
  31. const (
  32. errSomethingWentWrong = "Something went wrong"
  33. )
  34. type fakeClient struct {
  35. t *testing.T
  36. secretMap map[string]*v1.Secret
  37. expectedListOptions metav1.ListOptions
  38. err error
  39. }
  40. func (fk *fakeClient) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.Secret, error) {
  41. if fk.err != nil {
  42. return nil, fk.err
  43. }
  44. secret, ok := fk.secretMap[name]
  45. if !ok {
  46. return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, "secret")
  47. }
  48. // return inmutable to simulate external system and avoid accidental side effects
  49. sCopy := secret.DeepCopy()
  50. // update operation requires to relate names
  51. sCopy.Name = name
  52. return sCopy, nil
  53. }
  54. func (fk *fakeClient) List(_ context.Context, opts metav1.ListOptions) (*v1.SecretList, error) {
  55. assert.Equal(fk.t, fk.expectedListOptions, opts)
  56. list := &v1.SecretList{}
  57. for _, v := range fk.secretMap {
  58. list.Items = append(list.Items, *v)
  59. }
  60. return list, nil
  61. }
  62. func (fk *fakeClient) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error {
  63. if fk.err != nil {
  64. return fk.err
  65. }
  66. _, ok := fk.secretMap[name]
  67. if !ok {
  68. return apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, "secret")
  69. }
  70. delete(fk.secretMap, name)
  71. return nil
  72. }
  73. func (fk *fakeClient) Create(_ context.Context, secret *v1.Secret, _ metav1.CreateOptions) (*v1.Secret, error) {
  74. s := &v1.Secret{
  75. Data: secret.Data,
  76. ObjectMeta: secret.ObjectMeta,
  77. Type: secret.Type,
  78. }
  79. fk.secretMap[secret.Name] = s
  80. return s, nil
  81. }
  82. func (fk *fakeClient) Update(_ context.Context, secret *v1.Secret, _ metav1.UpdateOptions) (*v1.Secret, error) {
  83. s, ok := fk.secretMap[secret.Name]
  84. if !ok {
  85. return nil, errors.New("error while updating secret")
  86. }
  87. s.ObjectMeta = secret.ObjectMeta
  88. s.Data = secret.Data
  89. return s, nil
  90. }
  91. var binaryTestData = []byte{0x00, 0xff, 0x00, 0xff, 0xac, 0xab, 0x28, 0x21}
  92. func TestGetSecret(t *testing.T) {
  93. tests := []struct {
  94. desc string
  95. secrets map[string]*v1.Secret
  96. clientErr error
  97. ref esv1.ExternalSecretDataRemoteRef
  98. want []byte
  99. wantErr string
  100. }{
  101. {
  102. desc: "secret data with correct property",
  103. secrets: map[string]*v1.Secret{
  104. "mysec": {
  105. Data: map[string][]byte{
  106. "token": []byte(`foobar`),
  107. },
  108. },
  109. },
  110. ref: esv1.ExternalSecretDataRemoteRef{
  111. Key: "mysec",
  112. Property: "token",
  113. },
  114. want: []byte(`foobar`),
  115. },
  116. {
  117. desc: "secret data with multi level property",
  118. secrets: map[string]*v1.Secret{
  119. "mysec": {
  120. Data: map[string][]byte{
  121. "foo": []byte(`{"huga":{"bar":"val"}}`),
  122. },
  123. },
  124. },
  125. ref: esv1.ExternalSecretDataRemoteRef{
  126. Key: "mysec",
  127. Property: "foo.huga.bar",
  128. },
  129. want: []byte(`val`),
  130. },
  131. {
  132. desc: "secret data with property containing .",
  133. secrets: map[string]*v1.Secret{
  134. "mysec": {
  135. Data: map[string][]byte{
  136. "foo.png": []byte(`correct`),
  137. "foo": []byte(`{"png":"wrong"}`),
  138. },
  139. },
  140. },
  141. ref: esv1.ExternalSecretDataRemoteRef{
  142. Key: "mysec",
  143. Property: "foo.png",
  144. },
  145. want: []byte(`correct`),
  146. },
  147. {
  148. desc: "secret data contains html characters",
  149. secrets: map[string]*v1.Secret{
  150. "mysec": {
  151. Data: map[string][]byte{
  152. "html": []byte(`<foobar>`),
  153. },
  154. },
  155. },
  156. ref: esv1.ExternalSecretDataRemoteRef{
  157. Key: "mysec",
  158. },
  159. want: []byte(`{"html":"<foobar>"}`),
  160. },
  161. {
  162. desc: "secret metadata contains html characters",
  163. secrets: map[string]*v1.Secret{
  164. "mysec": {
  165. ObjectMeta: metav1.ObjectMeta{
  166. Annotations: map[string]string{"date": "today"},
  167. Labels: map[string]string{"dev": "<seb>"},
  168. },
  169. },
  170. },
  171. ref: esv1.ExternalSecretDataRemoteRef{
  172. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  173. Key: "mysec",
  174. },
  175. want: []byte(`{"annotations":{"date":"today"},"labels":{"dev":"<seb>"}}`),
  176. },
  177. {
  178. desc: "secret data contains binary",
  179. secrets: map[string]*v1.Secret{
  180. "mysec": {
  181. Data: map[string][]byte{
  182. "bindata": binaryTestData,
  183. },
  184. },
  185. },
  186. ref: esv1.ExternalSecretDataRemoteRef{
  187. Key: "mysec",
  188. Property: "bindata",
  189. },
  190. want: binaryTestData,
  191. },
  192. {
  193. desc: "secret data without property",
  194. secrets: map[string]*v1.Secret{
  195. "mysec": {
  196. Data: map[string][]byte{
  197. "token": []byte(`foobar`),
  198. },
  199. },
  200. },
  201. ref: esv1.ExternalSecretDataRemoteRef{
  202. Key: "mysec",
  203. },
  204. want: []byte(`{"token":"foobar"}`),
  205. },
  206. {
  207. desc: "secret metadata without property",
  208. secrets: map[string]*v1.Secret{
  209. "mysec": {
  210. ObjectMeta: metav1.ObjectMeta{
  211. Annotations: map[string]string{"date": "today"},
  212. Labels: map[string]string{"dev": "seb"},
  213. },
  214. },
  215. },
  216. ref: esv1.ExternalSecretDataRemoteRef{
  217. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  218. Key: "mysec",
  219. },
  220. want: []byte(`{"annotations":{"date":"today"},"labels":{"dev":"seb"}}`),
  221. },
  222. {
  223. desc: "secret metadata with single level property",
  224. secrets: map[string]*v1.Secret{
  225. "mysec": {
  226. ObjectMeta: metav1.ObjectMeta{
  227. Annotations: map[string]string{"date": "today"},
  228. Labels: map[string]string{"dev": "seb"},
  229. },
  230. },
  231. },
  232. ref: esv1.ExternalSecretDataRemoteRef{
  233. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  234. Key: "mysec",
  235. Property: "labels",
  236. },
  237. want: []byte(`{"dev":"seb"}`),
  238. },
  239. {
  240. desc: "secret metadata with multiple level property",
  241. secrets: map[string]*v1.Secret{
  242. "mysec": {
  243. ObjectMeta: metav1.ObjectMeta{
  244. Annotations: map[string]string{"date": "today"},
  245. Labels: map[string]string{"dev": "seb"},
  246. },
  247. },
  248. },
  249. ref: esv1.ExternalSecretDataRemoteRef{
  250. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  251. Key: "mysec",
  252. Property: "labels.dev",
  253. },
  254. want: []byte(`seb`),
  255. },
  256. {
  257. desc: "secret is not found",
  258. clientErr: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, "secret"),
  259. ref: esv1.ExternalSecretDataRemoteRef{
  260. Key: "mysec",
  261. Property: "token",
  262. },
  263. wantErr: `Secret "secret" not found`,
  264. },
  265. {
  266. desc: "secret data with wrong property",
  267. secrets: map[string]*v1.Secret{
  268. "mysec": {
  269. Data: map[string][]byte{
  270. "token": []byte(`foobar`),
  271. },
  272. },
  273. },
  274. ref: esv1.ExternalSecretDataRemoteRef{
  275. Key: "mysec",
  276. Property: "not-the-token",
  277. },
  278. wantErr: "property not-the-token does not exist in data of secret",
  279. },
  280. {
  281. desc: "secret metadata with wrong property",
  282. secrets: map[string]*v1.Secret{
  283. "mysec": {
  284. ObjectMeta: metav1.ObjectMeta{
  285. Annotations: map[string]string{"date": "today"},
  286. Labels: map[string]string{"dev": "seb"},
  287. },
  288. },
  289. },
  290. ref: esv1.ExternalSecretDataRemoteRef{
  291. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  292. Key: "mysec",
  293. Property: "foo",
  294. },
  295. wantErr: "property foo does not exist in metadata of secret",
  296. },
  297. }
  298. for _, tt := range tests {
  299. t.Run(tt.desc, func(t *testing.T) {
  300. p := &Client{
  301. userSecretClient: &fakeClient{t: t, secretMap: tt.secrets, err: tt.clientErr},
  302. namespace: "default",
  303. }
  304. got, err := p.GetSecret(context.Background(), tt.ref)
  305. if err != nil {
  306. if tt.wantErr == "" {
  307. t.Fatalf("failed to call GetSecret: %v", err)
  308. }
  309. if !strings.Contains(err.Error(), tt.wantErr) {
  310. t.Fatalf("received an unexpected error: %q should have contained %q", err.Error(), tt.wantErr)
  311. }
  312. return
  313. }
  314. if tt.wantErr != "" {
  315. t.Fatalf("expected to receive an error but got nil")
  316. }
  317. if !reflect.DeepEqual(got, tt.want) {
  318. t.Fatalf("received an unexpected secret: got: %s, want %s", got, tt.want)
  319. }
  320. })
  321. }
  322. }
  323. func TestGetSecretMap(t *testing.T) {
  324. type fields struct {
  325. Client KClient
  326. ReviewClient RClient
  327. Namespace string
  328. }
  329. tests := []struct {
  330. name string
  331. fields fields
  332. ref esv1.ExternalSecretDataRemoteRef
  333. want map[string][]byte
  334. wantErr bool
  335. }{
  336. {
  337. name: "successful case metadata without property",
  338. fields: fields{
  339. Client: &fakeClient{
  340. t: t,
  341. secretMap: map[string]*v1.Secret{
  342. "mysec": {
  343. ObjectMeta: metav1.ObjectMeta{
  344. Annotations: map[string]string{"date": "today"},
  345. Labels: map[string]string{"dev": "seb"},
  346. },
  347. },
  348. },
  349. },
  350. Namespace: "default",
  351. },
  352. ref: esv1.ExternalSecretDataRemoteRef{
  353. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  354. Key: "mysec",
  355. },
  356. want: map[string][]byte{"annotations": []byte("{\"date\":\"today\"}"), "labels": []byte("{\"dev\":\"seb\"}")},
  357. },
  358. {
  359. name: "successful case metadata with single property",
  360. fields: fields{
  361. Client: &fakeClient{
  362. t: t,
  363. secretMap: map[string]*v1.Secret{
  364. "mysec": {
  365. ObjectMeta: metav1.ObjectMeta{
  366. Annotations: map[string]string{"date": "today"},
  367. Labels: map[string]string{"dev": "seb"},
  368. },
  369. },
  370. },
  371. },
  372. Namespace: "default",
  373. },
  374. ref: esv1.ExternalSecretDataRemoteRef{
  375. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  376. Key: "mysec",
  377. Property: "labels",
  378. },
  379. want: map[string][]byte{"dev": []byte("\"seb\"")},
  380. },
  381. {
  382. name: "error case metadata with wrong property",
  383. fields: fields{
  384. Client: &fakeClient{
  385. t: t,
  386. secretMap: map[string]*v1.Secret{
  387. "mysec": {
  388. ObjectMeta: metav1.ObjectMeta{
  389. Annotations: map[string]string{"date": "today"},
  390. Labels: map[string]string{"dev": "seb"},
  391. },
  392. },
  393. },
  394. },
  395. Namespace: "default",
  396. },
  397. ref: esv1.ExternalSecretDataRemoteRef{
  398. MetadataPolicy: esv1.ExternalSecretMetadataPolicyFetch,
  399. Key: "mysec",
  400. Property: "foo",
  401. },
  402. wantErr: true,
  403. },
  404. }
  405. for _, tt := range tests {
  406. t.Run(tt.name, func(t *testing.T) {
  407. p := &Client{
  408. userSecretClient: tt.fields.Client,
  409. userReviewClient: tt.fields.ReviewClient,
  410. namespace: tt.fields.Namespace,
  411. }
  412. got, err := p.GetSecretMap(context.Background(), tt.ref)
  413. if (err != nil) != tt.wantErr {
  414. t.Errorf("ProviderKubernetes.GetSecretMap() error = %v, wantErr %v", err, tt.wantErr)
  415. return
  416. }
  417. if !reflect.DeepEqual(got, tt.want) {
  418. t.Errorf("ProviderKubernetes.GetSecretMap() = %v, want %v", got, tt.want)
  419. }
  420. })
  421. }
  422. }
  423. func TestGetAllSecrets(t *testing.T) {
  424. type fields struct {
  425. Client KClient
  426. ReviewClient RClient
  427. Namespace string
  428. }
  429. type args struct {
  430. ctx context.Context
  431. ref esv1.ExternalSecretFind
  432. }
  433. tests := []struct {
  434. name string
  435. fields fields
  436. args args
  437. want map[string][]byte
  438. wantErr bool
  439. }{
  440. {
  441. name: "use regex",
  442. fields: fields{
  443. Client: &fakeClient{
  444. t: t,
  445. secretMap: map[string]*v1.Secret{
  446. "mysec": {
  447. ObjectMeta: metav1.ObjectMeta{
  448. Name: "mysec",
  449. },
  450. Data: map[string][]byte{
  451. "token": []byte(`foo`),
  452. },
  453. },
  454. "other": {
  455. ObjectMeta: metav1.ObjectMeta{
  456. Name: "other",
  457. },
  458. Data: map[string][]byte{
  459. "token": []byte(`bar`),
  460. },
  461. },
  462. },
  463. },
  464. },
  465. args: args{
  466. ref: esv1.ExternalSecretFind{
  467. Name: &esv1.FindName{
  468. RegExp: "other",
  469. },
  470. },
  471. },
  472. want: map[string][]byte{
  473. "other": []byte(`{"token":"bar"}`),
  474. },
  475. },
  476. {
  477. name: "use tags/labels",
  478. fields: fields{
  479. Client: &fakeClient{
  480. t: t,
  481. expectedListOptions: metav1.ListOptions{
  482. LabelSelector: "app=foobar",
  483. },
  484. secretMap: map[string]*v1.Secret{
  485. "mysec": {
  486. ObjectMeta: metav1.ObjectMeta{
  487. Name: "mysec",
  488. },
  489. Data: map[string][]byte{
  490. "token": []byte(`foo`),
  491. },
  492. },
  493. "other": {
  494. ObjectMeta: metav1.ObjectMeta{
  495. Name: "other",
  496. },
  497. Data: map[string][]byte{
  498. "token": []byte(`bar`),
  499. },
  500. },
  501. },
  502. },
  503. },
  504. args: args{
  505. ref: esv1.ExternalSecretFind{
  506. Tags: map[string]string{
  507. "app": "foobar",
  508. },
  509. },
  510. },
  511. want: map[string][]byte{
  512. "mysec": []byte(`{"token":"foo"}`),
  513. "other": []byte(`{"token":"bar"}`),
  514. },
  515. },
  516. }
  517. for _, tt := range tests {
  518. t.Run(tt.name, func(t *testing.T) {
  519. p := &Client{
  520. userSecretClient: tt.fields.Client,
  521. userReviewClient: tt.fields.ReviewClient,
  522. namespace: tt.fields.Namespace,
  523. }
  524. got, err := p.GetAllSecrets(tt.args.ctx, tt.args.ref)
  525. if (err != nil) != tt.wantErr {
  526. t.Errorf("ProviderKubernetes.GetAllSecrets() error = %v, wantErr %v", err, tt.wantErr)
  527. return
  528. }
  529. if !reflect.DeepEqual(got, tt.want) {
  530. t.Errorf("ProviderKubernetes.GetAllSecrets() = %v, want %v", got, tt.want)
  531. }
  532. })
  533. }
  534. }
  535. func TestDeleteSecret(t *testing.T) {
  536. type fields struct {
  537. Client KClient
  538. }
  539. tests := []struct {
  540. name string
  541. fields fields
  542. ref esv1.PushSecretRemoteRef
  543. wantSecretMap map[string]*v1.Secret
  544. wantErr bool
  545. }{
  546. {
  547. name: "refuse to delete without property",
  548. fields: fields{
  549. Client: &fakeClient{
  550. t: t,
  551. secretMap: map[string]*v1.Secret{
  552. "mysec": {
  553. Data: map[string][]byte{
  554. "token": []byte(`foobar`),
  555. },
  556. },
  557. },
  558. },
  559. },
  560. ref: v1alpha1.PushSecretRemoteRef{
  561. RemoteKey: "mysec",
  562. },
  563. wantErr: true,
  564. wantSecretMap: map[string]*v1.Secret{
  565. "mysec": {
  566. Data: map[string][]byte{
  567. "token": []byte(`foobar`),
  568. },
  569. },
  570. },
  571. },
  572. {
  573. name: "gracefully ignore not found secret",
  574. fields: fields{
  575. Client: &fakeClient{
  576. t: t,
  577. secretMap: map[string]*v1.Secret{},
  578. },
  579. },
  580. ref: v1alpha1.PushSecretRemoteRef{
  581. RemoteKey: "mysec",
  582. Property: "token",
  583. },
  584. wantErr: false,
  585. wantSecretMap: map[string]*v1.Secret{},
  586. },
  587. {
  588. name: "gracefully ignore not found property",
  589. fields: fields{
  590. Client: &fakeClient{
  591. t: t,
  592. secretMap: map[string]*v1.Secret{
  593. "mysec": {
  594. Data: map[string][]byte{
  595. "token": []byte(`foobar`),
  596. },
  597. },
  598. },
  599. },
  600. },
  601. ref: v1alpha1.PushSecretRemoteRef{
  602. RemoteKey: "mysec",
  603. Property: "secret",
  604. },
  605. wantErr: false,
  606. wantSecretMap: map[string]*v1.Secret{
  607. "mysec": {
  608. Data: map[string][]byte{
  609. "token": []byte(`foobar`),
  610. },
  611. },
  612. },
  613. },
  614. {
  615. name: "unexpected lookup error",
  616. fields: fields{
  617. Client: &fakeClient{
  618. t: t,
  619. secretMap: map[string]*v1.Secret{
  620. "mysec": {
  621. Data: map[string][]byte{
  622. "token": []byte(`foobar`),
  623. },
  624. },
  625. },
  626. err: errors.New(errSomethingWentWrong),
  627. },
  628. },
  629. ref: v1alpha1.PushSecretRemoteRef{
  630. RemoteKey: "mysec",
  631. },
  632. wantErr: true,
  633. wantSecretMap: map[string]*v1.Secret{
  634. "mysec": {
  635. Data: map[string][]byte{
  636. "token": []byte(`foobar`),
  637. },
  638. },
  639. },
  640. },
  641. {
  642. name: "delete whole secret if only property should be removed",
  643. fields: fields{
  644. Client: &fakeClient{
  645. t: t,
  646. secretMap: map[string]*v1.Secret{
  647. "mysec": {
  648. Data: map[string][]byte{
  649. "token": []byte(`foobar`),
  650. },
  651. },
  652. },
  653. },
  654. },
  655. ref: v1alpha1.PushSecretRemoteRef{
  656. RemoteKey: "mysec",
  657. Property: "token",
  658. },
  659. wantErr: false,
  660. wantSecretMap: map[string]*v1.Secret{},
  661. },
  662. {
  663. name: "multiple properties, just remove that one",
  664. fields: fields{
  665. Client: &fakeClient{
  666. t: t,
  667. secretMap: map[string]*v1.Secret{
  668. "mysec": {
  669. Data: map[string][]byte{
  670. "token": []byte(`foo`),
  671. "secret": []byte(`bar`),
  672. },
  673. },
  674. },
  675. },
  676. },
  677. ref: v1alpha1.PushSecretRemoteRef{
  678. RemoteKey: "mysec",
  679. Property: "token",
  680. },
  681. wantErr: false,
  682. wantSecretMap: map[string]*v1.Secret{
  683. "mysec": {
  684. ObjectMeta: metav1.ObjectMeta{
  685. Name: "mysec",
  686. },
  687. Data: map[string][]byte{
  688. "secret": []byte(`bar`),
  689. },
  690. },
  691. },
  692. },
  693. }
  694. for _, tt := range tests {
  695. t.Run(tt.name, func(t *testing.T) {
  696. p := &Client{
  697. userSecretClient: tt.fields.Client,
  698. }
  699. err := p.DeleteSecret(context.Background(), tt.ref)
  700. if (err != nil) != tt.wantErr {
  701. t.Errorf("ProviderKubernetes.DeleteSecret() error = %v, wantErr %v", err, tt.wantErr)
  702. return
  703. }
  704. fClient := tt.fields.Client.(*fakeClient)
  705. if diff := cmp.Diff(tt.wantSecretMap, fClient.secretMap); diff != "" {
  706. t.Errorf("Unexpected resulting secrets map: -want, +got :\n%s\n", diff)
  707. }
  708. })
  709. }
  710. }
  711. func TestPushSecret(t *testing.T) {
  712. secretKey := "secret-key"
  713. type fields struct {
  714. Client KClient
  715. }
  716. tests := []struct {
  717. name string
  718. fields fields
  719. data testingfake.PushSecretData
  720. secret *v1.Secret
  721. wantSecretMap map[string]*v1.Secret
  722. wantErr bool
  723. }{
  724. {
  725. name: "refuse to work without property if secret key is provided",
  726. fields: fields{
  727. Client: &fakeClient{
  728. t: t,
  729. secretMap: map[string]*v1.Secret{
  730. "mysec": {
  731. Data: map[string][]byte{
  732. "token": []byte(`foo`),
  733. },
  734. },
  735. },
  736. },
  737. },
  738. data: testingfake.PushSecretData{
  739. SecretKey: secretKey,
  740. RemoteKey: "mysec",
  741. },
  742. secret: &v1.Secret{
  743. Data: map[string][]byte{secretKey: []byte("bar")},
  744. },
  745. wantErr: true,
  746. wantSecretMap: map[string]*v1.Secret{
  747. "mysec": {
  748. Data: map[string][]byte{
  749. "token": []byte(`foo`),
  750. },
  751. },
  752. },
  753. },
  754. {
  755. name: "push the whole secret if neither remote property or secretKey is defined but keep existing keys",
  756. fields: fields{
  757. Client: &fakeClient{
  758. t: t,
  759. secretMap: map[string]*v1.Secret{
  760. "mysec": {
  761. Data: map[string][]byte{
  762. "token": []byte(`foo`),
  763. },
  764. },
  765. },
  766. },
  767. },
  768. data: testingfake.PushSecretData{
  769. RemoteKey: "mysec",
  770. },
  771. secret: &v1.Secret{
  772. Data: map[string][]byte{"token2": []byte("foo")},
  773. },
  774. wantSecretMap: map[string]*v1.Secret{
  775. "mysec": {
  776. ObjectMeta: metav1.ObjectMeta{
  777. Name: "mysec",
  778. Labels: map[string]string{},
  779. Annotations: map[string]string{},
  780. },
  781. Data: map[string][]byte{
  782. "token": []byte(`foo`),
  783. "token2": []byte(`foo`),
  784. },
  785. },
  786. },
  787. },
  788. {
  789. name: "push the whole secret while secret exists into a single property",
  790. fields: fields{
  791. Client: &fakeClient{
  792. t: t,
  793. secretMap: map[string]*v1.Secret{
  794. "mysec": {
  795. Data: map[string][]byte{
  796. "token": []byte(`foo`),
  797. },
  798. },
  799. },
  800. },
  801. },
  802. data: testingfake.PushSecretData{
  803. RemoteKey: "mysec",
  804. Property: "token",
  805. },
  806. secret: &v1.Secret{
  807. Data: map[string][]byte{"foo": []byte("bar")},
  808. },
  809. wantSecretMap: map[string]*v1.Secret{
  810. "mysec": {
  811. ObjectMeta: metav1.ObjectMeta{
  812. Name: "mysec",
  813. Labels: map[string]string{},
  814. Annotations: map[string]string{},
  815. },
  816. Data: map[string][]byte{
  817. "token": []byte(`{"foo":"bar"}`),
  818. },
  819. },
  820. },
  821. },
  822. {
  823. name: "push the whole secret while secret exists but new property is defined should update the secret and keep existing key",
  824. fields: fields{
  825. Client: &fakeClient{
  826. t: t,
  827. secretMap: map[string]*v1.Secret{
  828. "mysec": {
  829. Data: map[string][]byte{
  830. "token": []byte(`foo`),
  831. },
  832. },
  833. },
  834. },
  835. },
  836. data: testingfake.PushSecretData{
  837. RemoteKey: "mysec",
  838. Property: "token2",
  839. },
  840. secret: &v1.Secret{
  841. Data: map[string][]byte{"foo": []byte("bar")},
  842. },
  843. wantSecretMap: map[string]*v1.Secret{
  844. "mysec": {
  845. ObjectMeta: metav1.ObjectMeta{
  846. Name: "mysec",
  847. Labels: map[string]string{},
  848. Annotations: map[string]string{},
  849. },
  850. Data: map[string][]byte{
  851. "token": []byte(`foo`),
  852. "token2": []byte(`{"foo":"bar"}`),
  853. },
  854. },
  855. },
  856. },
  857. {
  858. name: "push the whole secret as json if remote property is defined but secret key is not given",
  859. fields: fields{
  860. Client: &fakeClient{
  861. t: t,
  862. secretMap: map[string]*v1.Secret{},
  863. },
  864. },
  865. data: testingfake.PushSecretData{
  866. RemoteKey: "mysec",
  867. Property: "marshaled",
  868. },
  869. secret: &v1.Secret{
  870. Data: map[string][]byte{
  871. "token": []byte("foo"),
  872. "token2": []byte("2"),
  873. },
  874. },
  875. wantSecretMap: map[string]*v1.Secret{
  876. "mysec": {
  877. ObjectMeta: metav1.ObjectMeta{
  878. Name: "mysec",
  879. Labels: map[string]string{},
  880. Annotations: map[string]string{},
  881. },
  882. Data: map[string][]byte{
  883. "marshaled": []byte(`{"token":"foo","token2":"2"}`),
  884. },
  885. Type: "Opaque",
  886. },
  887. },
  888. },
  889. {
  890. name: "add missing property to existing secret",
  891. fields: fields{
  892. Client: &fakeClient{
  893. t: t,
  894. secretMap: map[string]*v1.Secret{
  895. "mysec": {
  896. Data: map[string][]byte{
  897. "token": []byte(`foo`),
  898. },
  899. },
  900. },
  901. },
  902. },
  903. secret: &v1.Secret{
  904. Data: map[string][]byte{secretKey: []byte("bar")},
  905. },
  906. data: testingfake.PushSecretData{
  907. SecretKey: secretKey,
  908. RemoteKey: "mysec",
  909. Property: "secret",
  910. },
  911. wantErr: false,
  912. wantSecretMap: map[string]*v1.Secret{
  913. "mysec": {
  914. ObjectMeta: metav1.ObjectMeta{
  915. Name: "mysec",
  916. Labels: map[string]string{},
  917. Annotations: map[string]string{},
  918. },
  919. Data: map[string][]byte{
  920. "token": []byte(`foo`),
  921. "secret": []byte(`bar`),
  922. },
  923. },
  924. },
  925. },
  926. {
  927. name: "replace existing property in existing secret",
  928. fields: fields{
  929. Client: &fakeClient{
  930. t: t,
  931. secretMap: map[string]*v1.Secret{
  932. "mysec": {
  933. Data: map[string][]byte{
  934. "token": []byte(`foo`),
  935. },
  936. },
  937. },
  938. },
  939. },
  940. secret: &v1.Secret{
  941. Data: map[string][]byte{secretKey: []byte("bar")},
  942. },
  943. data: testingfake.PushSecretData{
  944. SecretKey: secretKey,
  945. RemoteKey: "mysec",
  946. Property: "token",
  947. },
  948. wantErr: false,
  949. wantSecretMap: map[string]*v1.Secret{
  950. "mysec": {
  951. ObjectMeta: metav1.ObjectMeta{
  952. Name: "mysec",
  953. Labels: map[string]string{},
  954. Annotations: map[string]string{},
  955. },
  956. Data: map[string][]byte{
  957. "token": []byte(`bar`),
  958. },
  959. },
  960. },
  961. },
  962. {
  963. name: "replace existing property in existing secret with targetMergePolicy set to Ignore",
  964. fields: fields{
  965. Client: &fakeClient{
  966. t: t,
  967. secretMap: map[string]*v1.Secret{
  968. "mysec": {
  969. Data: map[string][]byte{
  970. "token": []byte(`foo`),
  971. },
  972. },
  973. },
  974. },
  975. },
  976. secret: &v1.Secret{
  977. ObjectMeta: metav1.ObjectMeta{
  978. Name: "mysec",
  979. // these should be ignored as the targetMergePolicy is set to Ignore
  980. Labels: map[string]string{"dev": "seb"},
  981. Annotations: map[string]string{"date": "today"},
  982. },
  983. Data: map[string][]byte{secretKey: []byte("bar")},
  984. },
  985. data: testingfake.PushSecretData{
  986. SecretKey: secretKey,
  987. RemoteKey: "mysec",
  988. Property: "token",
  989. Metadata: &apiextensionsv1.JSON{
  990. Raw: []byte(`{"apiVersion":"kubernetes.external-secrets.io/v1alpha1", "kind": "PushSecretMetadata", spec: {"targetMergePolicy": "Ignore"}}`),
  991. },
  992. },
  993. wantErr: false,
  994. wantSecretMap: map[string]*v1.Secret{
  995. "mysec": {
  996. ObjectMeta: metav1.ObjectMeta{
  997. Name: "mysec",
  998. Labels: map[string]string{},
  999. Annotations: map[string]string{},
  1000. },
  1001. Data: map[string][]byte{
  1002. "token": []byte(`bar`),
  1003. },
  1004. },
  1005. },
  1006. },
  1007. {
  1008. name: "replace existing property in existing secret with targetMergePolicy set to Replace",
  1009. fields: fields{
  1010. Client: &fakeClient{
  1011. t: t,
  1012. secretMap: map[string]*v1.Secret{
  1013. "mysec": {
  1014. ObjectMeta: metav1.ObjectMeta{
  1015. Name: "mysec",
  1016. Labels: map[string]string{
  1017. "already": "existing",
  1018. },
  1019. Annotations: map[string]string{
  1020. "already": "existing",
  1021. },
  1022. },
  1023. Data: map[string][]byte{
  1024. "token": []byte(`foo`),
  1025. },
  1026. },
  1027. },
  1028. },
  1029. },
  1030. secret: &v1.Secret{
  1031. ObjectMeta: metav1.ObjectMeta{
  1032. Name: "mysec",
  1033. // these should replace existing metadata as the targetMergePolicy is set to Replace
  1034. Labels: map[string]string{"dev": "seb"},
  1035. Annotations: map[string]string{"date": "today"},
  1036. },
  1037. Data: map[string][]byte{secretKey: []byte("bar")},
  1038. },
  1039. data: testingfake.PushSecretData{
  1040. SecretKey: secretKey,
  1041. RemoteKey: "mysec",
  1042. Property: "token",
  1043. Metadata: &apiextensionsv1.JSON{
  1044. Raw: []byte(`{"apiVersion":"kubernetes.external-secrets.io/v1alpha1", "kind": "PushSecretMetadata", spec: {"targetMergePolicy": "Replace"}}`),
  1045. },
  1046. },
  1047. wantErr: false,
  1048. wantSecretMap: map[string]*v1.Secret{
  1049. "mysec": {
  1050. ObjectMeta: metav1.ObjectMeta{
  1051. Name: "mysec",
  1052. Labels: map[string]string{
  1053. "dev": "seb",
  1054. },
  1055. Annotations: map[string]string{
  1056. "date": "today",
  1057. },
  1058. },
  1059. Data: map[string][]byte{
  1060. "token": []byte(`bar`),
  1061. },
  1062. },
  1063. },
  1064. },
  1065. {
  1066. name: "create new secret, merging existing metadata",
  1067. fields: fields{
  1068. Client: &fakeClient{
  1069. t: t,
  1070. secretMap: map[string]*v1.Secret{
  1071. "yoursec": {
  1072. Data: map[string][]byte{
  1073. "token": []byte(`foo`),
  1074. },
  1075. },
  1076. },
  1077. },
  1078. },
  1079. secret: &v1.Secret{
  1080. ObjectMeta: metav1.ObjectMeta{
  1081. Annotations: map[string]string{
  1082. "this-annotation": "should be present on the targey secret",
  1083. },
  1084. },
  1085. Data: map[string][]byte{secretKey: []byte("bar")},
  1086. },
  1087. data: testingfake.PushSecretData{
  1088. SecretKey: secretKey,
  1089. RemoteKey: "mysec",
  1090. Property: "secret",
  1091. Metadata: &apiextensionsv1.JSON{
  1092. Raw: []byte(`{"apiVersion":"kubernetes.external-secrets.io/v1alpha1", "kind": "PushSecretMetadata", spec: {"annotations": {"date": "today"}, "labels": {"dev": "seb"}}}`),
  1093. },
  1094. },
  1095. wantErr: false,
  1096. wantSecretMap: map[string]*v1.Secret{
  1097. "yoursec": {
  1098. Data: map[string][]byte{
  1099. "token": []byte(`foo`),
  1100. },
  1101. },
  1102. "mysec": {
  1103. ObjectMeta: metav1.ObjectMeta{
  1104. Name: "mysec",
  1105. Annotations: map[string]string{
  1106. "date": "today",
  1107. "this-annotation": "should be present on the targey secret",
  1108. },
  1109. Labels: map[string]string{"dev": "seb"},
  1110. },
  1111. Data: map[string][]byte{
  1112. "secret": []byte(`bar`),
  1113. },
  1114. Type: v1.SecretTypeOpaque,
  1115. },
  1116. },
  1117. },
  1118. {
  1119. name: "create new secret with metadata from secret metadata and remoteRef.metadata",
  1120. fields: fields{
  1121. Client: &fakeClient{
  1122. t: t,
  1123. secretMap: map[string]*v1.Secret{
  1124. "yoursec": {
  1125. Data: map[string][]byte{
  1126. "token": []byte(`foo`),
  1127. },
  1128. },
  1129. },
  1130. },
  1131. },
  1132. secret: &v1.Secret{
  1133. ObjectMeta: metav1.ObjectMeta{
  1134. Annotations: map[string]string{"date": "today"},
  1135. Labels: map[string]string{"dev": "seb"},
  1136. },
  1137. Data: map[string][]byte{secretKey: []byte("bar")},
  1138. },
  1139. data: testingfake.PushSecretData{
  1140. SecretKey: secretKey,
  1141. RemoteKey: "mysec",
  1142. Property: "secret",
  1143. Metadata: &apiextensionsv1.JSON{
  1144. Raw: []byte(`{"apiVersion":"kubernetes.external-secrets.io/v1alpha1", "kind": "PushSecretMetadata", spec: { "sourceMergePolicy": "Replace", "annotations": {"another-field": "from-remote-ref"}, "labels": {"other-label": "from-remote-ref"}}}`),
  1145. },
  1146. },
  1147. wantErr: false,
  1148. wantSecretMap: map[string]*v1.Secret{
  1149. "yoursec": {
  1150. Data: map[string][]byte{
  1151. "token": []byte(`foo`),
  1152. },
  1153. },
  1154. "mysec": {
  1155. ObjectMeta: metav1.ObjectMeta{
  1156. Name: "mysec",
  1157. Annotations: map[string]string{
  1158. "another-field": "from-remote-ref",
  1159. },
  1160. Labels: map[string]string{
  1161. "other-label": "from-remote-ref",
  1162. },
  1163. },
  1164. Data: map[string][]byte{
  1165. "secret": []byte(`bar`),
  1166. },
  1167. Type: v1.SecretTypeOpaque,
  1168. },
  1169. },
  1170. },
  1171. {
  1172. name: "invalid secret metadata structure results in error",
  1173. fields: fields{
  1174. Client: &fakeClient{
  1175. t: t,
  1176. secretMap: map[string]*v1.Secret{
  1177. "yoursec": {
  1178. Data: map[string][]byte{
  1179. "token": []byte(`foo`),
  1180. },
  1181. },
  1182. },
  1183. },
  1184. },
  1185. secret: &v1.Secret{
  1186. Data: map[string][]byte{secretKey: []byte("bar")},
  1187. },
  1188. data: testingfake.PushSecretData{
  1189. SecretKey: secretKey,
  1190. RemoteKey: "mysec",
  1191. Property: "secret",
  1192. Metadata: &apiextensionsv1.JSON{
  1193. Raw: []byte(`{}`),
  1194. },
  1195. },
  1196. wantErr: true,
  1197. wantSecretMap: map[string]*v1.Secret{
  1198. "yoursec": {
  1199. Data: map[string][]byte{
  1200. "token": []byte(`foo`),
  1201. },
  1202. },
  1203. },
  1204. },
  1205. {
  1206. name: "non-json secret metadata results in error",
  1207. fields: fields{
  1208. Client: &fakeClient{
  1209. t: t,
  1210. secretMap: map[string]*v1.Secret{
  1211. "yoursec": {
  1212. Data: map[string][]byte{
  1213. "token": []byte(`foo`),
  1214. },
  1215. },
  1216. },
  1217. },
  1218. },
  1219. secret: &v1.Secret{
  1220. Data: map[string][]byte{secretKey: []byte("bar")},
  1221. },
  1222. data: testingfake.PushSecretData{
  1223. SecretKey: secretKey,
  1224. RemoteKey: "mysec",
  1225. Property: "secret",
  1226. Metadata: &apiextensionsv1.JSON{
  1227. Raw: []byte(`--- not json ---`),
  1228. },
  1229. },
  1230. wantErr: true,
  1231. wantSecretMap: map[string]*v1.Secret{
  1232. "yoursec": {
  1233. Data: map[string][]byte{
  1234. "token": []byte(`foo`),
  1235. },
  1236. },
  1237. },
  1238. },
  1239. {
  1240. name: "create new secret with whole secret",
  1241. fields: fields{
  1242. Client: &fakeClient{
  1243. t: t,
  1244. secretMap: map[string]*v1.Secret{
  1245. "yoursec": {
  1246. Data: map[string][]byte{
  1247. "token": []byte(`foo`),
  1248. },
  1249. },
  1250. },
  1251. },
  1252. },
  1253. secret: &v1.Secret{
  1254. Data: map[string][]byte{
  1255. "foo": []byte("bar"),
  1256. "baz": []byte("bang"),
  1257. },
  1258. },
  1259. data: testingfake.PushSecretData{
  1260. RemoteKey: "mysec",
  1261. },
  1262. wantErr: false,
  1263. wantSecretMap: map[string]*v1.Secret{
  1264. "yoursec": {
  1265. Data: map[string][]byte{
  1266. "token": []byte(`foo`),
  1267. },
  1268. },
  1269. "mysec": {
  1270. ObjectMeta: metav1.ObjectMeta{
  1271. Name: "mysec",
  1272. Labels: map[string]string{},
  1273. Annotations: map[string]string{},
  1274. },
  1275. Data: map[string][]byte{
  1276. "foo": []byte("bar"),
  1277. "baz": []byte("bang"),
  1278. },
  1279. Type: v1.SecretTypeOpaque,
  1280. },
  1281. },
  1282. },
  1283. {
  1284. name: "create new dockerconfigjson secret",
  1285. fields: fields{
  1286. Client: &fakeClient{
  1287. t: t,
  1288. secretMap: map[string]*v1.Secret{
  1289. "yoursec": {
  1290. Data: map[string][]byte{
  1291. "token": []byte(`foo`),
  1292. },
  1293. },
  1294. },
  1295. },
  1296. },
  1297. secret: &v1.Secret{
  1298. Type: v1.SecretTypeDockerConfigJson,
  1299. Data: map[string][]byte{secretKey: []byte(`{"auths": {"myregistry.localhost": {"username": "{{ .username }}", "password": "{{ .password }}"}}}`)},
  1300. },
  1301. data: testingfake.PushSecretData{
  1302. SecretKey: secretKey,
  1303. RemoteKey: "mysec",
  1304. Property: "config.json",
  1305. },
  1306. wantErr: false,
  1307. wantSecretMap: map[string]*v1.Secret{
  1308. "yoursec": {
  1309. Data: map[string][]byte{
  1310. "token": []byte(`foo`),
  1311. },
  1312. },
  1313. "mysec": {
  1314. ObjectMeta: metav1.ObjectMeta{
  1315. Name: "mysec",
  1316. Labels: map[string]string{},
  1317. Annotations: map[string]string{},
  1318. },
  1319. Data: map[string][]byte{
  1320. "config.json": []byte(`{"auths": {"myregistry.localhost": {"username": "{{ .username }}", "password": "{{ .password }}"}}}`),
  1321. },
  1322. Type: v1.SecretTypeDockerConfigJson,
  1323. },
  1324. },
  1325. },
  1326. {
  1327. name: "create new secret with remote namespace",
  1328. fields: fields{
  1329. Client: &fakeClient{
  1330. t: t,
  1331. secretMap: map[string]*v1.Secret{},
  1332. },
  1333. },
  1334. secret: &v1.Secret{
  1335. ObjectMeta: metav1.ObjectMeta{
  1336. Name: "mysec",
  1337. Namespace: "source-namespace",
  1338. },
  1339. Data: map[string][]byte{secretKey: []byte("bar")},
  1340. },
  1341. data: testingfake.PushSecretData{
  1342. SecretKey: secretKey,
  1343. RemoteKey: "mysec",
  1344. Property: "secret",
  1345. Metadata: &apiextensionsv1.JSON{
  1346. Raw: []byte(`{"apiVersion":"kubernetes.external-secrets.io/v1alpha1", "kind": "PushSecretMetadata", "spec": {"remoteNamespace": "target-namespace"}}`),
  1347. },
  1348. },
  1349. wantErr: false,
  1350. wantSecretMap: map[string]*v1.Secret{
  1351. "mysec": {
  1352. ObjectMeta: metav1.ObjectMeta{
  1353. Name: "mysec",
  1354. Namespace: "target-namespace",
  1355. Labels: map[string]string{},
  1356. Annotations: map[string]string{},
  1357. },
  1358. Data: map[string][]byte{
  1359. "secret": []byte(`bar`),
  1360. },
  1361. Type: v1.SecretTypeOpaque,
  1362. },
  1363. },
  1364. },
  1365. }
  1366. for _, tt := range tests {
  1367. t.Run(tt.name, func(t *testing.T) {
  1368. p := &Client{
  1369. userSecretClient: tt.fields.Client,
  1370. store: &esv1.KubernetesProvider{},
  1371. }
  1372. err := p.PushSecret(context.Background(), tt.secret, tt.data)
  1373. if (err != nil) != tt.wantErr {
  1374. t.Errorf("ProviderKubernetes error = %v, wantErr %v", err, tt.wantErr)
  1375. return
  1376. }
  1377. fClient := tt.fields.Client.(*fakeClient)
  1378. if diff := cmp.Diff(tt.wantSecretMap, fClient.secretMap); diff != "" {
  1379. t.Errorf("Unexpected resulting secrets map: -want, +got :\n%s\n", diff)
  1380. }
  1381. })
  1382. }
  1383. }