externalsecret_controller.go 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  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 externalsecret
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "maps"
  19. "slices"
  20. "strings"
  21. "time"
  22. "github.com/go-logr/logr"
  23. "github.com/prometheus/client_golang/prometheus"
  24. v1 "k8s.io/api/core/v1"
  25. "k8s.io/apimachinery/pkg/api/equality"
  26. apierrors "k8s.io/apimachinery/pkg/api/errors"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apimachinery/pkg/fields"
  29. "k8s.io/apimachinery/pkg/labels"
  30. "k8s.io/apimachinery/pkg/runtime"
  31. "k8s.io/apimachinery/pkg/runtime/schema"
  32. "k8s.io/apimachinery/pkg/selection"
  33. "k8s.io/apimachinery/pkg/types"
  34. "k8s.io/client-go/rest"
  35. "k8s.io/client-go/tools/record"
  36. "k8s.io/utils/ptr"
  37. ctrl "sigs.k8s.io/controller-runtime"
  38. "sigs.k8s.io/controller-runtime/pkg/builder"
  39. "sigs.k8s.io/controller-runtime/pkg/cache"
  40. "sigs.k8s.io/controller-runtime/pkg/client"
  41. "sigs.k8s.io/controller-runtime/pkg/controller"
  42. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  43. "sigs.k8s.io/controller-runtime/pkg/handler"
  44. "sigs.k8s.io/controller-runtime/pkg/predicate"
  45. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  46. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  47. // Metrics.
  48. "github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
  49. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  50. "github.com/external-secrets/external-secrets/pkg/controllers/util"
  51. "github.com/external-secrets/external-secrets/pkg/generator/statemanager"
  52. "github.com/external-secrets/external-secrets/pkg/utils"
  53. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  54. _ "github.com/external-secrets/external-secrets/pkg/generator/register"
  55. // Loading registered providers.
  56. _ "github.com/external-secrets/external-secrets/pkg/provider/register"
  57. )
  58. const (
  59. fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
  60. // condition messages for "SecretSynced" reason.
  61. msgSynced = "secret synced"
  62. msgSyncedRetain = "secret retained due to DeletionPolicy=Retain"
  63. // condition messages for "SecretDeleted" reason.
  64. msgDeleted = "secret deleted due to DeletionPolicy=Delete"
  65. // condition messages for "SecretMissing" reason.
  66. msgMissing = "secret will not be created due to CreationPolicy=Merge"
  67. // condition messages for "SecretSyncedError" reason.
  68. msgErrorGetSecretData = "could not get secret data from provider"
  69. msgErrorDeleteSecret = "could not delete secret"
  70. msgErrorDeleteOrphaned = "could not delete orphaned secrets"
  71. msgErrorUpdateSecret = "could not update secret"
  72. msgErrorUpdateImmutable = "could not update secret, target is immutable"
  73. msgErrorBecomeOwner = "failed to take ownership of target secret"
  74. msgErrorIsOwned = "target is owned by another ExternalSecret"
  75. msgErrorGarbageCollect = "could not garbage collect generator state"
  76. // log messages.
  77. logErrorGetES = "unable to get ExternalSecret"
  78. logErrorUpdateESStatus = "unable to update ExternalSecret status"
  79. logErrorGetSecret = "unable to get Secret"
  80. logErrorPatchSecret = "unable to patch Secret"
  81. logErrorSecretCacheNotSynced = "controller caches for Secret are not in sync"
  82. logErrorUnmanagedStore = "unable to determine if store is managed"
  83. // error formats.
  84. errConvert = "could not apply conversion strategy to keys: %v"
  85. errDecode = "could not apply decoding strategy to %v[%d]: %v"
  86. errGenerate = "could not generate [%d]: %w"
  87. errRewrite = "could not rewrite spec.dataFrom[%d]: %v"
  88. errInvalidKeys = "secret keys from spec.dataFrom.%v[%d] can only have alphanumeric, '-', '_' or '.' characters. Convert them using rewrite (https://external-secrets.io/latest/guides/datafrom-rewrite/)"
  89. errFetchTplFrom = "error fetching templateFrom data: %w"
  90. errApplyTemplate = "could not apply template: %w"
  91. errExecTpl = "could not execute template: %w"
  92. errMutate = "unable to mutate secret %s: %w"
  93. errUpdate = "unable to update secret %s: %w"
  94. errUpdateNotFound = "unable to update secret %s: not found"
  95. errDeleteCreatePolicy = "unable to delete secret %s: creationPolicy=%s is not Owner"
  96. errSecretCachesNotSynced = "controller caches for secret %s are not in sync"
  97. )
  98. // these errors are explicitly defined so we can detect them with `errors.Is()`.
  99. var (
  100. ErrSecretImmutable = fmt.Errorf("secret is immutable")
  101. ErrSecretIsOwned = fmt.Errorf("secret is owned by another ExternalSecret")
  102. ErrSecretSetCtrlRef = fmt.Errorf("could not set controller reference on secret")
  103. ErrSecretRemoveCtrlRef = fmt.Errorf("could not remove controller reference on secret")
  104. )
  105. const indexESTargetSecretNameField = ".metadata.targetSecretName"
  106. const externalSecretFinalizer = "externalsecret.externalsecrets.io/finalizer"
  107. // Reconciler reconciles a ExternalSecret object.
  108. type Reconciler struct {
  109. client.Client
  110. SecretClient client.Client
  111. Log logr.Logger
  112. Scheme *runtime.Scheme
  113. RestConfig *rest.Config
  114. ControllerClass string
  115. RequeueInterval time.Duration
  116. ClusterSecretStoreEnabled bool
  117. EnableFloodGate bool
  118. recorder record.EventRecorder
  119. }
  120. // Reconcile implements the main reconciliation loop
  121. // for watched objects (ExternalSecret, ClusterSecretStore and SecretStore),
  122. // and updates/creates a Kubernetes secret based on them.
  123. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
  124. log := r.Log.WithValues("ExternalSecret", req.NamespacedName)
  125. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  126. start := time.Now()
  127. syncCallsError := esmetrics.GetCounterVec(esmetrics.SyncCallsErrorKey)
  128. // use closures to dynamically update resourceLabels
  129. defer func() {
  130. esmetrics.GetGaugeVec(esmetrics.ExternalSecretReconcileDurationKey).With(resourceLabels).Set(float64(time.Since(start)))
  131. esmetrics.GetCounterVec(esmetrics.SyncCallsKey).With(resourceLabels).Inc()
  132. }()
  133. externalSecret := &esv1beta1.ExternalSecret{}
  134. err = r.Get(ctx, req.NamespacedName, externalSecret)
  135. if err != nil {
  136. if apierrors.IsNotFound(err) {
  137. // NOTE: this does not actually set the condition on the ExternalSecret, because it does not exist
  138. // this is a hack to disable metrics for deleted ExternalSecrets, see:
  139. // https://github.com/external-secrets/external-secrets/pull/612
  140. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretDeleted, v1.ConditionFalse, esv1beta1.ConditionReasonSecretDeleted, "Secret was deleted")
  141. SetExternalSecretCondition(&esv1beta1.ExternalSecret{
  142. ObjectMeta: metav1.ObjectMeta{
  143. Name: req.Name,
  144. Namespace: req.Namespace,
  145. },
  146. }, *conditionSynced)
  147. return ctrl.Result{}, nil
  148. }
  149. log.Error(err, logErrorGetES)
  150. syncCallsError.With(resourceLabels).Inc()
  151. return ctrl.Result{}, err
  152. }
  153. // We fetch the ExternalSecret resource above, however the status subresource may be inconsistent.
  154. // We have to explicitly fetch it, otherwise it may be missing and will cause
  155. // unexpected side effects.
  156. // When we update the status below and immediately requeue the request, the status
  157. // will be updated, but the ExternalSecret cache won't be up to date yet.
  158. err = r.SubResource("status").Get(ctx, externalSecret, externalSecret)
  159. if err != nil {
  160. log.Error(err, "failed to get status subresource")
  161. return ctrl.Result{}, err
  162. }
  163. if externalSecret.ObjectMeta.DeletionTimestamp.IsZero() {
  164. if added := controllerutil.AddFinalizer(externalSecret, externalSecretFinalizer); added {
  165. if err := r.Client.Update(ctx, externalSecret, &client.UpdateOptions{}); err != nil {
  166. return ctrl.Result{}, fmt.Errorf("could not update finalizers: %w", err)
  167. }
  168. return ctrl.Result{Requeue: true}, nil
  169. }
  170. } else if controllerutil.ContainsFinalizer(externalSecret, externalSecretFinalizer) {
  171. sm := statemanager.New(r.Client, r.Scheme, externalSecret.Namespace, externalSecret)
  172. if err := sm.CleanupImmediate(ctx, externalSecret, r.Client, externalSecret.Namespace); err != nil {
  173. r.markAsFailed(msgErrorGarbageCollect, err, externalSecret, syncCallsError.With(resourceLabels))
  174. return ctrl.Result{}, err
  175. }
  176. r.Log.Info("removing finalizer")
  177. controllerutil.RemoveFinalizer(externalSecret, externalSecretFinalizer)
  178. if err := r.Client.Update(ctx, externalSecret, &client.UpdateOptions{}); err != nil {
  179. return ctrl.Result{}, fmt.Errorf("could not update finalizers: %w", err)
  180. }
  181. return ctrl.Result{}, nil
  182. }
  183. // skip reconciliation if deletion timestamp is set on external secret
  184. if !externalSecret.GetDeletionTimestamp().IsZero() {
  185. log.V(1).Info("skipping ExternalSecret, it is marked for deletion")
  186. return ctrl.Result{}, nil
  187. }
  188. // if extended metrics is enabled, refine the time series vector
  189. resourceLabels = ctrlmetrics.RefineLabels(resourceLabels, externalSecret.Labels)
  190. // skip this ExternalSecret if it uses a ClusterSecretStore and the feature is disabled
  191. if shouldSkipClusterSecretStore(r, externalSecret) {
  192. log.V(1).Info("skipping ExternalSecret, ClusterSecretStore feature is disabled")
  193. return ctrl.Result{}, nil
  194. }
  195. // skip this ExternalSecret if it uses any SecretStore not managed by this controller
  196. skip, err := shouldSkipUnmanagedStore(ctx, req.Namespace, r, externalSecret)
  197. if err != nil {
  198. log.Error(err, logErrorUnmanagedStore)
  199. syncCallsError.With(resourceLabels).Inc()
  200. return ctrl.Result{}, err
  201. }
  202. if skip {
  203. log.V(1).Info("skipping ExternalSecret, uses unmanaged SecretStore")
  204. return ctrl.Result{}, nil
  205. }
  206. // the target secret name defaults to the ExternalSecret name, if not explicitly set
  207. secretName := externalSecret.Spec.Target.Name
  208. if secretName == "" {
  209. secretName = externalSecret.Name
  210. }
  211. // fetch the existing secret (from the partial cache)
  212. // - please note that the ~partial cache~ is different from the ~full cache~
  213. // so there can be race conditions between the two caches
  214. // - the WatchesMetadata(v1.Secret{}) in SetupWithManager() is using the partial cache
  215. // so we might receive a reconcile request before the full cache is updated
  216. // - furthermore, when `--enable-managed-secrets-caching` is true, the full cache
  217. // will ONLY include secrets with the "managed" label, so we cant use the full cache
  218. // to reliably determine if a secret exists or not
  219. secretPartial := &metav1.PartialObjectMetadata{}
  220. secretPartial.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Secret"))
  221. err = r.Get(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, secretPartial)
  222. if err != nil && !apierrors.IsNotFound(err) {
  223. log.Error(err, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  224. syncCallsError.With(resourceLabels).Inc()
  225. return ctrl.Result{}, err
  226. }
  227. // if the secret exists but does not have the "managed" label, add the label
  228. // using a PATCH so it is visible in the cache, then requeue immediately
  229. if secretPartial.UID != "" && secretPartial.Labels[esv1beta1.LabelManaged] != esv1beta1.LabelManagedValue {
  230. fqdn := fmt.Sprintf(fieldOwnerTemplate, externalSecret.Name)
  231. patch := client.MergeFrom(secretPartial.DeepCopy())
  232. if secretPartial.Labels == nil {
  233. secretPartial.Labels = make(map[string]string)
  234. }
  235. secretPartial.Labels[esv1beta1.LabelManaged] = esv1beta1.LabelManagedValue
  236. err = r.Patch(ctx, secretPartial, patch, client.FieldOwner(fqdn))
  237. if err != nil {
  238. log.Error(err, logErrorPatchSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  239. syncCallsError.With(resourceLabels).Inc()
  240. return ctrl.Result{}, err
  241. }
  242. return ctrl.Result{Requeue: true}, nil
  243. }
  244. // fetch existing secret (from the full cache)
  245. // NOTE: we are using the `r.SecretClient` which we only use for managed secrets.
  246. // when `enableManagedSecretsCache` is true, this is a cached client that only sees our managed secrets,
  247. // otherwise it will be the normal controller-runtime client which may be cached or make direct API calls,
  248. // depending on if `enabledSecretCache` is true or false.
  249. existingSecret := &v1.Secret{}
  250. err = r.SecretClient.Get(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, existingSecret)
  251. if err != nil && !apierrors.IsNotFound(err) {
  252. log.Error(err, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  253. syncCallsError.With(resourceLabels).Inc()
  254. return ctrl.Result{}, err
  255. }
  256. // ensure the full cache is up-to-date
  257. // NOTE: this prevents race conditions between the partial and full cache.
  258. // we return an error so we get an exponential backoff if we end up looping,
  259. // for example, during high cluster load and frequent updates to the target secret by other controllers.
  260. if secretPartial.UID != existingSecret.UID || secretPartial.ResourceVersion != existingSecret.ResourceVersion {
  261. err = fmt.Errorf(errSecretCachesNotSynced, secretName)
  262. log.Error(err, logErrorSecretCacheNotSynced, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  263. syncCallsError.With(resourceLabels).Inc()
  264. return ctrl.Result{}, err
  265. }
  266. // refresh will be skipped if ALL the following conditions are met:
  267. // 1. refresh interval is not 0
  268. // 2. resource generation of the ExternalSecret has not changed
  269. // 3. the last refresh time of the ExternalSecret is within the refresh interval
  270. // 4. the target secret is valid:
  271. // - it exists
  272. // - it has the correct "managed" label
  273. // - it has the correct "data-hash" annotation
  274. if !shouldRefresh(externalSecret) && isSecretValid(existingSecret) {
  275. log.V(1).Info("skipping refresh")
  276. return r.getRequeueResult(externalSecret), nil
  277. }
  278. // update status of the ExternalSecret when this function returns, if needed.
  279. // NOTE: we use the ability of deferred functions to update named return values `result` and `err`
  280. // NOTE: we dereference the DeepCopy of the status field because status fields are NOT pointers,
  281. // so otherwise the `equality.Semantic.DeepEqual` will always return false.
  282. currentStatus := externalSecret.Status.DeepCopy()
  283. defer func() {
  284. // if the status has not changed, we don't need to update it
  285. if equality.Semantic.DeepEqual(currentStatus, externalSecret.Status) {
  286. return
  287. }
  288. // update the status of the ExternalSecret, storing any error in a new variable
  289. // if there was no new error, we don't need to change the `result` or `err` values
  290. updateErr := r.Status().Update(ctx, externalSecret)
  291. if updateErr == nil {
  292. return
  293. }
  294. // if we got an update conflict, we should requeue immediately
  295. if apierrors.IsConflict(updateErr) {
  296. log.V(1).Info("conflict while updating status, will requeue")
  297. // we only explicitly request a requeue if the main function did not return an `err`.
  298. // otherwise, we get an annoying log saying that results are ignored when there is an error,
  299. // as errors are always retried.
  300. if err == nil {
  301. result = ctrl.Result{Requeue: true}
  302. }
  303. return
  304. }
  305. // for other errors, log and update the `err` variable if there is no error already
  306. // so the reconciler will requeue the request
  307. log.Error(updateErr, logErrorUpdateESStatus)
  308. if err == nil {
  309. err = updateErr
  310. }
  311. }()
  312. // retrieve the provider secret data.
  313. dataMap, err := r.getProviderSecretData(ctx, externalSecret)
  314. if err != nil {
  315. r.markAsFailed(msgErrorGetSecretData, err, externalSecret, syncCallsError.With(resourceLabels))
  316. return ctrl.Result{}, err
  317. }
  318. // if no data was found we can delete the secret if needed.
  319. if len(dataMap) == 0 {
  320. switch externalSecret.Spec.Target.DeletionPolicy {
  321. // delete secret and return early.
  322. case esv1beta1.DeletionPolicyDelete:
  323. // safeguard that we only can delete secrets we own.
  324. // this is also implemented in the es validation webhook.
  325. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  326. creationPolicy := externalSecret.Spec.Target.CreationPolicy
  327. if creationPolicy != esv1beta1.CreatePolicyOwner {
  328. err := fmt.Errorf(errDeleteCreatePolicy, secretName, creationPolicy)
  329. r.markAsFailed(msgErrorDeleteSecret, err, externalSecret, syncCallsError.With(resourceLabels))
  330. return ctrl.Result{}, nil
  331. }
  332. // delete the secret, if it exists
  333. if existingSecret.UID != "" {
  334. if err := r.Delete(ctx, existingSecret); err != nil && !apierrors.IsNotFound(err) {
  335. r.markAsFailed(msgErrorDeleteSecret, err, externalSecret, syncCallsError.With(resourceLabels))
  336. return ctrl.Result{}, err
  337. }
  338. }
  339. r.markAsDone(externalSecret, start, log, esv1beta1.ConditionReasonSecretDeleted, msgDeleted)
  340. return r.getRequeueResult(externalSecret), nil
  341. // In case provider secrets don't exist the kubernetes secret will be kept as-is.
  342. case esv1beta1.DeletionPolicyRetain:
  343. r.markAsDone(externalSecret, start, log, esv1beta1.ConditionReasonSecretSynced, msgSyncedRetain)
  344. return r.getRequeueResult(externalSecret), nil
  345. // noop, handled below
  346. case esv1beta1.DeletionPolicyMerge:
  347. }
  348. }
  349. // mutationFunc is a function which can be applied to a secret to make it match the desired state.
  350. mutationFunc := func(secret *v1.Secret) error {
  351. // get information about the current owner of the secret
  352. // - we ignore the API version as it can change over time
  353. // - we ignore the UID for consistency with the SetControllerReference function
  354. currentOwner := metav1.GetControllerOf(secret)
  355. ownerIsESKind := false
  356. ownerIsCurrentES := false
  357. if currentOwner != nil {
  358. currentOwnerGK := schema.FromAPIVersionAndKind(currentOwner.APIVersion, currentOwner.Kind).GroupKind()
  359. ownerIsESKind = currentOwnerGK.String() == esv1beta1.ExtSecretGroupKind
  360. ownerIsCurrentES = ownerIsESKind && currentOwner.Name == externalSecret.Name
  361. }
  362. // if another ExternalSecret is the owner, we should return an error
  363. // otherwise the controller will fight with itself to update the secret.
  364. // note, this does not prevent other controllers from owning the secret.
  365. if ownerIsESKind && !ownerIsCurrentES {
  366. return fmt.Errorf("%w: %s", ErrSecretIsOwned, currentOwner.Name)
  367. }
  368. // if the CreationPolicy is Owner, we should set ourselves as the owner of the secret
  369. if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
  370. err = controllerutil.SetControllerReference(externalSecret, secret, r.Scheme)
  371. if err != nil {
  372. return fmt.Errorf("%w: %w", ErrSecretSetCtrlRef, err)
  373. }
  374. }
  375. // if the creation policy is not Owner, we should remove ourselves as the owner
  376. // this could happen if the creation policy was changed after the secret was created
  377. if externalSecret.Spec.Target.CreationPolicy != esv1beta1.CreatePolicyOwner && ownerIsCurrentES {
  378. err = controllerutil.RemoveControllerReference(externalSecret, secret, r.Scheme)
  379. if err != nil {
  380. return fmt.Errorf("%w: %w", ErrSecretRemoveCtrlRef, err)
  381. }
  382. }
  383. // initialize maps within the secret so it's safe to set values
  384. if secret.Annotations == nil {
  385. secret.Annotations = make(map[string]string)
  386. }
  387. if secret.Labels == nil {
  388. secret.Labels = make(map[string]string)
  389. }
  390. if secret.Data == nil {
  391. secret.Data = make(map[string][]byte)
  392. }
  393. // get the list of keys that are managed by this ExternalSecret
  394. keys, err := getManagedDataKeys(secret, externalSecret.Name)
  395. if err != nil {
  396. return err
  397. }
  398. // remove any data keys that are managed by this ExternalSecret, so we can re-add them
  399. // this ensures keys added by templates are not left behind when they are removed from the template
  400. for _, key := range keys {
  401. delete(secret.Data, key)
  402. }
  403. // WARNING: this will remove any labels or annotations managed by this ExternalSecret
  404. // so any updates to labels and annotations should be done AFTER this point
  405. err = r.applyTemplate(ctx, externalSecret, secret, dataMap)
  406. if err != nil {
  407. return fmt.Errorf(errApplyTemplate, err)
  408. }
  409. // set the immutable flag on the secret if requested by the ExternalSecret
  410. if externalSecret.Spec.Target.Immutable {
  411. secret.Immutable = ptr.To(true)
  412. }
  413. // we also use a label to keep track of the owner of the secret
  414. // this lets us remove secrets that are no longer needed if the target secret name changes
  415. if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
  416. lblValue := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
  417. secret.Labels[esv1beta1.LabelOwner] = lblValue
  418. } else {
  419. // the label should not be set if the creation policy is not Owner
  420. delete(secret.Labels, esv1beta1.LabelOwner)
  421. }
  422. secret.Labels[esv1beta1.LabelManaged] = esv1beta1.LabelManagedValue
  423. secret.Annotations[esv1beta1.AnnotationDataHash] = utils.ObjectHash(secret.Data)
  424. return nil
  425. }
  426. //nolint:nolintlint
  427. switch externalSecret.Spec.Target.CreationPolicy { //nolint:exhaustive
  428. case esv1beta1.CreatePolicyMerge:
  429. // update the secret, if it exists
  430. if existingSecret.UID != "" {
  431. err = r.updateSecret(ctx, existingSecret, mutationFunc, externalSecret, secretName)
  432. } else {
  433. // if the secret does not exist, we wait until the next refresh interval
  434. // rather than returning an error which would requeue immediately
  435. r.markAsDone(externalSecret, start, log, esv1beta1.ConditionReasonSecretMissing, msgMissing)
  436. return r.getRequeueResult(externalSecret), nil
  437. }
  438. case esv1beta1.CreatePolicyNone:
  439. log.V(1).Info("secret creation skipped due to creationPolicy=None")
  440. err = nil
  441. default:
  442. // create the secret, if it does not exist
  443. if existingSecret.UID == "" {
  444. err = r.createSecret(ctx, mutationFunc, externalSecret, secretName)
  445. // we may have orphaned secrets to clean up,
  446. // for example, if the target secret name was changed
  447. if err == nil {
  448. delErr := deleteOrphanedSecrets(ctx, r.Client, externalSecret, secretName)
  449. if delErr != nil {
  450. r.markAsFailed(msgErrorDeleteOrphaned, delErr, externalSecret, syncCallsError.With(resourceLabels))
  451. return ctrl.Result{}, delErr
  452. }
  453. }
  454. } else {
  455. // update the secret, if it exists
  456. err = r.updateSecret(ctx, existingSecret, mutationFunc, externalSecret, secretName)
  457. }
  458. }
  459. if err != nil {
  460. // if we got an update conflict, we should requeue immediately
  461. if apierrors.IsConflict(err) {
  462. log.V(1).Info("conflict while updating secret, will requeue")
  463. return ctrl.Result{Requeue: true}, nil
  464. }
  465. // detect errors indicating that we failed to set ourselves as the owner of the secret
  466. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  467. if errors.Is(err, ErrSecretSetCtrlRef) {
  468. r.markAsFailed(msgErrorBecomeOwner, err, externalSecret, syncCallsError.With(resourceLabels))
  469. return ctrl.Result{}, nil
  470. }
  471. // detect errors indicating that the secret has another ExternalSecret as owner
  472. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  473. if errors.Is(err, ErrSecretIsOwned) {
  474. r.markAsFailed(msgErrorIsOwned, err, externalSecret, syncCallsError.With(resourceLabels))
  475. return ctrl.Result{}, nil
  476. }
  477. // detect errors indicating that the secret is immutable
  478. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  479. if errors.Is(err, ErrSecretImmutable) {
  480. r.markAsFailed(msgErrorUpdateImmutable, err, externalSecret, syncCallsError.With(resourceLabels))
  481. return ctrl.Result{}, nil
  482. }
  483. r.markAsFailed(msgErrorUpdateSecret, err, externalSecret, syncCallsError.With(resourceLabels))
  484. return ctrl.Result{}, err
  485. }
  486. sm := statemanager.New(r.Client, r.Scheme, externalSecret.Namespace, externalSecret)
  487. if err := sm.GarbageCollect(ctx, r.Client, externalSecret.Namespace); err != nil {
  488. r.markAsFailed(msgErrorGarbageCollect, err, externalSecret, syncCallsError.With(resourceLabels))
  489. return ctrl.Result{}, err
  490. }
  491. r.markAsDone(externalSecret, start, log, esv1beta1.ConditionReasonSecretSynced, msgSynced)
  492. return r.getRequeueResult(externalSecret), nil
  493. }
  494. // getRequeueResult create a result with requeueAfter based on the ExternalSecret refresh interval.
  495. func (r *Reconciler) getRequeueResult(externalSecret *esv1beta1.ExternalSecret) ctrl.Result {
  496. // default to the global requeue interval
  497. // note, this will never be used because the CRD has a default value of 1 hour
  498. refreshInterval := r.RequeueInterval
  499. if externalSecret.Spec.RefreshInterval != nil {
  500. refreshInterval = externalSecret.Spec.RefreshInterval.Duration
  501. }
  502. // if the refresh interval is <= 0, we should not requeue
  503. if refreshInterval <= 0 {
  504. return ctrl.Result{}
  505. }
  506. // if the last refresh time is not set, requeue after the refresh interval
  507. // note, this should not happen, as we only call this function on ExternalSecrets
  508. // that have been reconciled at least once
  509. if externalSecret.Status.RefreshTime.IsZero() {
  510. return ctrl.Result{RequeueAfter: refreshInterval}
  511. }
  512. timeSinceLastRefresh := time.Since(externalSecret.Status.RefreshTime.Time)
  513. // if the last refresh time is in the future, we should requeue immediately
  514. // note, this should not happen, as we always refresh an ExternalSecret
  515. // that has a last refresh time in the future
  516. if timeSinceLastRefresh < 0 {
  517. return ctrl.Result{Requeue: true}
  518. }
  519. // if there is time remaining, requeue after the remaining time
  520. if timeSinceLastRefresh < refreshInterval {
  521. return ctrl.Result{RequeueAfter: refreshInterval - timeSinceLastRefresh}
  522. }
  523. // otherwise, requeue immediately
  524. return ctrl.Result{Requeue: true}
  525. }
  526. func (r *Reconciler) markAsDone(externalSecret *esv1beta1.ExternalSecret, start time.Time, log logr.Logger, reason, msg string) {
  527. oldReadyCondition := GetExternalSecretCondition(externalSecret.Status, esv1beta1.ExternalSecretReady)
  528. newReadyCondition := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, reason, msg)
  529. SetExternalSecretCondition(externalSecret, *newReadyCondition)
  530. externalSecret.Status.RefreshTime = metav1.NewTime(start)
  531. externalSecret.Status.SyncedResourceVersion = util.GetResourceVersion(externalSecret.ObjectMeta)
  532. // if the status or reason has changed, log at the appropriate verbosity level
  533. if oldReadyCondition == nil || oldReadyCondition.Status != newReadyCondition.Status || oldReadyCondition.Reason != newReadyCondition.Reason {
  534. if newReadyCondition.Reason == esv1beta1.ConditionReasonSecretDeleted {
  535. log.Info("deleted secret")
  536. } else {
  537. log.Info("reconciled secret")
  538. }
  539. } else {
  540. log.V(1).Info("reconciled secret")
  541. }
  542. }
  543. func (r *Reconciler) markAsFailed(msg string, err error, externalSecret *esv1beta1.ExternalSecret, counter prometheus.Counter) {
  544. r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
  545. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, msg)
  546. SetExternalSecretCondition(externalSecret, *conditionSynced)
  547. counter.Inc()
  548. }
  549. func deleteOrphanedSecrets(ctx context.Context, cl client.Client, externalSecret *esv1beta1.ExternalSecret, secretName string) error {
  550. ownerLabel := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
  551. secretListPartial := &metav1.PartialObjectMetadataList{}
  552. secretListPartial.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("SecretList"))
  553. listOpts := &client.ListOptions{
  554. LabelSelector: labels.SelectorFromSet(map[string]string{
  555. esv1beta1.LabelOwner: ownerLabel,
  556. }),
  557. Namespace: externalSecret.Namespace,
  558. }
  559. if err := cl.List(ctx, secretListPartial, listOpts); err != nil {
  560. return err
  561. }
  562. // delete all secrets that are not the target secret
  563. for _, secretPartial := range secretListPartial.Items {
  564. if secretPartial.GetName() != secretName {
  565. if err := cl.Delete(ctx, &secretPartial); err != nil {
  566. return err
  567. }
  568. }
  569. }
  570. return nil
  571. }
  572. // createSecret creates a new secret with the given mutation function.
  573. func (r *Reconciler) createSecret(ctx context.Context, mutationFunc func(secret *v1.Secret) error, es *esv1beta1.ExternalSecret, secretName string) error {
  574. fqdn := fmt.Sprintf(fieldOwnerTemplate, es.Name)
  575. // define and mutate the new secret
  576. newSecret := &v1.Secret{
  577. ObjectMeta: metav1.ObjectMeta{
  578. Name: secretName,
  579. Namespace: es.Namespace,
  580. },
  581. Data: make(map[string][]byte),
  582. }
  583. if err := mutationFunc(newSecret); err != nil {
  584. return err
  585. }
  586. // note, we set field owner even for Create
  587. if err := r.Create(ctx, newSecret, client.FieldOwner(fqdn)); err != nil {
  588. return err
  589. }
  590. // set the binding reference to the secret
  591. // https://github.com/external-secrets/external-secrets/pull/2263
  592. es.Status.Binding = v1.LocalObjectReference{Name: newSecret.Name}
  593. r.recorder.Event(es, v1.EventTypeNormal, esv1beta1.ReasonCreated, "Created Secret")
  594. return nil
  595. }
  596. func (r *Reconciler) updateSecret(ctx context.Context, existingSecret *v1.Secret, mutationFunc func(secret *v1.Secret) error, es *esv1beta1.ExternalSecret, secretName string) error {
  597. fqdn := fmt.Sprintf(fieldOwnerTemplate, es.Name)
  598. // fail if the secret does not exist
  599. // this should never happen because we check this before calling this function
  600. if existingSecret.UID == "" {
  601. return fmt.Errorf(errUpdateNotFound, secretName)
  602. }
  603. // set the binding reference to the secret
  604. // https://github.com/external-secrets/external-secrets/pull/2263
  605. es.Status.Binding = v1.LocalObjectReference{Name: secretName}
  606. // mutate a copy of the existing secret with the mutation function
  607. updatedSecret := existingSecret.DeepCopy()
  608. if err := mutationFunc(updatedSecret); err != nil {
  609. return fmt.Errorf(errMutate, updatedSecret.Name, err)
  610. }
  611. // if the secret does not need to be updated, return early
  612. if equality.Semantic.DeepEqual(existingSecret, updatedSecret) {
  613. return nil
  614. }
  615. // if the existing secret is immutable, we can only update the object metadata
  616. if ptr.Deref(existingSecret.Immutable, false) {
  617. // check if the metadata was changed
  618. metadataChanged := !equality.Semantic.DeepEqual(existingSecret.ObjectMeta, updatedSecret.ObjectMeta)
  619. // check if the immutable data/type was changed
  620. var dataChanged bool
  621. if metadataChanged {
  622. // update the `existingSecret` object with the metadata from `updatedSecret`
  623. // this lets us compare the objects to see if the immutable data/type was changed
  624. existingSecret.ObjectMeta = *updatedSecret.ObjectMeta.DeepCopy()
  625. dataChanged = !equality.Semantic.DeepEqual(existingSecret, updatedSecret)
  626. // because we use labels and annotations to keep track of the secret,
  627. // we need to update the metadata, regardless of if the immutable data was changed
  628. // NOTE: we are using the `existingSecret` object here, as we ONLY want to update the metadata,
  629. // and we previously copied the metadata from the `updatedSecret` object
  630. if err := r.Update(ctx, existingSecret, client.FieldOwner(fqdn)); err != nil {
  631. // if we get a conflict, we should return early to requeue immediately
  632. // note, we don't wrap this error so we can handle it in the caller
  633. if apierrors.IsConflict(err) {
  634. return err
  635. }
  636. return fmt.Errorf(errUpdate, existingSecret.Name, err)
  637. }
  638. } else {
  639. // we know there was some change in the secret (or we would have returned early)
  640. // we know the metadata was NOT changed (metadataChanged == false)
  641. // so, the only thing that could have changed is the immutable data/type fields
  642. dataChanged = true
  643. }
  644. // if the immutable data was changed, we should return an error
  645. if dataChanged {
  646. return fmt.Errorf(errUpdate, existingSecret.Name, ErrSecretImmutable)
  647. }
  648. }
  649. // update the secret
  650. if err := r.Update(ctx, updatedSecret, client.FieldOwner(fqdn)); err != nil {
  651. // if we get a conflict, we should return early to requeue immediately
  652. // note, we don't wrap this error so we can handle it in the caller
  653. if apierrors.IsConflict(err) {
  654. return err
  655. }
  656. return fmt.Errorf(errUpdate, updatedSecret.Name, err)
  657. }
  658. r.recorder.Event(es, v1.EventTypeNormal, esv1beta1.ReasonUpdated, "Updated Secret")
  659. return nil
  660. }
  661. // getManagedDataKeys returns the list of data keys in a secret which are managed by a specified owner.
  662. func getManagedDataKeys(secret *v1.Secret, fieldOwner string) ([]string, error) {
  663. return getManagedFieldKeys(secret, fieldOwner, func(fields map[string]any) []string {
  664. dataFields := fields["f:data"]
  665. if dataFields == nil {
  666. return nil
  667. }
  668. df, ok := dataFields.(map[string]any)
  669. if !ok {
  670. return nil
  671. }
  672. return slices.Collect(maps.Keys(df))
  673. })
  674. }
  675. func getManagedFieldKeys(
  676. secret *v1.Secret,
  677. fieldOwner string,
  678. process func(fields map[string]any) []string,
  679. ) ([]string, error) {
  680. fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
  681. var keys []string
  682. for _, v := range secret.ObjectMeta.ManagedFields {
  683. if v.Manager != fqdn {
  684. continue
  685. }
  686. fields := make(map[string]any)
  687. err := json.Unmarshal(v.FieldsV1.Raw, &fields)
  688. if err != nil {
  689. return nil, fmt.Errorf("error unmarshaling managed fields: %w", err)
  690. }
  691. for _, key := range process(fields) {
  692. if key == "." {
  693. continue
  694. }
  695. keys = append(keys, strings.TrimPrefix(key, "f:"))
  696. }
  697. }
  698. return keys, nil
  699. }
  700. func shouldSkipClusterSecretStore(r *Reconciler, es *esv1beta1.ExternalSecret) bool {
  701. return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind
  702. }
  703. // shouldSkipUnmanagedStore iterates over all secretStore references in the externalSecret spec,
  704. // fetches the store and evaluates the controllerClass property.
  705. // Returns true if any storeRef points to store with a non-matching controllerClass.
  706. func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconciler, es *esv1beta1.ExternalSecret) (bool, error) {
  707. var storeList []esv1beta1.SecretStoreRef
  708. if es.Spec.SecretStoreRef.Name != "" {
  709. storeList = append(storeList, es.Spec.SecretStoreRef)
  710. }
  711. for _, ref := range es.Spec.Data {
  712. if ref.SourceRef != nil {
  713. storeList = append(storeList, ref.SourceRef.SecretStoreRef)
  714. }
  715. }
  716. for _, ref := range es.Spec.DataFrom {
  717. if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
  718. storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
  719. }
  720. // verify that generator's controllerClass matches
  721. if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
  722. _, obj, err := resolvers.GeneratorRef(ctx, r.Client, r.Scheme, namespace, ref.SourceRef.GeneratorRef)
  723. if err != nil {
  724. if apierrors.IsNotFound(err) {
  725. // skip non-existent generators
  726. continue
  727. }
  728. if errors.Is(err, resolvers.ErrUnableToGetGenerator) {
  729. // skip generators that we can't get (e.g. due to being invalid)
  730. continue
  731. }
  732. return false, err
  733. }
  734. skipGenerator, err := shouldSkipGenerator(r, obj)
  735. if err != nil {
  736. return false, err
  737. }
  738. if skipGenerator {
  739. return true, nil
  740. }
  741. }
  742. }
  743. for _, ref := range storeList {
  744. var store esv1beta1.GenericStore
  745. switch ref.Kind {
  746. case esv1beta1.SecretStoreKind, "":
  747. store = &esv1beta1.SecretStore{}
  748. case esv1beta1.ClusterSecretStoreKind:
  749. store = &esv1beta1.ClusterSecretStore{}
  750. namespace = ""
  751. }
  752. err := r.Get(ctx, types.NamespacedName{
  753. Name: ref.Name,
  754. Namespace: namespace,
  755. }, store)
  756. if err != nil {
  757. if apierrors.IsNotFound(err) {
  758. // skip non-existent stores
  759. continue
  760. }
  761. return false, err
  762. }
  763. class := store.GetSpec().Controller
  764. if class != "" && class != r.ControllerClass {
  765. return true, nil
  766. }
  767. }
  768. return false, nil
  769. }
  770. func shouldRefresh(es *esv1beta1.ExternalSecret) bool {
  771. // if the refresh interval is 0, and we have synced previously, we should not refresh
  772. if es.Spec.RefreshInterval.Duration <= 0 && es.Status.SyncedResourceVersion != "" {
  773. return false
  774. }
  775. // if the ExternalSecret has been updated, we should refresh
  776. if es.Status.SyncedResourceVersion != util.GetResourceVersion(es.ObjectMeta) {
  777. return true
  778. }
  779. // if the last refresh time is zero, we should refresh
  780. if es.Status.RefreshTime.IsZero() {
  781. return true
  782. }
  783. // if the last refresh time is in the future, we should refresh
  784. if es.Status.RefreshTime.Time.After(time.Now()) {
  785. return true
  786. }
  787. // if the last refresh time + refresh interval is before now, we should refresh
  788. return es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(time.Now())
  789. }
  790. // isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
  791. func isSecretValid(existingSecret *v1.Secret) bool {
  792. // if target secret doesn't exist, we need to refresh
  793. if existingSecret.UID == "" {
  794. return false
  795. }
  796. // if the managed label is missing or incorrect, then it's invalid
  797. if existingSecret.Labels[esv1beta1.LabelManaged] != esv1beta1.LabelManagedValue {
  798. return false
  799. }
  800. // if the data-hash annotation is missing or incorrect, then it's invalid
  801. // this is how we know if the data has chanced since we last updated the secret
  802. if existingSecret.Annotations[esv1beta1.AnnotationDataHash] != utils.ObjectHash(existingSecret.Data) {
  803. return false
  804. }
  805. return true
  806. }
  807. // SetupWithManager returns a new controller builder that will be started by the provided Manager.
  808. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  809. r.recorder = mgr.GetEventRecorderFor("external-secrets")
  810. // index ExternalSecrets based on the target secret name,
  811. // this lets us quickly find all ExternalSecrets which target a specific Secret
  812. if err := mgr.GetFieldIndexer().IndexField(context.Background(), &esv1beta1.ExternalSecret{}, indexESTargetSecretNameField, func(obj client.Object) []string {
  813. es := obj.(*esv1beta1.ExternalSecret)
  814. // if the target name is set, use that as the index
  815. if es.Spec.Target.Name != "" {
  816. return []string{es.Spec.Target.Name}
  817. }
  818. // otherwise, use the ExternalSecret name
  819. return []string{es.Name}
  820. }); err != nil {
  821. return err
  822. }
  823. // predicate function to ignore secret events unless they have the "managed" label
  824. secretHasESLabel := predicate.NewPredicateFuncs(func(object client.Object) bool {
  825. value, hasLabel := object.GetLabels()[esv1beta1.LabelManaged]
  826. return hasLabel && value == esv1beta1.LabelManagedValue
  827. })
  828. return ctrl.NewControllerManagedBy(mgr).
  829. WithOptions(opts).
  830. For(&esv1beta1.ExternalSecret{}).
  831. // we cant use Owns(), as we don't set ownerReferences when the creationPolicy is not Owner.
  832. // we use WatchesMetadata() to reduce memory usage, as otherwise we have to process full secret objects.
  833. WatchesMetadata(
  834. &v1.Secret{},
  835. handler.EnqueueRequestsFromMapFunc(r.findObjectsForSecret),
  836. builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, secretHasESLabel),
  837. ).
  838. Complete(r)
  839. }
  840. func (r *Reconciler) findObjectsForSecret(ctx context.Context, secret client.Object) []reconcile.Request {
  841. externalSecretsList := &esv1beta1.ExternalSecretList{}
  842. listOps := &client.ListOptions{
  843. FieldSelector: fields.OneTermEqualSelector(indexESTargetSecretNameField, secret.GetName()),
  844. Namespace: secret.GetNamespace(),
  845. }
  846. err := r.List(ctx, externalSecretsList, listOps)
  847. if err != nil {
  848. return []reconcile.Request{}
  849. }
  850. requests := make([]reconcile.Request, len(externalSecretsList.Items))
  851. for i := range externalSecretsList.Items {
  852. requests[i] = reconcile.Request{
  853. NamespacedName: types.NamespacedName{
  854. Name: externalSecretsList.Items[i].GetName(),
  855. Namespace: externalSecretsList.Items[i].GetNamespace(),
  856. },
  857. }
  858. }
  859. return requests
  860. }
  861. func BuildManagedSecretClient(mgr ctrl.Manager) (client.Client, error) {
  862. // secrets we manage will have the `reconcile.external-secrets.io/managed=true` label
  863. managedLabelReq, _ := labels.NewRequirement(esv1beta1.LabelManaged, selection.Equals, []string{esv1beta1.LabelManagedValue})
  864. managedLabelSelector := labels.NewSelector().Add(*managedLabelReq)
  865. // create a new cache with a label selector for managed secrets
  866. // NOTE: this means that the cache/client will be unable to see secrets without the "managed" label
  867. secretCacheOpts := cache.Options{
  868. HTTPClient: mgr.GetHTTPClient(),
  869. Scheme: mgr.GetScheme(),
  870. Mapper: mgr.GetRESTMapper(),
  871. ByObject: map[client.Object]cache.ByObject{
  872. &v1.Secret{}: {
  873. Label: managedLabelSelector,
  874. },
  875. },
  876. // this requires us to explicitly start an informer for each object type
  877. // and helps avoid people mistakenly using the secret client for other resources
  878. ReaderFailOnMissingInformer: true,
  879. }
  880. secretCache, err := cache.New(mgr.GetConfig(), secretCacheOpts)
  881. if err != nil {
  882. return nil, err
  883. }
  884. // start an informer for secrets
  885. // this is required because we set ReaderFailOnMissingInformer to true
  886. _, err = secretCache.GetInformer(context.Background(), &v1.Secret{})
  887. if err != nil {
  888. return nil, err
  889. }
  890. // add the secret cache to the manager, so that it starts at the same time
  891. err = mgr.Add(secretCache)
  892. if err != nil {
  893. return nil, err
  894. }
  895. // create a new client that uses the secret cache
  896. secretClient, err := client.New(mgr.GetConfig(), client.Options{
  897. HTTPClient: mgr.GetHTTPClient(),
  898. Scheme: mgr.GetScheme(),
  899. Mapper: mgr.GetRESTMapper(),
  900. Cache: &client.CacheOptions{
  901. Reader: secretCache,
  902. },
  903. })
  904. if err != nil {
  905. return nil, err
  906. }
  907. return secretClient, nil
  908. }