client_manager_test.go 9.9 KB

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