func NewV1beta2Reconciler()

in internal/controller/acrpullbinding_v1beta2_controller.go [51:177]


func NewV1beta2Reconciler(opts *V1beta2ReconcilerOpts) *PullBindingReconciler {
	if opts.now == nil {
		opts.now = time.Now
	}
	if opts.fetchArmToken == nil {
		opts.fetchArmToken = authorizer.ARMTokenForBinding
	}
	if opts.exchangeArmTokenForAcrToken == nil {
		opts.exchangeArmTokenForAcrToken = authorizer.ExchangeACRAccessTokenForSpec
	}
	if opts.mintToken == nil {
		opts.mintToken = func(ctx context.Context, serviceAccountNamespace, serviceAccountName string) (*authenticationv1.TokenRequest, error) {
			return opts.ServiceAccountClient.ServiceAccounts(serviceAccountNamespace).CreateToken(ctx, serviceAccountName, &authenticationv1.TokenRequest{
				Spec: authenticationv1.TokenRequestSpec{
					Audiences: []string{opts.ServiceAccountTokenAudience},
				},
			}, metav1.CreateOptions{})
		}
	}

	return &PullBindingReconciler{
		genericReconciler: &genericReconciler[*msiacrpullv1beta2.AcrPullBinding]{
			Client: opts.Client,
			Logger: opts.Logger,
			Scheme: opts.Scheme,
			NewBinding: func() *msiacrpullv1beta2.AcrPullBinding {
				return &msiacrpullv1beta2.AcrPullBinding{}
			},
			AddFinalizer: func(binding *msiacrpullv1beta2.AcrPullBinding, finalizer string) *msiacrpullv1beta2.AcrPullBinding {
				updated := binding.DeepCopy()
				updated.ObjectMeta.Finalizers = append(updated.ObjectMeta.Finalizers, finalizer)
				return updated
			},
			RemoveFinalizer: func(binding *msiacrpullv1beta2.AcrPullBinding, finalizer string) *msiacrpullv1beta2.AcrPullBinding {
				updated := binding.DeepCopy()
				updated.ObjectMeta.Finalizers = slices.DeleteFunc(updated.ObjectMeta.Finalizers, func(s string) bool {
					return s == finalizer
				})
				return updated
			},
			GetServiceAccountName: func(binding *msiacrpullv1beta2.AcrPullBinding) string {
				return binding.Spec.ServiceAccountName
			},
			GetPullSecretName: func(binding *msiacrpullv1beta2.AcrPullBinding) string {
				return pullSecretName(binding.ObjectMeta.Name)
			},
			GetInputsHash: func(binding *msiacrpullv1beta2.AcrPullBinding) string {
				return inputsHash(binding.Spec)
			},
			CreatePullCredential: func(ctx context.Context, binding *msiacrpullv1beta2.AcrPullBinding, serviceAccount *corev1.ServiceAccount) (string, time.Time, error) {
				var tenantId, clientId, token string
				if binding.Spec.Auth.WorkloadIdentity != nil {
					if binding.Spec.Auth.WorkloadIdentity.TenantID != "" {
						tenantId = binding.Spec.Auth.WorkloadIdentity.TenantID
						clientId = binding.Spec.Auth.WorkloadIdentity.ClientID
					} else {
						for _, annotation := range []struct { // n.b. we need an array here to be able to test for the error output
							value string
							into  *string
						}{
							{value: azworkloadidentity.ClientIDAnnotation, into: &clientId},
							{value: azworkloadidentity.TenantIDAnnotation, into: &tenantId},
						} {
							value, set := serviceAccount.Annotations[annotation.value]
							if !set {
								return "", time.Time{}, fmt.Errorf("service account %s missing %s annotation", serviceAccount.Name, annotation.value)
							}
							*annotation.into = value
						}
					}

					response, err := opts.mintToken(ctx, serviceAccount.Namespace, serviceAccount.Name)
					if err != nil {
						return "", time.Time{}, fmt.Errorf("failed to mint service account token: %w", err)
					}
					token = response.Status.Token
				}

				armToken, err := opts.fetchArmToken(ctx, binding.Spec, tenantId, clientId, token)
				if err != nil {
					return "", time.Time{}, fmt.Errorf("failed to retrieve ARM token: %v", err)
				}

				acrToken, err := opts.exchangeArmTokenForAcrToken(ctx, armToken, binding.Spec.ACR)
				if err != nil {
					return "", time.Time{}, fmt.Errorf("failed to retrieve ACR token: %v", err)
				}

				dockerConfig, err := authorizer.CreateACRDockerCfg(binding.Spec.ACR.Server, acrToken)
				if err != nil {
					return "", time.Time{}, fmt.Errorf("failed to write ACR dockercfg: %v", err)
				}
				return dockerConfig, acrToken.ExpiresOn, nil
			},
			UpdateStatusError: func(binding *msiacrpullv1beta2.AcrPullBinding, s string) *msiacrpullv1beta2.AcrPullBinding {
				updated := binding.DeepCopy()
				updated.Status.Error = s
				return updated
			},
			NeedsRefresh: func(logger logr.Logger, pullSecret *corev1.Secret, now func() time.Time) bool {
				return needsRefresh(now, pullSecretRefresh(logger, pullSecret), pullSecretExpiry(logger, pullSecret), opts.TTLRotationFraction)
			},
			RequeueAfter: func(now func() time.Time) func(binding *msiacrpullv1beta2.AcrPullBinding) time.Duration {
				return func(binding *msiacrpullv1beta2.AcrPullBinding) time.Duration {
					var requeueAfter time.Duration
					if binding.Status.TokenExpirationTime != nil && binding.Status.LastTokenRefreshTime != nil {
						refresh, expiry := binding.Status.LastTokenRefreshTime.Time, binding.Status.TokenExpirationTime.Time
						requeueAfter = refreshBoundary(refresh, expiry, opts.TTLRotationFraction).Sub(now())
					}
					return requeueAfter
				}
			},
			NeedsStatusUpdate: func(refresh time.Time, expiry time.Time, binding *msiacrpullv1beta2.AcrPullBinding) bool {
				return binding.Status.Error != "" || binding.Status.TokenExpirationTime == nil || !binding.Status.TokenExpirationTime.Equal(&metav1.Time{Time: expiry}) ||
					binding.Status.LastTokenRefreshTime == nil || !binding.Status.LastTokenRefreshTime.Equal(&metav1.Time{Time: refresh})
			},
			UpdateStatus: func(refresh time.Time, expiry time.Time, binding *msiacrpullv1beta2.AcrPullBinding) *msiacrpullv1beta2.AcrPullBinding {
				updated := binding.DeepCopy()
				updated.Status.TokenExpirationTime = &metav1.Time{Time: expiry}
				updated.Status.LastTokenRefreshTime = &metav1.Time{Time: refresh}
				updated.Status.Error = ""
				return updated
			},
			now: opts.now,
		},
	}
}