testcase.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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 framework
  14. import (
  15. "context"
  16. "strings"
  17. "time"
  18. v1 "k8s.io/api/core/v1"
  19. apierrors "k8s.io/apimachinery/pkg/api/errors"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/util/wait"
  22. "sigs.k8s.io/controller-runtime/pkg/client"
  23. //nolint
  24. "github.com/external-secrets/external-secrets-e2e/framework/log"
  25. "github.com/external-secrets/external-secrets-e2e/framework/util"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  28. . "github.com/onsi/ginkgo/v2"
  29. . "github.com/onsi/gomega"
  30. )
  31. var TargetSecretName = "target-secret"
  32. const (
  33. createObjectRetryPollInterval = 250 * time.Millisecond
  34. createObjectRetryTimeout = 30 * time.Second
  35. )
  36. // TestCase contains the test infra to run a table driven test.
  37. type TestCase struct {
  38. Framework *Framework
  39. ExternalSecret *esv1.ExternalSecret
  40. PushSecret *esv1alpha1.PushSecret
  41. PushSecretSource *v1.Secret
  42. AdditionalObjects []client.Object
  43. Secrets map[string]SecretEntry
  44. ExpectedSecret *v1.Secret
  45. Prepare func(*TestCase, SecretStoreProvider)
  46. Cleanup func()
  47. ProviderOverride SecretStoreProvider
  48. AfterSync func(SecretStoreProvider, *v1.Secret)
  49. VerifyPushSecretOutcome func(ps *esv1alpha1.PushSecret, pushClient esv1.SecretsClient)
  50. }
  51. type SecretEntry struct {
  52. Value string
  53. Tags map[string]string
  54. }
  55. // SecretStoreProvider is a interface that must be implemented
  56. // by a provider that runs the e2e test.
  57. type SecretStoreProvider interface {
  58. CreateSecret(key string, val SecretEntry)
  59. DeleteSecret(key string)
  60. }
  61. // TableFuncWithExternalSecret returns the main func that runs a TestCase in a table driven test.
  62. func TableFuncWithExternalSecret(f *Framework, prov SecretStoreProvider) func(...func(*TestCase)) {
  63. return func(tweaks ...func(*TestCase)) {
  64. // make default test case
  65. // and apply customization to it
  66. tc := makeDefaultExternalSecretTestCase(f)
  67. for _, tweak := range tweaks {
  68. tweak(tc)
  69. }
  70. defer func() {
  71. if tc.Cleanup != nil {
  72. tc.Cleanup()
  73. }
  74. }()
  75. prov = prepareTestCase(tc, prov)
  76. // create secrets & defer delete
  77. var deferRemoveKeys []string
  78. for k, v := range tc.Secrets {
  79. key := k
  80. prov.CreateSecret(key, v)
  81. deferRemoveKeys = append(deferRemoveKeys, key)
  82. }
  83. defer func() {
  84. for _, k := range deferRemoveKeys {
  85. prov.DeleteSecret(k)
  86. }
  87. }()
  88. // create additional objects
  89. generateAdditionalObjects(tc)
  90. // create v1alpha1 external secret, if provided
  91. createProvidedExternalSecret(tc)
  92. // wait for Kind=Secret to have the expected data
  93. executeAfterSync(tc, f, prov)
  94. }
  95. }
  96. func executeAfterSync(tc *TestCase, f *Framework, prov SecretStoreProvider) {
  97. if tc.ExpectedSecret != nil {
  98. secret, err := tc.Framework.WaitForSecretValue(tc.Framework.Namespace.Name, externalSecretTargetName(tc), tc.ExpectedSecret)
  99. if err != nil {
  100. f.printESDebugLogs(tc.ExternalSecret.Name, tc.ExternalSecret.Namespace)
  101. log.Logf("Did not match. Expected: %+v, Got: %+v", tc.ExpectedSecret, secret)
  102. }
  103. Expect(err).ToNot(HaveOccurred())
  104. tc.AfterSync(prov, secret)
  105. } else {
  106. tc.AfterSync(prov, nil)
  107. }
  108. }
  109. func externalSecretTargetName(tc *TestCase) string {
  110. if tc == nil || tc.ExternalSecret == nil {
  111. return TargetSecretName
  112. }
  113. if tc.ExternalSecret.Spec.Target.Name != "" {
  114. return tc.ExternalSecret.Spec.Target.Name
  115. }
  116. if tc.ExternalSecret.Name != "" {
  117. return tc.ExternalSecret.Name
  118. }
  119. return TargetSecretName
  120. }
  121. func generateAdditionalObjects(tc *TestCase) {
  122. if tc.AdditionalObjects != nil {
  123. for _, obj := range tc.AdditionalObjects {
  124. err := tc.Framework.CreateObjectWithRetry(obj)
  125. Expect(err).ToNot(HaveOccurred())
  126. }
  127. }
  128. }
  129. func createProvidedExternalSecret(tc *TestCase) {
  130. if tc.ExternalSecret == nil {
  131. return
  132. }
  133. err := tc.Framework.CreateObjectWithRetry(tc.ExternalSecret)
  134. Expect(err).ToNot(HaveOccurred())
  135. }
  136. // TableFuncWithPushSecret returns the main func that runs a TestCase in a table driven test for push secrets.
  137. func TableFuncWithPushSecret(f *Framework, prov SecretStoreProvider, pushClient esv1.SecretsClient) func(...func(*TestCase)) {
  138. return func(tweaks ...func(*TestCase)) {
  139. var err error
  140. // make default test case
  141. // and apply customization to it
  142. tc := makeDefaultPushSecretTestCase(f)
  143. for _, tweak := range tweaks {
  144. tweak(tc)
  145. }
  146. prov = prepareTestCase(tc, prov)
  147. // additional objects
  148. generateAdditionalObjects(tc)
  149. if tc.PushSecretSource != nil {
  150. err := tc.Framework.CreateObjectWithRetry(tc.PushSecretSource)
  151. Expect(err).ToNot(HaveOccurred())
  152. }
  153. // create v1alpha1 push secret, if provided
  154. if tc.PushSecret != nil {
  155. // create v1beta1 external secret otherwise
  156. err = tc.Framework.CreateObjectWithRetry(tc.PushSecret)
  157. Expect(err).ToNot(HaveOccurred())
  158. }
  159. // Run verification on the secret that push secret created or not.
  160. tc.VerifyPushSecretOutcome(tc.PushSecret, pushClient)
  161. }
  162. }
  163. func prepareTestCase(tc *TestCase, prov SecretStoreProvider) SecretStoreProvider {
  164. prov = effectiveTestCaseProvider(tc, prov)
  165. if tc.Prepare != nil {
  166. tc.Prepare(tc, prov)
  167. }
  168. return effectiveTestCaseProvider(tc, prov)
  169. }
  170. func effectiveTestCaseProvider(tc *TestCase, prov SecretStoreProvider) SecretStoreProvider {
  171. if tc.ProviderOverride != nil {
  172. return tc.ProviderOverride
  173. }
  174. return prov
  175. }
  176. func makeDefaultExternalSecretTestCase(f *Framework) *TestCase {
  177. return &TestCase{
  178. AfterSync: func(ssp SecretStoreProvider, s *v1.Secret) {},
  179. Framework: f,
  180. ExternalSecret: &esv1.ExternalSecret{
  181. ObjectMeta: metav1.ObjectMeta{
  182. Name: "e2e-es",
  183. Namespace: f.Namespace.Name,
  184. },
  185. Spec: esv1.ExternalSecretSpec{
  186. RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
  187. SecretStoreRef: esv1.SecretStoreRef{
  188. Name: f.Namespace.Name,
  189. Kind: f.DefaultSecretStoreRefKind,
  190. },
  191. Target: esv1.ExternalSecretTarget{
  192. Name: TargetSecretName,
  193. },
  194. },
  195. },
  196. }
  197. }
  198. func makeDefaultPushSecretTestCase(f *Framework) *TestCase {
  199. return &TestCase{
  200. Framework: f,
  201. PushSecret: &esv1alpha1.PushSecret{
  202. ObjectMeta: metav1.ObjectMeta{
  203. Name: "e2e-ps",
  204. Namespace: f.Namespace.Name,
  205. },
  206. Spec: esv1alpha1.PushSecretSpec{
  207. RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
  208. SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{
  209. {
  210. Name: f.Namespace.Name,
  211. Kind: f.DefaultPushSecretStoreRefKind,
  212. APIVersion: f.DefaultPushSecretStoreRefAPIVersion,
  213. },
  214. },
  215. },
  216. },
  217. }
  218. }
  219. func (f *Framework) CreateObjectWithRetry(obj client.Object) error {
  220. return f.CreateObjectWithRetryContext(GinkgoT().Context(), obj)
  221. }
  222. func (f *Framework) CreateObjectWithRetryContext(ctx context.Context, obj client.Object) error {
  223. return wait.PollUntilContextTimeout(ctx, createObjectRetryPollInterval, createObjectRetryTimeout, true, func(ctx context.Context) (bool, error) {
  224. err := f.CRClient.Create(ctx, obj)
  225. switch {
  226. case err == nil, apierrors.IsAlreadyExists(err):
  227. return true, nil
  228. case isRetryableCreateObjectError(err):
  229. if f.KubeConfig != nil {
  230. f.refreshClients()
  231. }
  232. return false, nil
  233. default:
  234. return false, err
  235. }
  236. })
  237. }
  238. func createObjectWithRetryPolling(ctx context.Context, c client.Client, obj client.Object, pollInterval, timeout time.Duration) error {
  239. return wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true, func(ctx context.Context) (bool, error) {
  240. err := c.Create(ctx, obj)
  241. switch {
  242. case err == nil, apierrors.IsAlreadyExists(err):
  243. return true, nil
  244. case isRetryableCreateObjectError(err):
  245. return false, nil
  246. default:
  247. return false, err
  248. }
  249. })
  250. }
  251. func isRetryableCreateObjectError(err error) bool {
  252. if util.IsMissingAPIResourceError(err) {
  253. return true
  254. }
  255. if apierrors.IsNotFound(err) && strings.Contains(err.Error(), "could not find the requested resource") {
  256. return true
  257. }
  258. if !(apierrors.IsInternalError(err) || apierrors.IsServiceUnavailable(err)) {
  259. return false
  260. }
  261. msg := strings.ToLower(err.Error())
  262. return strings.Contains(msg, "failed calling webhook") &&
  263. (strings.Contains(msg, "connection refused") || strings.Contains(msg, "no endpoints available"))
  264. }