controller.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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 provider implements the controller for Provider resources.
  13. package provider
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. "github.com/go-logr/logr"
  19. corev1 "k8s.io/api/core/v1"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. ctrl "sigs.k8s.io/controller-runtime"
  24. "sigs.k8s.io/controller-runtime/pkg/client"
  25. "sigs.k8s.io/controller-runtime/pkg/controller"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. pb "github.com/external-secrets/external-secrets/proto/provider"
  28. "github.com/external-secrets/external-secrets/providers/v2/common/grpc"
  29. )
  30. // Reconciler reconciles a Provider object.
  31. type Reconciler struct {
  32. client.Client
  33. Log logr.Logger
  34. Scheme *runtime.Scheme
  35. RequeueInterval time.Duration
  36. }
  37. // Reconcile validates the Provider and updates its status.
  38. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  39. log := r.Log.WithValues("Provider", req.NamespacedName)
  40. start := time.Now()
  41. defer func() {
  42. duration := time.Since(start)
  43. if gaugeVec := GetGaugeVec(ProviderReconcileDurationKey); gaugeVec != nil {
  44. gaugeVec.WithLabelValues(req.Name, req.Namespace).Set(duration.Seconds())
  45. }
  46. }()
  47. log.Info("reconciling Provider")
  48. var store esv1.Provider
  49. if err := r.Get(ctx, req.NamespacedName, &store); err != nil {
  50. if apierrors.IsNotFound(err) {
  51. RemoveMetrics(req.Namespace, req.Name)
  52. return ctrl.Result{}, nil
  53. }
  54. log.Error(err, "unable to get Provider")
  55. return ctrl.Result{}, err
  56. }
  57. // Validate provider config and get capabilities
  58. capabilities, err := r.validateStoreAndGetCapabilities(ctx, &store)
  59. if err != nil {
  60. log.Error(err, "validation failed")
  61. r.setNotReadyCondition(&store, "ValidationFailed", err.Error())
  62. store.Status.Capabilities = ""
  63. if updateErr := r.Status().Update(ctx, &store); updateErr != nil {
  64. log.Error(updateErr, "failed to update status")
  65. return ctrl.Result{}, updateErr
  66. }
  67. // Requeue after interval to retry
  68. return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
  69. }
  70. // Set ready condition and capabilities
  71. r.setReadyCondition(&store)
  72. store.Status.Capabilities = capabilities
  73. if err := r.Status().Update(ctx, &store); err != nil {
  74. log.Error(err, "failed to update status")
  75. return ctrl.Result{}, err
  76. }
  77. log.Info("Provider is ready", "capabilities", capabilities)
  78. return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
  79. }
  80. // validateStoreAndGetCapabilities validates the Provider configuration and retrieves capabilities by:
  81. // 1. Creating a gRPC client to the provider
  82. // 2. Calling Validate() on the provider with the ProviderReference
  83. // 3. Calling Capabilities() to get the provider's capabilities.
  84. func (r *Reconciler) validateStoreAndGetCapabilities(ctx context.Context, store *esv1.Provider) (esv1.ProviderCapabilities, error) {
  85. // Get provider address
  86. address := store.Spec.Config.Address
  87. if address == "" {
  88. return "", fmt.Errorf("provider address is required")
  89. }
  90. tlsSecretNamespace := grpc.ResolveTLSSecretNamespace(
  91. store.Spec.Config.Address,
  92. "",
  93. store.Namespace,
  94. store.Spec.Config.ProviderRef.Namespace,
  95. )
  96. // Load TLS configuration
  97. tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, tlsSecretNamespace)
  98. if err != nil {
  99. return "", fmt.Errorf("failed to load TLS config: %w", err)
  100. }
  101. // Create gRPC client with TLS
  102. client, err := grpc.NewClient(address, tlsConfig)
  103. if err != nil {
  104. return "", fmt.Errorf("failed to create gRPC client: %w", err)
  105. }
  106. defer func() { _ = client.Close(ctx) }()
  107. // Convert ProviderReference to protobuf format
  108. providerRef := &pb.ProviderReference{
  109. ApiVersion: store.Spec.Config.ProviderRef.APIVersion,
  110. Kind: store.Spec.Config.ProviderRef.Kind,
  111. Name: store.Spec.Config.ProviderRef.Name,
  112. Namespace: store.Spec.Config.ProviderRef.Namespace,
  113. }
  114. // Validate the provider configuration
  115. if err := client.Validate(ctx, providerRef, store.Namespace); err != nil {
  116. r.Log.Error(err, "provider validation failed")
  117. return "", fmt.Errorf("provider validation failed: %w", err)
  118. }
  119. // Get provider capabilities
  120. caps, err := client.Capabilities(ctx, providerRef, store.Namespace)
  121. if err != nil {
  122. r.Log.Error(err, "failed to get capabilities")
  123. // Don't fail validation if capabilities check fails, just log and default to ReadOnly
  124. return esv1.ProviderReadOnly, nil
  125. }
  126. // Map gRPC capabilities to our API type
  127. var capabilities esv1.ProviderCapabilities
  128. switch caps {
  129. case pb.SecretStoreCapabilities_READ_ONLY:
  130. capabilities = esv1.ProviderReadOnly
  131. case pb.SecretStoreCapabilities_WRITE_ONLY:
  132. capabilities = esv1.ProviderWriteOnly
  133. case pb.SecretStoreCapabilities_READ_WRITE:
  134. capabilities = esv1.ProviderReadWrite
  135. default:
  136. capabilities = esv1.ProviderReadOnly
  137. }
  138. return capabilities, nil
  139. }
  140. // setReadyCondition sets the Ready condition to True.
  141. func (r *Reconciler) setReadyCondition(store *esv1.Provider) {
  142. condition := esv1.ProviderCondition{
  143. Type: esv1.ProviderReady,
  144. Status: metav1.ConditionTrue,
  145. LastTransitionTime: metav1.Now(),
  146. Reason: "Validated",
  147. Message: "Provider is ready",
  148. }
  149. r.setCondition(store, condition)
  150. }
  151. // setNotReadyCondition sets the Ready condition to False.
  152. func (r *Reconciler) setNotReadyCondition(store *esv1.Provider, reason, message string) {
  153. condition := esv1.ProviderCondition{
  154. Type: esv1.ProviderReady,
  155. Status: metav1.ConditionFalse,
  156. LastTransitionTime: metav1.Now(),
  157. Reason: reason,
  158. Message: message,
  159. }
  160. r.setCondition(store, condition)
  161. }
  162. // setCondition updates or adds a condition to the Provider status.
  163. func (r *Reconciler) setCondition(store *esv1.Provider, newCondition esv1.ProviderCondition) {
  164. // Find existing condition
  165. for i, condition := range store.Status.Conditions {
  166. if condition.Type == newCondition.Type {
  167. // Preserve LastTransitionTime unless the condition status actually changes.
  168. if condition.Status == newCondition.Status {
  169. newCondition.LastTransitionTime = condition.LastTransitionTime
  170. }
  171. if condition.Status != newCondition.Status ||
  172. condition.Reason != newCondition.Reason ||
  173. condition.Message != newCondition.Message {
  174. store.Status.Conditions[i] = newCondition
  175. }
  176. // Update metrics
  177. UpdateStatusCondition(store, newCondition)
  178. return
  179. }
  180. }
  181. // Add new condition
  182. store.Status.Conditions = append(store.Status.Conditions, newCondition)
  183. // Update metrics
  184. UpdateStatusCondition(store, newCondition)
  185. }
  186. // SetupWithManager sets up the controller with the Manager.
  187. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  188. return ctrl.NewControllerManagedBy(mgr).
  189. WithOptions(opts).
  190. For(&esv1.Provider{}).
  191. Owns(&corev1.Secret{}). // Watch secrets that might be used for auth
  192. Complete(r)
  193. }