client_manager_test.go 10 KB

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