client_manager_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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 secretstore
  13. import (
  14. "context"
  15. "testing"
  16. "github.com/go-logr/logr"
  17. "github.com/stretchr/testify/assert"
  18. "github.com/stretchr/testify/require"
  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. "k8s.io/apimachinery/pkg/runtime"
  23. clientgoscheme "k8s.io/client-go/kubernetes/scheme"
  24. "sigs.k8s.io/controller-runtime/pkg/client"
  25. fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
  26. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  27. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  28. )
  29. func TestManagerGet(t *testing.T) {
  30. scheme := runtime.NewScheme()
  31. _ = clientgoscheme.AddToScheme(scheme)
  32. _ = esv1beta1.AddToScheme(scheme)
  33. _ = apiextensionsv1.AddToScheme(scheme)
  34. // We have a test provider to control
  35. // the behavior of the NewClient func.
  36. fakeProvider := &WrapProvider{}
  37. esv1beta1.ForceRegister(fakeProvider, &esv1beta1.SecretStoreProvider{
  38. AWS: &esv1beta1.AWSProvider{},
  39. })
  40. // fake clients are re-used to compare the
  41. // in-memory reference
  42. clientA := &MockFakeClient{id: "1"}
  43. clientB := &MockFakeClient{id: "2"}
  44. const testNamespace = "foo"
  45. readyStatus := esv1beta1.SecretStoreStatus{
  46. Conditions: []esv1beta1.SecretStoreStatusCondition{
  47. {
  48. Type: esv1beta1.SecretStoreReady,
  49. Status: corev1.ConditionTrue,
  50. },
  51. },
  52. }
  53. fakeSpec := esv1beta1.SecretStoreSpec{
  54. Provider: &esv1beta1.SecretStoreProvider{
  55. AWS: &esv1beta1.AWSProvider{},
  56. },
  57. }
  58. defaultStore := &esv1beta1.SecretStore{
  59. TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
  60. ObjectMeta: metav1.ObjectMeta{
  61. Name: "foo",
  62. Namespace: testNamespace,
  63. },
  64. Spec: fakeSpec,
  65. Status: readyStatus,
  66. }
  67. otherStore := &esv1beta1.SecretStore{
  68. TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
  69. ObjectMeta: metav1.ObjectMeta{
  70. Name: "other",
  71. Namespace: testNamespace,
  72. },
  73. Spec: fakeSpec,
  74. Status: readyStatus,
  75. }
  76. var mgr *Manager
  77. provKey := clientKey{
  78. providerType: "*secretstore.WrapProvider",
  79. }
  80. type fields struct {
  81. client client.Client
  82. clientMap map[clientKey]*clientVal
  83. }
  84. type args struct {
  85. storeRef esv1beta1.SecretStoreRef
  86. namespace string
  87. sourceRef *esv1beta1.StoreGeneratorSourceRef
  88. }
  89. tests := []struct {
  90. name string
  91. fields fields
  92. args args
  93. clientConstructor func(
  94. ctx context.Context,
  95. store esv1beta1.GenericStore,
  96. kube client.Client,
  97. namespace string) (esv1beta1.SecretsClient, error)
  98. verify func(esv1beta1.SecretsClient)
  99. afterClose func()
  100. want esv1beta1.SecretsClient
  101. wantErr bool
  102. }{
  103. {
  104. name: "creates a new client from storeRef and stores it",
  105. wantErr: false,
  106. fields: fields{
  107. client: fakeclient.NewClientBuilder().
  108. WithScheme(scheme).
  109. WithObjects(defaultStore).
  110. Build(),
  111. clientMap: make(map[clientKey]*clientVal),
  112. },
  113. args: args{
  114. storeRef: esv1beta1.SecretStoreRef{
  115. Name: defaultStore.Name,
  116. Kind: esv1beta1.SecretStoreKind,
  117. },
  118. namespace: defaultStore.Namespace,
  119. sourceRef: nil,
  120. },
  121. clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  122. return clientA, nil
  123. },
  124. verify: func(sc esv1beta1.SecretsClient) {
  125. // we now must have this provider in the clientMap
  126. // and it mustbe the client defined in clientConstructor
  127. assert.NotNil(t, sc)
  128. c, ok := mgr.clientMap[provKey]
  129. require.True(t, ok)
  130. assert.Same(t, c.client, clientA)
  131. },
  132. afterClose: func() {
  133. v, ok := mgr.clientMap[provKey]
  134. assert.False(t, ok)
  135. assert.Nil(t, v)
  136. },
  137. },
  138. {
  139. name: "creates a new client using both storeRef and sourceRef",
  140. wantErr: false,
  141. fields: fields{
  142. client: fakeclient.NewClientBuilder().
  143. WithScheme(scheme).
  144. WithObjects(otherStore).
  145. Build(),
  146. clientMap: make(map[clientKey]*clientVal),
  147. },
  148. args: args{
  149. storeRef: esv1beta1.SecretStoreRef{
  150. Name: defaultStore.Name,
  151. Kind: esv1beta1.SecretStoreKind,
  152. },
  153. // this should take precedence
  154. sourceRef: &esv1beta1.StoreGeneratorSourceRef{
  155. SecretStoreRef: &esv1beta1.SecretStoreRef{
  156. Name: otherStore.Name,
  157. Kind: esv1beta1.SecretStoreKind,
  158. },
  159. },
  160. namespace: defaultStore.Namespace,
  161. },
  162. clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  163. return clientB, nil
  164. },
  165. verify: func(sc esv1beta1.SecretsClient) {
  166. // we now must have this provider in the clientMap
  167. // and it mustbe the client defined in clientConstructor
  168. assert.NotNil(t, sc)
  169. c, ok := mgr.clientMap[provKey]
  170. assert.True(t, ok)
  171. assert.Same(t, c.client, clientB)
  172. },
  173. afterClose: func() {
  174. v, ok := mgr.clientMap[provKey]
  175. assert.False(t, ok)
  176. assert.True(t, clientB.closeCalled)
  177. assert.Nil(t, v)
  178. },
  179. },
  180. {
  181. name: "retrieve cached client when store matches",
  182. wantErr: false,
  183. fields: fields{
  184. client: fakeclient.NewClientBuilder().
  185. WithScheme(scheme).
  186. WithObjects(defaultStore).
  187. Build(),
  188. clientMap: map[clientKey]*clientVal{
  189. provKey: {
  190. client: clientA,
  191. store: defaultStore,
  192. },
  193. },
  194. },
  195. args: args{
  196. storeRef: esv1beta1.SecretStoreRef{
  197. Name: defaultStore.Name,
  198. Kind: esv1beta1.SecretStoreKind,
  199. },
  200. namespace: defaultStore.Namespace,
  201. sourceRef: nil,
  202. },
  203. clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  204. // constructor should not be called,
  205. // the client from the cache should be returned instead
  206. t.Fail()
  207. return nil, nil
  208. },
  209. verify: func(sc esv1beta1.SecretsClient) {
  210. // verify that the secretsClient is the one from cache
  211. assert.NotNil(t, sc)
  212. c, ok := mgr.clientMap[provKey]
  213. assert.True(t, ok)
  214. assert.Same(t, c.client, clientA)
  215. assert.Same(t, sc, clientA)
  216. },
  217. afterClose: func() {
  218. v, ok := mgr.clientMap[provKey]
  219. assert.False(t, ok)
  220. assert.True(t, clientA.closeCalled)
  221. assert.Nil(t, v)
  222. },
  223. },
  224. {
  225. name: "create new client when store doesn't match",
  226. wantErr: false,
  227. fields: fields{
  228. client: fakeclient.NewClientBuilder().
  229. WithScheme(scheme).
  230. WithObjects(otherStore).
  231. Build(),
  232. clientMap: map[clientKey]*clientVal{
  233. provKey: {
  234. // we have clientA in cache pointing at defaultStore
  235. client: clientA,
  236. store: defaultStore,
  237. },
  238. },
  239. },
  240. args: args{
  241. storeRef: esv1beta1.SecretStoreRef{
  242. Name: otherStore.Name,
  243. Kind: esv1beta1.SecretStoreKind,
  244. },
  245. namespace: otherStore.Namespace,
  246. sourceRef: nil,
  247. },
  248. clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  249. // because there is a store mismatch
  250. // we create a new client
  251. return clientB, nil
  252. },
  253. verify: func(sc esv1beta1.SecretsClient) {
  254. // verify that SecretsClient is NOT the one from cache
  255. assert.NotNil(t, sc)
  256. c, ok := mgr.clientMap[provKey]
  257. assert.True(t, ok)
  258. assert.Same(t, c.client, clientB)
  259. assert.Same(t, sc, clientB)
  260. assert.True(t, clientA.closeCalled)
  261. },
  262. afterClose: func() {
  263. v, ok := mgr.clientMap[provKey]
  264. assert.False(t, ok)
  265. assert.True(t, clientB.closeCalled)
  266. assert.Nil(t, v)
  267. },
  268. },
  269. }
  270. for _, tt := range tests {
  271. t.Run(tt.name, func(t *testing.T) {
  272. mgr = &Manager{
  273. log: logr.Discard(),
  274. client: tt.fields.client,
  275. enableFloodgate: true,
  276. clientMap: tt.fields.clientMap,
  277. }
  278. fakeProvider.newClientFunc = tt.clientConstructor
  279. clientA.closeCalled = false
  280. clientB.closeCalled = false
  281. got, err := mgr.Get(context.Background(), tt.args.storeRef, tt.args.namespace, tt.args.sourceRef)
  282. if (err != nil) != tt.wantErr {
  283. t.Errorf("Manager.Get() error = %v, wantErr %v", err, tt.wantErr)
  284. return
  285. }
  286. tt.verify(got)
  287. mgr.Close(context.Background())
  288. tt.afterClose()
  289. })
  290. }
  291. }
  292. type WrapProvider struct {
  293. newClientFunc func(
  294. context.Context,
  295. esv1beta1.GenericStore,
  296. client.Client,
  297. string) (esv1beta1.SecretsClient, error)
  298. }
  299. // NewClient constructs a SecretsManager Provider.
  300. func (f *WrapProvider) NewClient(
  301. ctx context.Context,
  302. store esv1beta1.GenericStore,
  303. kube client.Client,
  304. namespace string) (esv1beta1.SecretsClient, error) {
  305. return f.newClientFunc(ctx, store, kube, namespace)
  306. }
  307. func (f *WrapProvider) Capabilities() esv1beta1.SecretStoreCapabilities {
  308. return esv1beta1.SecretStoreReadOnly
  309. }
  310. // ValidateStore checks if the provided store is valid.
  311. func (f *WrapProvider) ValidateStore(_ esv1beta1.GenericStore) (admission.Warnings, error) {
  312. return nil, nil
  313. }
  314. type MockFakeClient struct {
  315. id string
  316. closeCalled bool
  317. }
  318. func (c *MockFakeClient) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
  319. return nil
  320. }
  321. func (c *MockFakeClient) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
  322. return nil
  323. }
  324. func (c *MockFakeClient) GetSecret(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  325. return nil, nil
  326. }
  327. func (c *MockFakeClient) Validate() (esv1beta1.ValidationResult, error) {
  328. return esv1beta1.ValidationResultReady, nil
  329. }
  330. // GetSecretMap returns multiple k/v pairs from the provider.
  331. func (c *MockFakeClient) GetSecretMap(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  332. return nil, nil
  333. }
  334. // GetAllSecrets returns multiple k/v pairs from the provider.
  335. func (c *MockFakeClient) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  336. return nil, nil
  337. }
  338. func (c *MockFakeClient) Close(_ context.Context) error {
  339. c.closeCalled = true
  340. return nil
  341. }