Repository: rancher/fleet

Issue:
[RFE] Reduce GitRepo status update when deploying workloads
**Is your feature request related to a problem? Please describe.**
On large scale environments, the status of GitRepo get updated too often (multiple times per second sometimes) when a new workload is deployed: a new Bundle is added to a repo, targets selection changes, new cluster added, etc.
Despite keeping the status as up-to-date as possible may seem desirable for reactivity reasons, it's could be even counterproductive, as having too many update events may incur into higher API latency and network usage (and ultimately contribute to temporarily increase etcd's DB size).

**Describe the solution you'd like**
GitRepo status is up-to-date in a timely fashion but with fewer updates events.

**Describe alternatives you've considered**
Review Bundle updates frequency, but this would be enough in high scale environments.

**Additional context**


Discussion:
@mmartin24: Already tested by dev.

QA regression tests look ok on `v2.12-62362dfd905a75f77865a47aff683a195cc6ac66-head` ([link](https://github.com/rancher/fleet-e2e/actions/runs/15433161956/job/43434605117)) with `fleet:107.0.0+up0.13.0-alpha.3`

Solution patch:
diff --git a/.github/workflows/e2e-test-fleet-in-rancher.yml b/.github/workflows/e2e-test-fleet-in-rancher.yml
index 532cfb93..64de010b 100644
--- a/.github/workflows/e2e-test-fleet-in-rancher.yml
+++ b/.github/workflows/e2e-test-fleet-in-rancher.yml
@@ -183,6 +183,7 @@ jobs:
           until kubectl get bundles -n fleet-local | grep -q "fleet-agent-local.*1/1"; do sleep 3; done
 
           # wait for deployment of webhook, needed by tests
+          until kubectl get deployments -n cattle-system | grep -q "rancher-webhook"; do sleep 3; done
           kubectl -n cattle-system rollout status deploy/rancher-webhook
 
           helm list -A
diff --git a/README.md b/README.md
index c1b2b0b1..ed7a98ce 100644
--- a/README.md
+++ b/README.md
@@ -37,9 +37,9 @@ Install the Fleet Helm charts (there's two because we separate out CRDs for ulti
 
 ```shell
 helm -n cattle-fleet-system install --create-namespace --wait \
-    fleet-crd https://github.com/rancher/fleet/releases/download/v0.12.1/fleet-crd-0.12.1.tgz
+    fleet-crd https://github.com/rancher/fleet/releases/download/v0.12.2/fleet-crd-0.12.2.tgz
 helm -n cattle-fleet-system install --create-namespace --wait \
-    fleet https://github.com/rancher/fleet/releases/download/v0.12.1/fleet-0.12.1.tgz
+    fleet https://github.com/rancher/fleet/releases/download/v0.12.2/fleet-0.12.2.tgz
 ```
 
 ## Add a Git Repo to watch
diff --git a/charts/fleet-crd/templates/crds.yaml b/charts/fleet-crd/templates/crds.yaml
index 24721d4b..172d6295 100644
--- a/charts/fleet-crd/templates/crds.yaml
+++ b/charts/fleet-crd/templates/crds.yaml
@@ -6498,7 +6498,7 @@ spec:
                   type: string
                 repo:
                   description: Repo is a URL to a git repo to clone and index.
-                  nullable: true
+                  minLength: 1
                   type: string
                 revision:
                   description: Revision A specific commit or tag to operate on.
@@ -6660,6 +6660,8 @@ spec:
                   description: WebhookSecret contains the name of the secret to use
                     for webhook parsing
                   type: string
+              required:
+                - repo
               type: object
             status:
               properties:
diff --git a/charts/fleet/templates/deployment.yaml b/charts/fleet/templates/deployment.yaml
index df6f8c22..9963e8e5 100644
--- a/charts/fleet/templates/deployment.yaml
+++ b/charts/fleet/templates/deployment.yaml
@@ -197,6 +197,18 @@ spec:
         - name: FLEET_AGENT_REPLICA_COUNT
           value: "{{ $.Values.agent.replicas }}"
         {{- end }}
+        {{- if $.Values.agent.leaderElection.leaseDuration }}
+        - name: FLEET_AGENT_ELECTION_LEASE_DURATION
+          value: {{$.Values.agent.leaderElection.leaseDuration}}
+        {{- end }}
+        {{- if $.Values.agent.leaderElection.retryPeriod }}
+        - name: FLEET_AGENT_ELECTION_RETRY_PERIOD
+          value: {{$.Values.agent.leaderElection.retryPeriod}}
+        {{- end }}
+        {{- if $.Values.agent.leaderElection.renewDeadline }}
+        - name: FLEET_AGENT_ELECTION_RENEW_DEADLINE
+          value: {{$.Values.agent.leaderElection.renewDeadline}}
+        {{- end }}
         image: '{{ template "system_default_registry" $ }}{{ $.Values.image.repository }}:{{ $.Values.image.tag }}'
         name: fleet-agentmanagement
         imagePullPolicy: "{{ $.Values.image.imagePullPolicy }}"
diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml
index ae5c1877..288cccf0 100644
--- a/charts/fleet/values.yaml
+++ b/charts/fleet/values.yaml
@@ -130,6 +130,12 @@ agent:
     workers:
       bundledeployment: "50"
       drift: "50"
+  ## Leader election configuration
+  leaderElection:
+    leaseDuration: 30s
+    retryPeriod: 10s
+    renewDeadline: 25s
+
 
 # Extra environment variables passed to the fleet pods.
 # extraEnv:
diff --git a/internal/cmd/agent/controller/drift_controller.go b/internal/cmd/agent/controller/drift_controller.go
index 6130775a..bcbad984 100644
--- a/internal/cmd/agent/controller/drift_controller.go
+++ b/internal/cmd/agent/controller/drift_controller.go
@@ -10,6 +10,7 @@ import (
 	"github.com/rancher/fleet/internal/cmd/agent/deployer/monitor"
 	fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 
+	"github.com/go-logr/logr"
 	"github.com/rancher/wrangler/v3/pkg/condition"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -121,19 +122,34 @@ func (r *DriftReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
 	}
 
 	// final status update
-	statusPatch := client.MergeFrom(orig)
-	err = r.Status().Patch(ctx, bd, statusPatch)
-	if apierrors.IsNotFound(err) {
-		merr = append(merr, fmt.Errorf("bundledeployment has been deleted: %w", err))
-	} else if err != nil {
-		merr = append(merr, fmt.Errorf("failed final update to bundledeployment status: %w", err))
-	} else {
-		logger.V(1).Info("Reconcile finished, bundledeployment status updated", "statusPatch", statusPatch)
+	if err := r.updateStatus(ctx, logger, orig, bd); err != nil {
+		if apierrors.IsNotFound(err) {
+			merr = append(merr, fmt.Errorf("bundledeployment has been deleted: %w", err))
+		} else {
+			merr = append(merr, fmt.Errorf("failed final update to bundledeployment status: %w", err))
+		}
 	}
 
 	return ctrl.Result{}, errutil.NewAggregate(merr)
 }
 
+func (r *DriftReconciler) updateStatus(ctx context.Context, logger logr.Logger, orig *fleetv1.BundleDeployment, obj *fleetv1.BundleDeployment) error {
+	statusPatch := client.MergeFrom(orig)
+
+	// Pre-calculate patch contents, to skip request if it's empty
+	patchData, err := statusPatch.Data(obj)
+	if err == nil && string(patchData) == "{}" {
+		return nil
+	}
+
+	if err := r.Status().Patch(ctx, obj, statusPatch); err != nil {
+		return err
+	}
+
+	logger.V(1).Info("Reconcile finished, bundledeployment status updated", "statusPatch", string(patchData))
+	return nil
+}
+
 // enqueueRequestHandlerWithDelay implements a TypedEventHandler that introduces a constant delay in the resources being enqueued
 // Due to how workqueue.TypedDelayingInterface's AddAfter is implemented, successive calls with the same key are aggregated.
 // Only implemented for Generic events, as this is only meant to be used from with source.Channel to receive internal events, not from an informer
diff --git a/internal/cmd/agent/root.go b/internal/cmd/agent/root.go
index 26264153..316af315 100644
--- a/internal/cmd/agent/root.go
+++ b/internal/cmd/agent/root.go
@@ -120,9 +120,9 @@ func (a *FleetAgent) Run(cmd *cobra.Command, args []string) error {
 
 	leaderElectionConfig := leaderelection.LeaderElectionConfig{
 		Lock:          &lock,
-		LeaseDuration: *leaderOpts.LeaseDuration,
-		RetryPeriod:   *leaderOpts.RetryPeriod,
-		RenewDeadline: *leaderOpts.RenewDeadline,
+		LeaseDuration: leaderOpts.LeaseDuration,
+		RetryPeriod:   leaderOpts.RetryPeriod,
+		RenewDeadline: leaderOpts.RenewDeadline,
 		Callbacks: leaderelection.LeaderCallbacks{
 			OnStartedLeading: func(ctx context.Context) {
 				// Handle agent registration.
diff --git a/internal/cmd/controller/agentmanagement/agent/agent.go b/internal/cmd/controller/agentmanagement/agent/agent.go
index a6a1bb20..42e4058a 100644
--- a/internal/cmd/controller/agentmanagement/agent/agent.go
+++ b/internal/cmd/controller/agentmanagement/agent/agent.go
@@ -79,6 +79,10 @@ func AgentWithConfig(ctx context.Context, agentNamespace, controllerNamespace, a
 	mo.DriftWorkers = cfg.AgentWorkers.Drift
 
 	mo.AgentReplicas = cmd.ParseEnvAgentReplicaCount()
+	mo.LeaderElectionOptions, err = cmd.NewLeaderElectionOptionsWithPrefix("FLEET_AGENT")
+	if err != nil {
+		return objs, err
+	}
 
 	objs = append(objs, Manifest(agentNamespace, agentScope, mo)...)
 
diff --git a/internal/cmd/controller/agentmanagement/agent/manifest.go b/internal/cmd/controller/agentmanagement/agent/manifest.go
index 6ad46410..3cba4afd 100644
--- a/internal/cmd/controller/agentmanagement/agent/manifest.go
+++ b/internal/cmd/controller/agentmanagement/agent/manifest.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/sirupsen/logrus"
 
+	"github.com/rancher/fleet/internal/cmd"
 	"github.com/rancher/fleet/internal/config"
 	"github.com/rancher/fleet/internal/names"
 
@@ -25,10 +26,7 @@ var (
 )
 
 const (
-	DefaultName           = "fleet-agent"
-	electionLeaseDuration = "30s"
-	electionRetryPeriod   = "10s"
-	electionRenewDeadline = "25s"
+	DefaultName = "fleet-agent"
 )
 
 type ManifestOptions struct {
@@ -45,6 +43,7 @@ type ManifestOptions struct {
 	HostNetwork             bool
 	BundleDeploymentWorkers string
 	DriftWorkers            string
+	cmd.LeaderElectionOptions
 }
 
 // Manifest builds and returns a deployment manifest for the fleet-agent with a
@@ -191,9 +190,9 @@ func agentApp(namespace string, agentScope string, opts ManifestOptions) *appsv1
 								},
 								{Name: "AGENT_SCOPE", Value: agentScope},
 								{Name: "CHECKIN_INTERVAL", Value: opts.CheckinInterval},
-								{Name: "CATTLE_ELECTION_LEASE_DURATION", Value: electionLeaseDuration},
-								{Name: "CATTLE_ELECTION_RETRY_PERIOD", Value: electionRetryPeriod},
-								{Name: "CATTLE_ELECTION_RENEW_DEADLINE", Value: electionRenewDeadline},
+								{Name: "CATTLE_ELECTION_LEASE_DURATION", Value: opts.LeaseDuration.String()},
+								{Name: "CATTLE_ELECTION_RETRY_PERIOD", Value: opts.RetryPeriod.String()},
+								{Name: "CATTLE_ELECTION_RENEW_DEADLINE", Value: opts.RenewDeadline.String()},
 							},
 							Command: []string{
 								"fleetagent",
diff --git a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go
index e091f7a1..5d5f46f1 100644
--- a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go
+++ b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go
@@ -258,6 +258,10 @@ func (h *handler) newAgentBundle(ns string, cluster *fleet.Cluster) (runtime.Obj
 	}
 
 	agentReplicas := cmd.ParseEnvAgentReplicaCount()
+	leaderElectionOptions, err := cmd.NewLeaderElectionOptionsWithPrefix("FLEET_AGENT")
+	if err != nil {
+		return nil, err
+	}
 
 	// Notice we only set the agentScope when it's a non-default agentNamespace. This is for backwards compatibility
 	// for when we didn't have agent scope before
@@ -280,6 +284,7 @@ func (h *handler) newAgentBundle(ns string, cluster *fleet.Cluster) (runtime.Obj
 			BundleDeploymentWorkers: cfg.AgentWorkers.BundleDeployment,
 			DriftWorkers:            cfg.AgentWorkers.Drift,
 			AgentReplicas:           agentReplicas,
+			LeaderElectionOptions:   leaderElectionOptions,
 		},
 	)
 	agentYAML, err := yaml.Export(objs...)
diff --git a/internal/cmd/controller/gitops/operator.go b/internal/cmd/controller/gitops/operator.go
index f32af762..38bf98cb 100644
--- a/internal/cmd/controller/gitops/operator.go
+++ b/internal/cmd/controller/gitops/operator.go
@@ -111,9 +111,9 @@ func (g *GitOperator) Run(cmd *cobra.Command, args []string) error {
 		LeaderElection:          g.EnableLeaderElection,
 		LeaderElectionID:        fmt.Sprintf("fleet-gitops-leader-election-shard%s", shardIDSuffix),
 		LeaderElectionNamespace: namespace,
-		LeaseDuration:           leaderOpts.LeaseDuration,
-		RenewDeadline:           leaderOpts.RenewDeadline,
-		RetryPeriod:             leaderOpts.RetryPeriod,
+		LeaseDuration:           &leaderOpts.LeaseDuration,
+		RenewDeadline:           &leaderOpts.RenewDeadline,
+		RetryPeriod:             &leaderOpts.RetryPeriod,
 		// resync to pick up lost gitrepos
 		Cache: cache.Options{
 			SyncPeriod: &syncPeriod,
diff --git a/internal/cmd/controller/gitops/reconciler/gitjob.go b/internal/cmd/controller/gitops/reconciler/gitjob.go
index 3bd21680..1b868b6f 100644
--- a/internal/cmd/controller/gitops/reconciler/gitjob.go
+++ b/internal/cmd/controller/gitops/reconciler/gitjob.go
@@ -1,89 +1,945 @@
-// Copyright (c) 2021-2023 SUSE LLC
-
 package reconciler
 
 import (
 	"context"
+	"encoding/json"
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"time"
 
+	"github.com/go-logr/logr"
+	fleetcli "github.com/rancher/fleet/internal/cmd/cli"
+	"github.com/rancher/fleet/internal/config"
+	"github.com/rancher/fleet/internal/names"
+	"github.com/rancher/fleet/internal/ociwrapper"
+	ssh "github.com/rancher/fleet/internal/ssh"
 	v1alpha1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+	"github.com/rancher/fleet/pkg/cert"
+	fleetevent "github.com/rancher/fleet/pkg/event"
+	"github.com/rancher/fleet/pkg/sharding"
+
+	appsv1 "k8s.io/api/apps/v1"
+	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
-	rbacv1 "k8s.io/api/rbac/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+	"sigs.k8s.io/controller-runtime/pkg/log"
+)
+
+const (
+	bundleCAVolumeName        = "additional-ca"
+	bundleCAFile              = "additional-ca.crt"
+	gitCredentialVolumeName   = "git-credential" // #nosec G101 this is not a credential
+	ociRegistryAuthVolumeName = "oci-auth"
+	gitClonerVolumeName       = "git-cloner"
+	emptyDirVolumeName        = "git-cloner-empty-dir"
+
+	fleetHomeDir = "/fleet-home"
+
+	bundleOptionsSeparatorChars = ":,|?<>"
 )
 
-func (r *GitJobReconciler) createServiceAccount(ctx context.Context, gitRepo *v1alpha1.GitRepo, saName string) error {
-	sa := &corev1.ServiceAccount{
+func (r *GitJobReconciler) createJobAndResources(ctx context.Context, gitrepo *v1alpha1.GitRepo, logger logr.Logger) error {
+	logger.V(1).Info("Creating Git job resources")
+	if err := r.createJobRBAC(ctx, gitrepo); err != nil {
+		return fmt.Errorf("failed to create RBAC resources for git job: %w", err)
+	}
+	if err := r.createTargetsConfigMap(ctx, gitrepo); err != nil {
+		return fmt.Errorf("failed to create targets config map for git job: %w", err)
+	}
+	if _, err := r.createCABundleSecret(ctx, gitrepo, caBundleName(gitrepo)); err != nil {
+		return fmt.Errorf("failed to create cabundle secret for git job: %w", err)
+	}
+	if err := r.createJob(ctx, gitrepo); err != nil {
+		return fmt.Errorf("error creating git job: %w", err)
+	}
+	r.Recorder.Event(gitrepo, fleetevent.Normal, "Created", "GitJob was created")
+	return nil
+}
+
+func (r *GitJobReconciler) createTargetsConfigMap(ctx context.Context, gitrepo *v1alpha1.GitRepo) error {
+	configMap, err := newTargetsConfigMap(gitrepo)
+	if err != nil {
+		return err
+	}
+	if err := controllerutil.SetControllerReference(gitrepo, configMap, r.Scheme); err != nil {
+		return err
+	}
+	data := configMap.BinaryData
+	_, err = controllerutil.CreateOrUpdate(ctx, r.Client, configMap, func() error {
+		configMap.BinaryData = data
+		return nil
+	})
+
+	return err
+}
+
+// createCABundleSecret creates a CA bundle secret, if the provided source contains data.
+// That provided source may be the CABundle field of the provided gitrepo (if the provided name matches the CA bundle
+// name expected for the gitrepo, and that CABundle field is non-empty), or Rancher-configured secrets in all other cases.
+// This returns a boolean indicating whether the secret has been successfully created (or updated, in case it already
+// existed), and an error.
+func (r *GitJobReconciler) createCABundleSecret(ctx context.Context, gitrepo *v1alpha1.GitRepo, name string) (bool, error) {
+	var caBundle []byte
+	fieldName := "cacerts"
+
+	if name == caBundleName(gitrepo) {
+		caBundle = gitrepo.Spec.CABundle
+		fieldName = bundleCAFile
+	}
+
+	if len(caBundle) == 0 {
+		cab, err := cert.GetRancherCABundle(ctx, r.Client)
+		if err != nil {
+			return false, err
+		}
+
+		if len(cab) == 0 {
+			return false, nil
+		}
+
+		caBundle = cab
+	}
+
+	secret := &corev1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      saName,
-			Namespace: gitRepo.Namespace,
+			Namespace: gitrepo.Namespace,
+			Name:      name,
+		},
+		Data: map[string][]byte{
+			fieldName: caBundle,
 		},
 	}
-	if err := controllerutil.SetControllerReference(gitRepo, sa, r.Scheme); err != nil {
+	if err := controllerutil.SetControllerReference(gitrepo, secret, r.Scheme); err != nil {
+		return false, err
+	}
+	data := secret.StringData
+	_, err := controllerutil.CreateOrUpdate(ctx, r.Client, secret, func() error {
+		secret.StringData = data // Supports update case, if the secret already exists.
+		return nil
+	})
+
+	return true, err
+}
+
+func (r *GitJobReconciler) createJob(ctx context.Context, gitRepo *v1alpha1.GitRepo) error {
+	job, err := r.newGitJob(ctx, gitRepo)
+	if err != nil {
 		return err
 	}
-	// No update needed, values are the same. So we ignore AlreadyExists.
-	if err := r.Create(ctx, sa); err != nil && !errors.IsAlreadyExists(err) {
+	if err := controllerutil.SetControllerReference(gitRepo, job, r.Scheme); err != nil {
 		return err
 	}
-	return nil
+	return r.Create(ctx, job)
 }
 
-func (r *GitJobReconciler) createOrUpdateRole(ctx context.Context, gitRepo *v1alpha1.GitRepo, saName string) error {
-	role := &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: gitRepo.Namespace, Name: saName}}
-	if err := controllerutil.SetControllerReference(gitRepo, role, r.Scheme); err != nil {
-		return err
+func (r *GitJobReconciler) newGitJob(ctx context.Context, obj *v1alpha1.GitRepo) (*batchv1.Job, error) {
+	jobSpec, err := r.newJobSpec(ctx, obj)
+	if err != nil {
+		return nil, err
+	}
+	var fleetControllerDeployment appsv1.Deployment
+	if err := r.Get(ctx, types.NamespacedName{
+		Namespace: r.SystemNamespace,
+		Name:      config.ManagerConfigName,
+	}, &fleetControllerDeployment); err != nil {
+		return nil, err
+	}
+
+	// add tolerations from the fleet-controller deployment
+	jobSpec.Template.Spec.Tolerations = append(
+		jobSpec.Template.Spec.Tolerations,
+		fleetControllerDeployment.Spec.Template.Spec.Tolerations...,
+	)
+	job := &batchv1.Job{
+		ObjectMeta: metav1.ObjectMeta{
+			Annotations: map[string]string{
+				"generation": strconv.Itoa(int(obj.Generation)),
+				"commit":     obj.Status.Commit,
+			},
+			Labels: map[string]string{
+				forceSyncGenerationLabel: fmt.Sprintf("%d", obj.Spec.ForceSyncGeneration),
+				generationLabel:          fmt.Sprintf("%d", obj.Generation),
+			},
+			Namespace: obj.Namespace,
+			Name:      jobName(obj),
+		},
+		Spec: *jobSpec,
+	}
+	// if the repo references a shard, add the same label to the job
+	// this avoids a call to Reconcile for controllers that do not match
+	// the shard-id
+	label, hasLabel := obj.GetLabels()[sharding.ShardingRefLabel]
+	if hasLabel {
+		job.Labels = labels.Merge(job.Labels, map[string]string{
+			sharding.ShardingRefLabel: label,
+		})
 	}
-	if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, role, func() error {
-		role.Rules = []rbacv1.PolicyRule{
-			{
-				Verbs:     []string{"get", "create", "update", "list", "delete"},
-				APIGroups: []string{"fleet.cattle.io"},
-				Resources: []string{"bundles", "imagescans"},
+
+	knownHostsData, err := r.KnownHosts.Get(ctx, r.Client, obj.Namespace, obj.Spec.ClientSecretName)
+	if err != nil {
+		return nil, err
+	}
+
+	initContainer, err := r.newGitCloner(ctx, obj, knownHostsData)
+	if err != nil {
+		return nil, err
+	}
+
+	job.Spec.Template.Spec.InitContainers = []corev1.Container{initContainer}
+	job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
+		corev1.Volume{
+			Name: gitClonerVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				EmptyDir: &corev1.EmptyDirVolumeSource{},
 			},
-			{
-				Verbs:     []string{"get"},
-				APIGroups: []string{"fleet.cattle.io"},
-				Resources: []string{"gitrepos"},
+		}, corev1.Volume{
+			Name: emptyDirVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				EmptyDir: &corev1.EmptyDirVolumeSource{},
 			},
-			{
-				Verbs:     []string{"get", "create", "update", "delete"},
-				APIGroups: []string{""},
-				Resources: []string{"secrets"},
+		},
+	)
+
+	// Look for a `--ca-bundle-file` arg to the git cloner. This applies to cases where the GitRepo's `Spec.CABundle` is
+	// specified, but also to cases where a CA bundle secret has been created instead, with data from Rancher
+	// secrets.
+	if slices.Contains(initContainer.Args, "--ca-bundle-file") {
+		job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
+			Name: bundleCAVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: caBundleName(obj),
+				},
 			},
+		})
+	}
+
+	if obj.Spec.ClientSecretName != "" {
+		job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
+			corev1.Volume{
+				Name: gitCredentialVolumeName,
+				VolumeSource: corev1.VolumeSource{
+					Secret: &corev1.SecretVolumeSource{
+						SecretName: obj.Spec.ClientSecretName,
+					},
+				},
+			},
+		)
+	} else {
+		// Create a volume for the default credentials secret if it exists
+		var secret corev1.Secret
+		err := r.Get(ctx, types.NamespacedName{
+			Namespace: obj.Namespace,
+			Name:      config.DefaultGitCredentialsSecretName,
+		}, &secret)
+
+		if err != nil && !apierrors.IsNotFound(err) {
+			return nil, err
+		}
+
+		if err == nil {
+			job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
+				corev1.Volume{
+					Name: gitCredentialVolumeName,
+					VolumeSource: corev1.VolumeSource{
+						Secret: &corev1.SecretVolumeSource{
+							SecretName: config.DefaultGitCredentialsSecretName,
+						},
+					},
+				},
+			)
 		}
-		return nil
-	}); err != nil {
-		return err
 	}
-	return nil
+
+	for i := range job.Spec.Template.Spec.Containers {
+		job.Spec.Template.Spec.Containers[i].VolumeMounts = append(job.Spec.Template.Spec.Containers[i].VolumeMounts, corev1.VolumeMount{
+			MountPath: "/workspace/source",
+			Name:      gitClonerVolumeName,
+		})
+
+		if knownHostsData != "" {
+			job.Spec.Template.Spec.Containers[i].Env = append(
+				job.Spec.Template.Spec.Containers[i].Env,
+				corev1.EnvVar{Name: ssh.KnownHostsEnvVar, Value: knownHostsData},
+			)
+		}
+
+		job.Spec.Template.Spec.Containers[i].Env = append(job.Spec.Template.Spec.Containers[i].Env,
+			corev1.EnvVar{
+				Name:  "COMMIT",
+				Value: obj.Status.Commit,
+			},
+		)
+		job.Spec.Template.Spec.Containers[i].Env = append(job.Spec.Template.Spec.Containers[i].Env, proxyEnvVars()...)
+	}
+
+	return job, nil
 }
 
-func (r *GitJobReconciler) createOrUpdateRoleBinding(ctx context.Context, gitRepo *v1alpha1.GitRepo, saName string) error {
-	rb := &rbacv1.RoleBinding{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      saName,
-			Namespace: gitRepo.Namespace,
+func (r *GitJobReconciler) newJobSpec(ctx context.Context, gitrepo *v1alpha1.GitRepo) (*batchv1.JobSpec, error) {
+	var CACertsFilePathOverride string
+
+	paths := gitrepo.Spec.Paths
+	if len(paths) == 0 {
+		paths = []string{"."}
+	}
+
+	drivenScanSeparator := ""
+	var err error
+	if len(gitrepo.Spec.Bundles) > 0 {
+		paths = []string{}
+		// use driven scan instead
+		// We calculate a separator because we will continue using the
+		// same call format for "fleet apply."
+		// The bundle definitions + options file (fleet.yaml)
+		// will be passed at the end, in the same way we pass the bundle
+		// directories for the classic fleet scan, but since we need to
+		// pass 2 strings, we will separate them with
+		// the calculated separator.
+		drivenScanSeparator, err = getDrivenScanSeparator(*gitrepo)
+		if err != nil {
+			return nil, err
+		}
+		for _, b := range gitrepo.Spec.Bundles {
+			path := b.Base
+			if b.Options != "" {
+				path = path + drivenScanSeparator + b.Options
+			}
+			paths = append(paths, path)
+		}
+	}
+
+	// compute configmap, needed because its name contains a hash
+	configMap, err := newTargetsConfigMap(gitrepo)
+	if err != nil {
+		return nil, err
+	}
+
+	volumes, volumeMounts := volumes(configMap.Name)
+	var certVolCreated bool
+
+	if gitrepo.Spec.HelmSecretNameForPaths != "" {
+		vols, volMnts, hasCertVol := volumesFromSecret(ctx, r.Client,
+			gitrepo.Namespace,
+			gitrepo.Spec.HelmSecretNameForPaths,
+			"helm-secret-by-path",
+			"",
+		)
+
+		certVolCreated = hasCertVol
+
+		volumes = append(volumes, vols...)
+		volumeMounts = append(volumeMounts, volMnts...)
+
+	} else if gitrepo.Spec.HelmSecretName != "" {
+		vols, volMnts, hasCertVol := volumesFromSecret(ctx, r.Client,
+			gitrepo.Namespace,
+			gitrepo.Spec.HelmSecretName,
+			"helm-secret",
+			"",
+		)
+
+		certVolCreated = hasCertVol
+
+		volumes = append(volumes, vols...)
+		volumeMounts = append(volumeMounts, volMnts...)
+	}
+
+	// In case no Helm secret volume has been created, because Helm secrets don't exist or don't contain a CA
+	// bundle, mount a volume with a Rancher CA bundle.
+	if !certVolCreated {
+		// Fall back to Rancher-configured secrets
+		// We need to copy secret data from Rancher, because Rancher secrets live in a different namespace and
+		// can therefore not be used as sources for a volume.
+		secretName := rancherCABundleName(gitrepo)
+		res, err := r.createCABundleSecret(ctx, gitrepo, secretName)
+		if err != nil {
+			return nil, err
+		}
+
+		if res {
+			CACertsDirOverride := "/etc/rancher/certs"
+
+			// Override the volume name and mount path to prevent any conflict with an existing Helm secret
+			// providing username and password.
+			vols, volMnts, _ := volumesFromSecret(ctx, r.Client,
+				gitrepo.Namespace,
+				secretName,
+				"rancher-helm-secret",
+				CACertsDirOverride,
+			)
+
+			volumes = append(volumes, vols...)
+			volumeMounts = append(volumeMounts, volMnts...)
+
+			CACertsFilePathOverride = CACertsDirOverride + "/cacerts"
+		}
+	}
+
+	if ociwrapper.ExperimentalOCIIsEnabled() && gitrepo.Spec.OCIRegistry != nil && gitrepo.Spec.OCIRegistry.AuthSecretName != "" {
+		vol, volMnt, err := ociVolumeFromSecret(ctx, r.Client,
+			gitrepo.Namespace,
+			gitrepo.Spec.OCIRegistry.AuthSecretName,
+			ociRegistryAuthVolumeName,
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		volumes = append(volumes, vol)
+		volumeMounts = append(volumeMounts, volMnt)
+	}
+
+	shardID := gitrepo.Labels[sharding.ShardingRefLabel]
+
+	nodeSelector := map[string]string{"kubernetes.io/os": "linux"}
+	if shardID != "" && len(strings.TrimSpace(r.JobNodeSelector)) > 0 {
+		var shardNodeSelector map[string]string
+		if err := json.Unmarshal([]byte(r.JobNodeSelector), &shardNodeSelector); err != nil {
+			return nil, fmt.Errorf("could not decode shard node selector: %w", err)
+		}
+
+		for k, v := range shardNodeSelector {
+			nodeSelector[k] = v
+		}
+	}
+
+	saName := names.SafeConcatName("git", gitrepo.Name)
+	logger := log.FromContext(ctx)
+	args, envs := argsAndEnvs(gitrepo, logger, CACertsFilePathOverride, r.KnownHosts, drivenScanSeparator)
+
+	return &batchv1.JobSpec{
+		BackoffLimit: &zero,
+		Template: corev1.PodTemplateSpec{
+			ObjectMeta: metav1.ObjectMeta{
+				CreationTimestamp: metav1.Time{Time: time.Unix(0, 0)},
+			},
+			Spec: corev1.PodSpec{
+				Volumes: volumes,
+				SecurityContext: &corev1.PodSecurityContext{
+					RunAsUser: &[]int64{1000}[0],
+				},
+				ServiceAccountName: saName,
+				RestartPolicy:      corev1.RestartPolicyNever,
+				Containers: []corev1.Container{
+					{
+						Name:         "fleet",
+						Image:        r.Image,
+						Command:      []string{"log.sh"},
+						Args:         append(args, paths...),
+						WorkingDir:   "/workspace/source",
+						VolumeMounts: volumeMounts,
+						Env:          envs,
+						SecurityContext: &corev1.SecurityContext{
+							AllowPrivilegeEscalation: &[]bool{false}[0],
+							ReadOnlyRootFilesystem:   &[]bool{true}[0],
+							Privileged:               &[]bool{false}[0],
+							RunAsNonRoot:             &[]bool{true}[0],
+							SeccompProfile: &corev1.SeccompProfile{
+								Type: corev1.SeccompProfileTypeRuntimeDefault,
+							},
+							Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}},
+						},
+					},
+				},
+				NodeSelector: nodeSelector,
+				Tolerations: []corev1.Toleration{
+					{
+						Key:      "cattle.io/os",
+						Operator: "Equal",
+						Value:    "linux",
+						Effect:   "NoSchedule",
+					},
+					{
+						Key:      "node.cloudprovider.kubernetes.io/uninitialized",
+						Operator: "Equal",
+						Value:    "true",
+						Effect:   "NoSchedule",
+					},
+				},
+			},
+		},
+	}, nil
+}
+
+func (r *GitJobReconciler) newGitCloner(
+	ctx context.Context,
+	obj *v1alpha1.GitRepo,
+	knownHosts string,
+) (corev1.Container, error) {
+	args := []string{"fleet", "gitcloner", obj.Spec.Repo, "/workspace"}
+	volumeMounts := []corev1.VolumeMount{
+		{
+			Name:      gitClonerVolumeName,
+			MountPath: "/workspace",
+		},
+		{
+			Name:      emptyDirVolumeName,
+			MountPath: "/tmp",
 		},
 	}
-	if err := controllerutil.SetControllerReference(gitRepo, rb, r.Scheme); err != nil {
-		return err
+
+	branch, rev := obj.Spec.Branch, obj.Spec.Revision
+	if branch != "" {
+		args = append(args, "--branch", branch)
+	} else if rev != "" {
+		args = append(args, "--revision", rev)
+	} else {
+		args = append(args, "--branch", "master")
 	}
-	if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, rb, func() error {
-		rb.Subjects = []rbacv1.Subject{{
-			Kind:      "ServiceAccount",
-			Name:      saName,
-			Namespace: gitRepo.Namespace,
-		}}
-		rb.RoleRef = rbacv1.RoleRef{
-			APIGroup: "rbac.authorization.k8s.io",
-			Kind:     "Role",
-			Name:     saName,
+
+	secretName := obj.Spec.ClientSecretName
+	if secretName == "" {
+		secretName = config.DefaultGitCredentialsSecretName
+	}
+
+	var secret corev1.Secret
+	err := r.Get(ctx, types.NamespacedName{
+		Namespace: obj.Namespace,
+		Name:      secretName,
+	}, &secret)
+
+	if err != nil && secretName == obj.Spec.ClientSecretName {
+		// Only error if an explicitly referenced secret was not found;
+		// The absence of a default secret might simply mean that no credentials are needed.
+		return corev1.Container{}, err
+	}
+
+	if err == nil {
+		switch secret.Type {
+		case corev1.SecretTypeBasicAuth:
+			volumeMounts = append(volumeMounts, corev1.VolumeMount{
+				Name:      gitCredentialVolumeName,
+				MountPath: "/gitjob/credentials",
+			})
+			args = append(args, "--username", string(secret.Data[corev1.BasicAuthUsernameKey]))
+			args = append(args, "--password-file", "/gitjob/credentials/"+corev1.BasicAuthPasswordKey)
+		case corev1.SecretTypeSSHAuth:
+			volumeMounts = append(volumeMounts, corev1.VolumeMount{
+				Name:      gitCredentialVolumeName,
+				MountPath: "/gitjob/ssh",
+			})
+			args = append(args, "--ssh-private-key-file", "/gitjob/ssh/"+corev1.SSHAuthPrivateKey)
 		}
-		return nil
-	}); err != nil {
-		return err
 	}
-	return nil
+
+	if obj.Spec.InsecureSkipTLSverify {
+		args = append(args, "--insecure-skip-tls")
+	}
+
+	var CABundleSecret corev1.Secret
+	err = r.Get(ctx, types.NamespacedName{
+		Namespace: obj.Namespace,
+		Name:      caBundleName(obj),
+	}, &CABundleSecret)
+	if client.IgnoreNotFound(err) != nil {
+		return corev1.Container{}, err
+	}
+
+	if !apierrors.IsNotFound(err) {
+		volumeMounts = append(volumeMounts, corev1.VolumeMount{
+			Name:      bundleCAVolumeName,
+			MountPath: "/gitjob/cabundle",
+		})
+		args = append(args, "--ca-bundle-file", "/gitjob/cabundle/"+bundleCAFile)
+	}
+
+	env := proxyEnvVars()
+
+	// If strict host key checks are enabled but no entries are available, another error will be shown by the known
+	// hosts getter, as that means that the Fleet deployment is incomplete.
+	// On the other hand, we do not want to feed entries to the cloner if strict host key checks are disabled, as that
+	// would lead it to unduly reject SSH connection attempts.
+	if r.KnownHosts.IsStrict() {
+		env = append(env, corev1.EnvVar{Name: ssh.KnownHostsEnvVar, Value: knownHosts})
+	}
+
+	return corev1.Container{
+		Command:      []string{"log.sh"},
+		Args:         args,
+		Image:        r.Image,
+		Name:         "gitcloner-initializer",
+		VolumeMounts: volumeMounts,
+		Env:          env,
+		SecurityContext: &corev1.SecurityContext{
+			AllowPrivilegeEscalation: &[]bool{false}[0],
+			ReadOnlyRootFilesystem:   &[]bool{true}[0],
+			Privileged:               &[]bool{false}[0],
+			Capabilities:             &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}},
+			RunAsNonRoot:             &[]bool{true}[0],
+			SeccompProfile: &corev1.SeccompProfile{
+				Type: corev1.SeccompProfileTypeRuntimeDefault,
+			},
+		},
+	}, nil
+}
+
+func argsAndEnvs(
+	gitrepo *v1alpha1.GitRepo,
+	logger logr.Logger,
+	CACertsPathOverride string,
+	knownHosts KnownHostsGetter,
+	drivenScanSeparator string,
+) ([]string, []corev1.EnvVar) {
+	args := []string{
+		"fleet",
+		"apply",
+	}
+
+	if logger.V(1).Enabled() {
+		args = append(args, "--debug", "--debug-level", "9")
+	}
+
+	bundleLabels := labels.Merge(gitrepo.Labels, map[string]string{
+		v1alpha1.RepoLabel: gitrepo.Name,
+	})
+
+	args = append(args,
+		"--targets-file=/run/config/targets.yaml",
+		"--label="+bundleLabels.String(),
+		"--namespace", gitrepo.Namespace,
+		"--service-account", gitrepo.Spec.ServiceAccount,
+		fmt.Sprintf("--sync-generation=%d", gitrepo.Spec.ForceSyncGeneration),
+		fmt.Sprintf("--paused=%v", gitrepo.Spec.Paused),
+		"--target-namespace", gitrepo.Spec.TargetNamespace,
+	)
+
+	if gitrepo.Spec.KeepResources {
+		args = append(args, "--keep-resources")
+	}
+
+	if gitrepo.Spec.DeleteNamespace {
+		args = append(args, "--delete-namespace")
+	}
+
+	if gitrepo.Spec.CorrectDrift != nil && gitrepo.Spec.CorrectDrift.Enabled {
+		args = append(args, "--correct-drift")
+		if gitrepo.Spec.CorrectDrift.Force {
+			args = append(args, "--correct-drift-force")
+		}
+		if gitrepo.Spec.CorrectDrift.KeepFailHistory {
+			args = append(args, "--correct-drift-keep-fail-history")
+		}
+	}
+
+	fleetApplyRetries, err := fleetcli.GetOnConflictRetries()
+	if err != nil {
+		logger.Error(err, "failed parsing env variable, using defaults", "env_var_name", fleetcli.FleetApplyConflictRetriesEnv)
+	}
+	env := []corev1.EnvVar{
+		{
+			Name:  "HOME",
+			Value: fleetHomeDir,
+		},
+		{
+			Name:  fleetcli.FleetApplyConflictRetriesEnv,
+			Value: strconv.Itoa(fleetApplyRetries),
+		},
+	}
+
+	if gitrepo.Spec.HelmSecretNameForPaths != "" {
+		helmArgs := []string{
+			"--helm-credentials-by-path-file",
+			"/etc/fleet/helm/secrets-path.yaml",
+		}
+
+		args = append(args, helmArgs...)
+		// for ssh go-getter
+		env = append(env, gitSSHCommandEnvVar(knownHosts.IsStrict()))
+	} else if gitrepo.Spec.HelmSecretName != "" {
+		helmArgs := []string{
+			"--password-file",
+			"/etc/fleet/helm/password",
+			"--ssh-privatekey-file",
+			"/etc/fleet/helm/ssh-privatekey",
+		}
+
+		if CACertsPathOverride == "" {
+			helmArgs = append(helmArgs,
+				"--cacerts-file",
+				"/etc/fleet/helm/cacerts",
+			)
+		}
+
+		if gitrepo.Spec.HelmRepoURLRegex != "" {
+			helmArgs = append(helmArgs, "--helm-repo-url-regex", gitrepo.Spec.HelmRepoURLRegex)
+		}
+		args = append(args, helmArgs...)
+		// for ssh go-getter
+		env = append(env, gitSSHCommandEnvVar(knownHosts.IsStrict()))
+		env = append(env,
+			corev1.EnvVar{
+				Name: "HELM_USERNAME",
+				ValueFrom: &corev1.EnvVarSource{
+					SecretKeyRef: &corev1.SecretKeySelector{
+						Optional: &[]bool{true}[0],
+						Key:      "username",
+						LocalObjectReference: corev1.LocalObjectReference{
+							Name: gitrepo.Spec.HelmSecretName,
+						},
+					},
+				},
+			})
+	}
+
+	if CACertsPathOverride != "" {
+		helmArgs := []string{
+			"--cacerts-file",
+			CACertsPathOverride,
+		}
+		if gitrepo.Spec.HelmRepoURLRegex != "" {
+			helmArgs = append(helmArgs, "--helm-repo-url-regex", gitrepo.Spec.HelmRepoURLRegex)
+		}
+		args = append(args, helmArgs...)
+		env = append(env, gitSSHCommandEnvVar(knownHosts.IsStrict()))
+	}
+
+	if ociwrapper.ExperimentalOCIIsEnabled() && gitrepo.Spec.OCIRegistry != nil && gitrepo.Spec.OCIRegistry.Reference != "" {
+		args = append(args, "--oci-reference", gitrepo.Spec.OCIRegistry.Reference)
+		if gitrepo.Spec.OCIRegistry.AuthSecretName != "" {
+			args = append(args, "--oci-password-file", "/etc/fleet/oci/password")
+			env = append(env,
+				corev1.EnvVar{
+					Name: "OCI_USERNAME",
+					ValueFrom: &corev1.EnvVarSource{
+						SecretKeyRef: &corev1.SecretKeySelector{
+							Optional: &[]bool{true}[0],
+							Key:      "username",
+							LocalObjectReference: corev1.LocalObjectReference{
+								Name: gitrepo.Spec.OCIRegistry.AuthSecretName,
+							},
+						},
+					},
+				})
+		}
+		if gitrepo.Spec.OCIRegistry.BasicHTTP {
+			args = append(args, "--oci-basic-http")
+		}
+		if gitrepo.Spec.OCIRegistry.InsecureSkipTLS {
+			args = append(args, "--oci-insecure")
+		}
+	}
+	if len(gitrepo.Spec.Bundles) > 0 {
+		args = append(args, "--driven-scan")
+		if drivenScanSeparator != "" {
+			args = append(args, "--driven-scan-sep", drivenScanSeparator)
+		}
+	}
+
+	return append(args, "--", gitrepo.Name), env
+}
+
+// volumes builds sets of volumes and their volume mounts for default folders and the targets config map.
+func volumes(targetsConfigName string) ([]corev1.Volume, []corev1.VolumeMount) {
+	const (
+		emptyDirTmpVolumeName  = "fleet-tmp-empty-dir"
+		emptyDirHomeVolumeName = "fleet-home-empty-dir"
+		configVolumeName       = "config"
+	)
+
+	volumes := []corev1.Volume{
+		{
+			Name: configVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: targetsConfigName,
+					},
+				},
+			},
+		},
+		{
+			Name: emptyDirTmpVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				EmptyDir: &corev1.EmptyDirVolumeSource{},
+			},
+		},
+		{
+			Name: emptyDirHomeVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				EmptyDir: &corev1.EmptyDirVolumeSource{},
+			},
+		},
+	}
+
+	volumeMounts := []corev1.VolumeMount{
+		{
+			Name:      configVolumeName,
+			MountPath: "/run/config",
+		},
+		{
+			Name:      emptyDirTmpVolumeName,
+			MountPath: "/tmp",
+		},
+		{
+			Name:      emptyDirHomeVolumeName,
+			MountPath: fleetHomeDir,
+		},
+	}
+
+	return volumes, volumeMounts
+}
+
+// ociVolumeFromSecret generates a volume and volume mount from a basic-auth secret.
+func ociVolumeFromSecret(
+	ctx context.Context,
+	c client.Client,
+	namespace, secretName, volumeName string,
+) (corev1.Volume, corev1.VolumeMount, error) {
+	var secret corev1.Secret
+	if err := c.Get(ctx, types.NamespacedName{
+		Namespace: namespace,
+		Name:      secretName,
+	}, &secret); err != nil {
+		return corev1.Volume{}, corev1.VolumeMount{}, err
+	}
+	volume := corev1.Volume{
+		Name: volumeName,
+		VolumeSource: corev1.VolumeSource{
+			Secret: &corev1.SecretVolumeSource{
+				SecretName: secretName,
+			},
+		},
+	}
+	volumeMount := corev1.VolumeMount{
+		Name:      volumeName,
+		MountPath: "/etc/fleet/oci",
+	}
+	return volume, volumeMount, nil
+}
+
+// volumesFromSecret generates volumes and volume mounts from a Helm secret, assuming that that secret exists.
+// If the secret has a cacerts key, it will be mounted into /etc/ssl/certs, too.
+func volumesFromSecret(
+	ctx context.Context,
+	c client.Client,
+	namespace string,
+	secretName, volumeName, mountPath string,
+) ([]corev1.Volume, []corev1.VolumeMount, bool) {
+	if mountPath == "" {
+		mountPath = "/etc/fleet/helm"
+	}
+
+	volumes := []corev1.Volume{
+		{
+			Name: volumeName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: secretName,
+				},
+			},
+		},
+	}
+	volumeMounts := []corev1.VolumeMount{
+		{
+			Name:      volumeName,
+			MountPath: mountPath,
+		},
+	}
+
+	// Mount a CA certificate, if specified in the secret. This is necessary to support Helm registries with
+	// self-signed certificates.
+	secret := &corev1.Secret{}
+	var certVolCreated bool
+	_ = c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: secretName}, secret)
+	if _, ok := secret.Data["cacerts"]; ok {
+		volumes = append(volumes, corev1.Volume{
+			Name: fmt.Sprintf("%s-cert", volumeName),
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: secretName,
+					Items: []corev1.KeyToPath{
+						{
+							Key:  "cacerts",
+							Path: "cacert.crt",
+						},
+					},
+				},
+			},
+		})
+		volumeMounts = append(volumeMounts, corev1.VolumeMount{
+			Name:      fmt.Sprintf("%s-cert", volumeName),
+			MountPath: "/etc/ssl/certs",
+		})
+
+		certVolCreated = true
+	}
+
+	return volumes, volumeMounts, certVolCreated
+}
+
+func proxyEnvVars() []corev1.EnvVar {
+	var envVars []corev1.EnvVar
+	for _, envVar := range []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"} {
+		if val, ok := os.LookupEnv(envVar); ok {
+			envVars = append(envVars, corev1.EnvVar{Name: envVar, Value: val})
+		}
+	}
+
+	return envVars
+}
+
+func gitSSHCommandEnvVar(strictChecks bool) corev1.EnvVar {
+	strictVal := "no"
+
+	if strictChecks {
+		strictVal = "yes"
+	}
+
+	return corev1.EnvVar{
+		Name:  "GIT_SSH_COMMAND",
+		Value: fmt.Sprintf("ssh -o stricthostkeychecking=%s", strictVal),
+	}
+}
+
+// getDrivenScanSeparator returns a separator that is valid for all the Bundle
+// definitions in the given GitRepo.
+// Since we cannot disregard the possibility that a user might have an
+// unavoidable need to use the character ":" (or another character typically not
+// used in directory or file paths), we need to find possible alternatives.
+// The function will search for simple characters from those in
+// bundleOptionsSeparatorChars, and if none of them can be used, it will return an error.
+func getDrivenScanSeparator(gitrepo v1alpha1.GitRepo) (string, error) {
+	for _, sep := range bundleOptionsSeparatorChars {
+		if !separatorInBundleDefinitions(gitrepo, sep) {
+			// We can safely use this separator
+			return string(sep), nil
+		}
+	}
+
+	return "", fmt.Errorf("bundle base and/or options paths contain all possible characters from %q, please update those paths to remedy this", bundleOptionsSeparatorChars)
+}
+
+func separatorInBundleDefinitions(gitrepo v1alpha1.GitRepo, sep rune) bool {
+	for _, b := range gitrepo.Spec.Bundles {
+		if strings.ContainsRune(b.Options, sep) {
+			return true
+		}
+
+		if strings.ContainsRune(b.Base, sep) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func jobName(obj *v1alpha1.GitRepo) string {
+	return names.SafeConcatName(obj.Name, names.Hex(obj.Spec.Repo+obj.Status.Commit, 5))
+}
+
+func caBundleName(obj *v1alpha1.GitRepo) string {
+	return fmt.Sprintf("%s-cabundle", obj.Name)
+}
+
+func rancherCABundleName(obj *v1alpha1.GitRepo) string {
+	return fmt.Sprintf("%s-rancher-cabundle", obj.Name)
 }
diff --git a/internal/cmd/controller/gitops/reconciler/gitjob_controller.go b/internal/cmd/controller/gitops/reconciler/gitjob_controller.go
index f1bbfb1f..7a65331f 100644
--- a/internal/cmd/controller/gitops/reconciler/gitjob_controller.go
+++ b/internal/cmd/controller/gitops/reconciler/gitjob_controller.go
@@ -2,31 +2,20 @@ package reconciler
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"math/rand/v2"
-	"os"
-	"reflect"
-	"slices"
 	"sort"
-	"strconv"
 	"strings"
 	"time"
 
 	"github.com/go-logr/logr"
 	"github.com/reugn/go-quartz/quartz"
 
-	fleetcli "github.com/rancher/fleet/internal/cmd/cli"
 	fleetutil "github.com/rancher/fleet/internal/cmd/controller/errorutil"
 	"github.com/rancher/fleet/internal/cmd/controller/finalize"
 	"github.com/rancher/fleet/internal/cmd/controller/imagescan"
-	"github.com/rancher/fleet/internal/config"
 	"github.com/rancher/fleet/internal/metrics"
-	"github.com/rancher/fleet/internal/names"
-	"github.com/rancher/fleet/internal/ociwrapper"
-	ssh "github.com/rancher/fleet/internal/ssh"
 	v1alpha1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/cert"
 	fleetevent "github.com/rancher/fleet/pkg/event"
 	"github.com/rancher/fleet/pkg/sharding"
 
@@ -34,7 +23,6 @@ import (
 	"github.com/rancher/wrangler/v3/pkg/genericcondition"
 	"github.com/rancher/wrangler/v3/pkg/kstatus"
 
-	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/equality"
@@ -53,28 +41,16 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
-	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
 const (
-	bundleCAVolumeName        = "additional-ca"
-	bundleCAFile              = "additional-ca.crt"
-	gitCredentialVolumeName   = "git-credential" // #nosec G101 this is not a credential
-	ociRegistryAuthVolumeName = "oci-auth"
-	gitClonerVolumeName       = "git-cloner"
-	emptyDirVolumeName        = "git-cloner-empty-dir"
-
-	fleetHomeDir = "/fleet-home"
-
 	defaultPollingSyncInterval = 15 * time.Second
 	gitPollingCondition        = "GitPolling"
 	generationLabel            = "fleet.cattle.io/gitrepo-generation"
 	forceSyncGenerationLabel   = "fleet.cattle.io/force-sync-generation"
-
-	bundleOptionsSeparatorChars = ":,|?<>"
 )
 
 var zero = int32(0)
@@ -222,16 +198,6 @@ func (r *GitJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
 	return r.result(gitrepo), nil
 }
 
-// addJitter to the requeue time to avoid thundering herd
-// generate a random number between -10% and +10% of the duration
-func addJitter(d time.Duration) time.Duration {
-	if d <= 0 {
-		return d
-	}
-
-	return d + time.Duration(rand.Int64N(int64(d)/10)) // nolint:gosec // gosec G404 false positive, not used for crypto
-}
-
 // manageGitJob is responsible for creating, updating and deleting the GitJob and setting the GitRepo's status accordingly
 func (r *GitJobReconciler) manageGitJob(ctx context.Context, logger logr.Logger, gitrepo *v1alpha1.GitRepo, oldCommit string, repoPolled bool) (reconcile.Result, error) {
 	name := types.NamespacedName{Namespace: gitrepo.Namespace, Name: gitrepo.Name}
@@ -377,89 +343,6 @@ func (r *GitJobReconciler) addGitRepoFinalizer(ctx context.Context, nsName types
 	return nil
 }
 
-func (r *GitJobReconciler) createJobRBAC(ctx context.Context, gitRepo *v1alpha1.GitRepo) error {
-	saName := names.SafeConcatName("git", gitRepo.Name)
-
-	if err := r.createServiceAccount(ctx, gitRepo, saName); err != nil {
-		return err
-	}
-
-	if err := r.createOrUpdateRole(ctx, gitRepo, saName); err != nil {
-		return err
-	}
-
-	if err := r.createOrUpdateRoleBinding(ctx, gitRepo, saName); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (r *GitJobReconciler) createTargetsConfigMap(ctx context.Context, gitrepo *v1alpha1.GitRepo) error {
-	configMap, err := newTargetsConfigMap(gitrepo)
-	if err != nil {
-		return err
-	}
-	if err := controllerutil.SetControllerReference(gitrepo, configMap, r.Scheme); err != nil {
-		return err
-	}
-	data := configMap.BinaryData
-	_, err = controllerutil.CreateOrUpdate(ctx, r.Client, configMap, func() error {
-		configMap.BinaryData = data
-		return nil
-	})
-
-	return err
-}
-
-// createCABundleSecret creates a CA bundle secret, if the provided source contains data.
-// That provided source may be the CABundle field of the provided gitrepo (if the provided name matches the CA bundle
-// name expected for the gitrepo, and that CABundle field is non-empty), or Rancher-configured secrets in all other cases.
-// This returns a boolean indicating whether the secret has been successfully created (or updated, in case it already
-// existed), and an error.
-func (r *GitJobReconciler) createCABundleSecret(ctx context.Context, gitrepo *v1alpha1.GitRepo, name string) (bool, error) {
-	var caBundle []byte
-	fieldName := "cacerts"
-
-	if name == caBundleName(gitrepo) {
-		caBundle = gitrepo.Spec.CABundle
-		fieldName = bundleCAFile
-	}
-
-	if len(caBundle) == 0 {
-		cab, err := cert.GetRancherCABundle(ctx, r.Client)
-		if err != nil {
-			return false, err
-		}
-
-		if len(cab) == 0 {
-			return false, nil
-		}
-
-		caBundle = cab
-	}
-
-	secret := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: gitrepo.Namespace,
-			Name:      name,
-		},
-		Data: map[string][]byte{
-			fieldName: caBundle,
-		},
-	}
-	if err := controllerutil.SetControllerReference(gitrepo, secret, r.Scheme); err != nil {
-		return false, err
-	}
-	data := secret.StringData
-	_, err := controllerutil.CreateOrUpdate(ctx, r.Client, secret, func() error {
-		secret.StringData = data // Supports update case, if the secret already exists.
-		return nil
-	})
-
-	return true, err
-}
-
 func (r *GitJobReconciler) validateExternalSecretExist(ctx context.Context, gitrepo *v1alpha1.GitRepo) error {
 	if gitrepo.Spec.HelmSecretNameForPaths != "" {
 		if err := r.Get(ctx, types.NamespacedName{Namespace: gitrepo.Namespace, Name: gitrepo.Spec.HelmSecretNameForPaths}, &corev1.Secret{}); err != nil {
@@ -473,35 +356,6 @@ func (r *GitJobReconciler) validateExternalSecretExist(ctx context.Context, gitr
 	return nil
 }
 
-func (r *GitJobReconciler) createJob(ctx context.Context, gitRepo *v1alpha1.GitRepo) error {
-	job, err := r.newGitJob(ctx, gitRepo)
-	if err != nil {
-		return err
-	}
-	if err := controllerutil.SetControllerReference(gitRepo, job, r.Scheme); err != nil {
-		return err
-	}
-	return r.Create(ctx, job)
-}
-
-func (r *GitJobReconciler) createJobAndResources(ctx context.Context, gitrepo *v1alpha1.GitRepo, logger logr.Logger) error {
-	logger.V(1).Info("Creating Git job resources")
-	if err := r.createJobRBAC(ctx, gitrepo); err != nil {
-		return fmt.Errorf("failed to create RBAC resources for git job: %w", err)
-	}
-	if err := r.createTargetsConfigMap(ctx, gitrepo); err != nil {
-		return fmt.Errorf("failed to create targets config map for git job: %w", err)
-	}
-	if _, err := r.createCABundleSecret(ctx, gitrepo, caBundleName(gitrepo)); err != nil {
-		return fmt.Errorf("failed to create cabundle secret for git job: %w", err)
-	}
-	if err := r.createJob(ctx, gitrepo); err != nil {
-		return fmt.Errorf("error creating git job: %w", err)
-	}
-	r.Recorder.Event(gitrepo, fleetevent.Normal, "Created", "GitJob was created")
-	return nil
-}
-
 func (r *GitJobReconciler) deleteJobIfNeeded(ctx context.Context, gitRepo *v1alpha1.GitRepo, job *batchv1.Job) (error, bool) {
 	logger := log.FromContext(ctx)
 
@@ -550,775 +404,6 @@ func (r *GitJobReconciler) deleteJobIfNeeded(ctx context.Context, gitRepo *v1alp
 	return nil, false
 }
 
-func generationChanged(r *v1alpha1.GitRepo) bool {
-	// checks if generation changed.
-	// it ignores the case when Status.ObservedGeneration=0 because that's
-	// the initial value of a just created GitRepo and the initial value
-	// for Generation in k8s is 1.
-	// If we don't ignore we would be deleting the gitjob that was just created
-	// until later we reconcile ObservedGeneration with Generation
-	return (r.Generation != r.Status.ObservedGeneration) && r.Status.ObservedGeneration > 0
-}
-
-func jobName(obj *v1alpha1.GitRepo) string {
-	return names.SafeConcatName(obj.Name, names.Hex(obj.Spec.Repo+obj.Status.Commit, 5))
-}
-
-func caBundleName(obj *v1alpha1.GitRepo) string {
-	return fmt.Sprintf("%s-cabundle", obj.Name)
-}
-
-func rancherCABundleName(obj *v1alpha1.GitRepo) string {
-	return fmt.Sprintf("%s-rancher-cabundle", obj.Name)
-}
-
-func (r *GitJobReconciler) newGitJob(ctx context.Context, obj *v1alpha1.GitRepo) (*batchv1.Job, error) {
-	jobSpec, err := r.newJobSpec(ctx, obj)
-	if err != nil {
-		return nil, err
-	}
-	var fleetControllerDeployment appsv1.Deployment
-	if err := r.Get(ctx, types.NamespacedName{
-		Namespace: r.SystemNamespace,
-		Name:      config.ManagerConfigName,
-	}, &fleetControllerDeployment); err != nil {
-		return nil, err
-	}
-
-	// add tolerations from the fleet-controller deployment
-	jobSpec.Template.Spec.Tolerations = append(
-		jobSpec.Template.Spec.Tolerations,
-		fleetControllerDeployment.Spec.Template.Spec.Tolerations...,
-	)
-	job := &batchv1.Job{
-		ObjectMeta: metav1.ObjectMeta{
-			Annotations: map[string]string{
-				"generation": strconv.Itoa(int(obj.Generation)),
-				"commit":     obj.Status.Commit,
-			},
-			Labels: map[string]string{
-				forceSyncGenerationLabel: fmt.Sprintf("%d", obj.Spec.ForceSyncGeneration),
-				generationLabel:          fmt.Sprintf("%d", obj.Generation),
-			},
-			Namespace: obj.Namespace,
-			Name:      jobName(obj),
-		},
-		Spec: *jobSpec,
-	}
-	// if the repo references a shard, add the same label to the job
-	// this avoids a call to Reconcile for controllers that do not match
-	// the shard-id
-	label, hasLabel := obj.GetLabels()[sharding.ShardingRefLabel]
-	if hasLabel {
-		job.Labels = labels.Merge(job.Labels, map[string]string{
-			sharding.ShardingRefLabel: label,
-		})
-	}
-
-	knownHostsData, err := r.KnownHosts.Get(ctx, r.Client, obj.Namespace, obj.Spec.ClientSecretName)
-	if err != nil {
-		return nil, err
-	}
-
-	initContainer, err := r.newGitCloner(ctx, obj, knownHostsData)
-	if err != nil {
-		return nil, err
-	}
-
-	job.Spec.Template.Spec.InitContainers = []corev1.Container{initContainer}
-	job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
-		corev1.Volume{
-			Name: gitClonerVolumeName,
-			VolumeSource: corev1.VolumeSource{
-				EmptyDir: &corev1.EmptyDirVolumeSource{},
-			},
-		}, corev1.Volume{
-			Name: emptyDirVolumeName,
-			VolumeSource: corev1.VolumeSource{
-				EmptyDir: &corev1.EmptyDirVolumeSource{},
-			},
-		},
-	)
-
-	// Look for a `--ca-bundle-file` arg to the git cloner. This applies to cases where the GitRepo's `Spec.CABundle` is
-	// specified, but also to cases where a CA bundle secret has been created instead, with data from Rancher
-	// secrets.
-	if slices.Contains(initContainer.Args, "--ca-bundle-file") {
-		job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
-			Name: bundleCAVolumeName,
-			VolumeSource: corev1.VolumeSource{
-				Secret: &corev1.SecretVolumeSource{
-					SecretName: caBundleName(obj),
-				},
-			},
-		})
-	}
-
-	if obj.Spec.ClientSecretName != "" {
-		job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
-			corev1.Volume{
-				Name: gitCredentialVolumeName,
-				VolumeSource: corev1.VolumeSource{
-					Secret: &corev1.SecretVolumeSource{
-						SecretName: obj.Spec.ClientSecretName,
-					},
-				},
-			},
-		)
-	} else {
-		// Create a volume for the default credentials secret if it exists
-		var secret corev1.Secret
-		err := r.Get(ctx, types.NamespacedName{
-			Namespace: obj.Namespace,
-			Name:      config.DefaultGitCredentialsSecretName,
-		}, &secret)
-
-		if err != nil && !apierrors.IsNotFound(err) {
-			return nil, err
-		}
-
-		if err == nil {
-			job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
-				corev1.Volume{
-					Name: gitCredentialVolumeName,
-					VolumeSource: corev1.VolumeSource{
-						Secret: &corev1.SecretVolumeSource{
-							SecretName: config.DefaultGitCredentialsSecretName,
-						},
-					},
-				},
-			)
-		}
-	}
-
-	for i := range job.Spec.Template.Spec.Containers {
-		job.Spec.Template.Spec.Containers[i].VolumeMounts = append(job.Spec.Template.Spec.Containers[i].VolumeMounts, corev1.VolumeMount{
-			MountPath: "/workspace/source",
-			Name:      gitClonerVolumeName,
-		})
-
-		if knownHostsData != "" {
-			job.Spec.Template.Spec.Containers[i].Env = append(
-				job.Spec.Template.Spec.Containers[i].Env,
-				corev1.EnvVar{Name: ssh.KnownHostsEnvVar, Value: knownHostsData},
-			)
-		}
-
-		job.Spec.Template.Spec.Containers[i].Env = append(job.Spec.Template.Spec.Containers[i].Env,
-			corev1.EnvVar{
-				Name:  "COMMIT",
-				Value: obj.Status.Commit,
-			},
-		)
-		job.Spec.Template.Spec.Containers[i].Env = append(job.Spec.Template.Spec.Containers[i].Env, proxyEnvVars()...)
-	}
-
-	return job, nil
-}
-
-func (r *GitJobReconciler) newJobSpec(ctx context.Context, gitrepo *v1alpha1.GitRepo) (*batchv1.JobSpec, error) {
-	var CACertsFilePathOverride string
-
-	paths := gitrepo.Spec.Paths
-	if len(paths) == 0 {
-		paths = []string{"."}
-	}
-
-	drivenScanSeparator := ""
-	var err error
-	if len(gitrepo.Spec.Bundles) > 0 {
-		paths = []string{}
-		// use driven scan instead
-		// We calculate a separator because we will continue using the
-		// same call format for "fleet apply."
-		// The bundle definitions + options file (fleet.yaml)
-		// will be passed at the end, in the same way we pass the bundle
-		// directories for the classic fleet scan, but since we need to
-		// pass 2 strings, we will separate them with
-		// the calculated separator.
-		drivenScanSeparator, err = getDrivenScanSeparator(*gitrepo)
-		if err != nil {
-			return nil, err
-		}
-		for _, b := range gitrepo.Spec.Bundles {
-			path := b.Base
-			if b.Options != "" {
-				path = path + drivenScanSeparator + b.Options
-			}
-			paths = append(paths, path)
-		}
-	}
-
-	// compute configmap, needed because its name contains a hash
-	configMap, err := newTargetsConfigMap(gitrepo)
-	if err != nil {
-		return nil, err
-	}
-
-	volumes, volumeMounts := volumes(configMap.Name)
-	var certVolCreated bool
-
-	if gitrepo.Spec.HelmSecretNameForPaths != "" {
-		vols, volMnts, hasCertVol := volumesFromSecret(ctx, r.Client,
-			gitrepo.Namespace,
-			gitrepo.Spec.HelmSecretNameForPaths,
-			"helm-secret-by-path",
-			"",
-		)
-
-		certVolCreated = hasCertVol
-
-		volumes = append(volumes, vols...)
-		volumeMounts = append(volumeMounts, volMnts...)
-
-	} else if gitrepo.Spec.HelmSecretName != "" {
-		vols, volMnts, hasCertVol := volumesFromSecret(ctx, r.Client,
-			gitrepo.Namespace,
-			gitrepo.Spec.HelmSecretName,
-			"helm-secret",
-			"",
-		)
-
-		certVolCreated = hasCertVol
-
-		volumes = append(volumes, vols...)
-		volumeMounts = append(volumeMounts, volMnts...)
-	}
-
-	// In case no Helm secret volume has been created, because Helm secrets don't exist or don't contain a CA
-	// bundle, mount a volume with a Rancher CA bundle.
-	if !certVolCreated {
-		// Fall back to Rancher-configured secrets
-		// We need to copy secret data from Rancher, because Rancher secrets live in a different namespace and
-		// can therefore not be used as sources for a volume.
-		secretName := rancherCABundleName(gitrepo)
-		res, err := r.createCABundleSecret(ctx, gitrepo, secretName)
-		if err != nil {
-			return nil, err
-		}
-
-		if res {
-			CACertsDirOverride := "/etc/rancher/certs"
-
-			// Override the volume name and mount path to prevent any conflict with an existing Helm secret
-			// providing username and password.
-			vols, volMnts, _ := volumesFromSecret(ctx, r.Client,
-				gitrepo.Namespace,
-				secretName,
-				"rancher-helm-secret",
-				CACertsDirOverride,
-			)
-
-			volumes = append(volumes, vols...)
-			volumeMounts = append(volumeMounts, volMnts...)
-
-			CACertsFilePathOverride = CACertsDirOverride + "/cacerts"
-		}
-	}
-
-	if ociwrapper.ExperimentalOCIIsEnabled() && gitrepo.Spec.OCIRegistry != nil && gitrepo.Spec.OCIRegistry.AuthSecretName != "" {
-		vol, volMnt, err := ociVolumeFromSecret(ctx, r.Client,
-			gitrepo.Namespace,
-			gitrepo.Spec.OCIRegistry.AuthSecretName,
-			ociRegistryAuthVolumeName,
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		volumes = append(volumes, vol)
-		volumeMounts = append(volumeMounts, volMnt)
-	}
-
-	shardID := gitrepo.Labels[sharding.ShardingRefLabel]
-
-	nodeSelector := map[string]string{"kubernetes.io/os": "linux"}
-	if shardID != "" && len(strings.TrimSpace(r.JobNodeSelector)) > 0 {
-		var shardNodeSelector map[string]string
-		if err := json.Unmarshal([]byte(r.JobNodeSelector), &shardNodeSelector); err != nil {
-			return nil, fmt.Errorf("could not decode shard node selector: %w", err)
-		}
-
-		for k, v := range shardNodeSelector {
-			nodeSelector[k] = v
-		}
-	}
-
-	saName := names.SafeConcatName("git", gitrepo.Name)
-	logger := log.FromContext(ctx)
-	args, envs := argsAndEnvs(gitrepo, logger, CACertsFilePathOverride, r.KnownHosts, drivenScanSeparator)
-
-	return &batchv1.JobSpec{
-		BackoffLimit: &zero,
-		Template: corev1.PodTemplateSpec{
-			ObjectMeta: metav1.ObjectMeta{
-				CreationTimestamp: metav1.Time{Time: time.Unix(0, 0)},
-			},
-			Spec: corev1.PodSpec{
-				Volumes: volumes,
-				SecurityContext: &corev1.PodSecurityContext{
-					RunAsUser: &[]int64{1000}[0],
-				},
-				ServiceAccountName: saName,
-				RestartPolicy:      corev1.RestartPolicyNever,
-				Containers: []corev1.Container{
-					{
-						Name:         "fleet",
-						Image:        r.Image,
-						Command:      []string{"log.sh"},
-						Args:         append(args, paths...),
-						WorkingDir:   "/workspace/source",
-						VolumeMounts: volumeMounts,
-						Env:          envs,
-						SecurityContext: &corev1.SecurityContext{
-							AllowPrivilegeEscalation: &[]bool{false}[0],
-							ReadOnlyRootFilesystem:   &[]bool{true}[0],
-							Privileged:               &[]bool{false}[0],
-							RunAsNonRoot:             &[]bool{true}[0],
-							SeccompProfile: &corev1.SeccompProfile{
-								Type: corev1.SeccompProfileTypeRuntimeDefault,
-							},
-							Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}},
-						},
-					},
-				},
-				NodeSelector: nodeSelector,
-				Tolerations: []corev1.Toleration{
-					{
-						Key:      "cattle.io/os",
-						Operator: "Equal",
-						Value:    "linux",
-						Effect:   "NoSchedule",
-					},
-					{
-						Key:      "node.cloudprovider.kubernetes.io/uninitialized",
-						Operator: "Equal",
-						Value:    "true",
-						Effect:   "NoSchedule",
-					},
-				},
-			},
-		},
-	}, nil
-}
-
-func argsAndEnvs(
-	gitrepo *v1alpha1.GitRepo,
-	logger logr.Logger,
-	CACertsPathOverride string,
-	knownHosts KnownHostsGetter,
-	drivenScanSeparator string,
-) ([]string, []corev1.EnvVar) {
-	args := []string{
-		"fleet",
-		"apply",
-	}
-
-	if logger.V(1).Enabled() {
-		args = append(args, "--debug", "--debug-level", "9")
-	}
-
-	bundleLabels := labels.Merge(gitrepo.Labels, map[string]string{
-		v1alpha1.RepoLabel: gitrepo.Name,
-	})
-
-	args = append(args,
-		"--targets-file=/run/config/targets.yaml",
-		"--label="+bundleLabels.String(),
-		"--namespace", gitrepo.Namespace,
-		"--service-account", gitrepo.Spec.ServiceAccount,
-		fmt.Sprintf("--sync-generation=%d", gitrepo.Spec.ForceSyncGeneration),
-		fmt.Sprintf("--paused=%v", gitrepo.Spec.Paused),
-		"--target-namespace", gitrepo.Spec.TargetNamespace,
-	)
-
-	if gitrepo.Spec.KeepResources {
-		args = append(args, "--keep-resources")
-	}
-
-	if gitrepo.Spec.DeleteNamespace {
-		args = append(args, "--delete-namespace")
-	}
-
-	if gitrepo.Spec.CorrectDrift != nil && gitrepo.Spec.CorrectDrift.Enabled {
-		args = append(args, "--correct-drift")
-		if gitrepo.Spec.CorrectDrift.Force {
-			args = append(args, "--correct-drift-force")
-		}
-		if gitrepo.Spec.CorrectDrift.KeepFailHistory {
-			args = append(args, "--correct-drift-keep-fail-history")
-		}
-	}
-
-	fleetApplyRetries, err := fleetcli.GetOnConflictRetries()
-	if err != nil {
-		logger.Error(err, "failed parsing env variable, using defaults", "env_var_name", fleetcli.FleetApplyConflictRetriesEnv)
-	}
-	env := []corev1.EnvVar{
-		{
-			Name:  "HOME",
-			Value: fleetHomeDir,
-		},
-		{
-			Name:  fleetcli.FleetApplyConflictRetriesEnv,
-			Value: strconv.Itoa(fleetApplyRetries),
-		},
-	}
-
-	if gitrepo.Spec.HelmSecretNameForPaths != "" {
-		helmArgs := []string{
-			"--helm-credentials-by-path-file",
-			"/etc/fleet/helm/secrets-path.yaml",
-		}
-
-		args = append(args, helmArgs...)
-		// for ssh go-getter
-		env = append(env, gitSSHCommandEnvVar(knownHosts.IsStrict()))
-	} else if gitrepo.Spec.HelmSecretName != "" {
-		helmArgs := []string{
-			"--password-file",
-			"/etc/fleet/helm/password",
-			"--ssh-privatekey-file",
-			"/etc/fleet/helm/ssh-privatekey",
-		}
-
-		if CACertsPathOverride == "" {
-			helmArgs = append(helmArgs,
-				"--cacerts-file",
-				"/etc/fleet/helm/cacerts",
-			)
-		}
-
-		if gitrepo.Spec.HelmRepoURLRegex != "" {
-			helmArgs = append(helmArgs, "--helm-repo-url-regex", gitrepo.Spec.HelmRepoURLRegex)
-		}
-		args = append(args, helmArgs...)
-		// for ssh go-getter
-		env = append(env, gitSSHCommandEnvVar(knownHosts.IsStrict()))
-		env = append(env,
-			corev1.EnvVar{
-				Name: "HELM_USERNAME",
-				ValueFrom: &corev1.EnvVarSource{
-					SecretKeyRef: &corev1.SecretKeySelector{
-						Optional: &[]bool{true}[0],
-						Key:      "username",
-						LocalObjectReference: corev1.LocalObjectReference{
-							Name: gitrepo.Spec.HelmSecretName,
-						},
-					},
-				},
-			})
-	}
-
-	if CACertsPathOverride != "" {
-		helmArgs := []string{
-			"--cacerts-file",
-			CACertsPathOverride,
-		}
-		if gitrepo.Spec.HelmRepoURLRegex != "" {
-			helmArgs = append(helmArgs, "--helm-repo-url-regex", gitrepo.Spec.HelmRepoURLRegex)
-		}
-		args = append(args, helmArgs...)
-		env = append(env, gitSSHCommandEnvVar(knownHosts.IsStrict()))
-	}
-
-	if ociwrapper.ExperimentalOCIIsEnabled() && gitrepo.Spec.OCIRegistry != nil && gitrepo.Spec.OCIRegistry.Reference != "" {
-		args = append(args, "--oci-reference", gitrepo.Spec.OCIRegistry.Reference)
-		if gitrepo.Spec.OCIRegistry.AuthSecretName != "" {
-			args = append(args, "--oci-password-file", "/etc/fleet/oci/password")
-			env = append(env,
-				corev1.EnvVar{
-					Name: "OCI_USERNAME",
-					ValueFrom: &corev1.EnvVarSource{
-						SecretKeyRef: &corev1.SecretKeySelector{
-							Optional: &[]bool{true}[0],
-							Key:      "username",
-							LocalObjectReference: corev1.LocalObjectReference{
-								Name: gitrepo.Spec.OCIRegistry.AuthSecretName,
-							},
-						},
-					},
-				})
-		}
-		if gitrepo.Spec.OCIRegistry.BasicHTTP {
-			args = append(args, "--oci-basic-http")
-		}
-		if gitrepo.Spec.OCIRegistry.InsecureSkipTLS {
-			args = append(args, "--oci-insecure")
-		}
-	}
-	if len(gitrepo.Spec.Bundles) > 0 {
-		args = append(args, "--driven-scan")
-		if drivenScanSeparator != "" {
-			args = append(args, "--driven-scan-sep", drivenScanSeparator)
-		}
-	}
-
-	return append(args, "--", gitrepo.Name), env
-}
-
-// volumes builds sets of volumes and their volume mounts for default folders and the targets config map.
-func volumes(targetsConfigName string) ([]corev1.Volume, []corev1.VolumeMount) {
-	const (
-		emptyDirTmpVolumeName  = "fleet-tmp-empty-dir"
-		emptyDirHomeVolumeName = "fleet-home-empty-dir"
-		configVolumeName       = "config"
-	)
-
-	volumes := []corev1.Volume{
-		{
-			Name: configVolumeName,
-			VolumeSource: corev1.VolumeSource{
-				ConfigMap: &corev1.ConfigMapVolumeSource{
-					LocalObjectReference: corev1.LocalObjectReference{
-						Name: targetsConfigName,
-					},
-				},
-			},
-		},
-		{
-			Name: emptyDirTmpVolumeName,
-			VolumeSource: corev1.VolumeSource{
-				EmptyDir: &corev1.EmptyDirVolumeSource{},
-			},
-		},
-		{
-			Name: emptyDirHomeVolumeName,
-			VolumeSource: corev1.VolumeSource{
-				EmptyDir: &corev1.EmptyDirVolumeSource{},
-			},
-		},
-	}
-
-	volumeMounts := []corev1.VolumeMount{
-		{
-			Name:      configVolumeName,
-			MountPath: "/run/config",
-		},
-		{
-			Name:      emptyDirTmpVolumeName,
-			MountPath: "/tmp",
-		},
-		{
-			Name:      emptyDirHomeVolumeName,
-			MountPath: fleetHomeDir,
-		},
-	}
-
-	return volumes, volumeMounts
-}
-
-// ociVolumeFromSecret generates a volume and volume mount from a basic-auth secret.
-func ociVolumeFromSecret(
-	ctx context.Context,
-	c client.Client,
-	namespace, secretName, volumeName string,
-) (corev1.Volume, corev1.VolumeMount, error) {
-	var secret corev1.Secret
-	if err := c.Get(ctx, types.NamespacedName{
-		Namespace: namespace,
-		Name:      secretName,
-	}, &secret); err != nil {
-		return corev1.Volume{}, corev1.VolumeMount{}, err
-	}
-	volume := corev1.Volume{
-		Name: volumeName,
-		VolumeSource: corev1.VolumeSource{
-			Secret: &corev1.SecretVolumeSource{
-				SecretName: secretName,
-			},
-		},
-	}
-	volumeMount := corev1.VolumeMount{
-		Name:      volumeName,
-		MountPath: "/etc/fleet/oci",
-	}
-	return volume, volumeMount, nil
-}
-
-// volumesFromSecret generates volumes and volume mounts from a Helm secret, assuming that that secret exists.
-// If the secret has a cacerts key, it will be mounted into /etc/ssl/certs, too.
-func volumesFromSecret(
-	ctx context.Context,
-	c client.Client,
-	namespace string,
-	secretName, volumeName, mountPath string,
-) ([]corev1.Volume, []corev1.VolumeMount, bool) {
-	if mountPath == "" {
-		mountPath = "/etc/fleet/helm"
-	}
-
-	volumes := []corev1.Volume{
-		{
-			Name: volumeName,
-			VolumeSource: corev1.VolumeSource{
-				Secret: &corev1.SecretVolumeSource{
-					SecretName: secretName,
-				},
-			},
-		},
-	}
-	volumeMounts := []corev1.VolumeMount{
-		{
-			Name:      volumeName,
-			MountPath: mountPath,
-		},
-	}
-
-	// Mount a CA certificate, if specified in the secret. This is necessary to support Helm registries with
-	// self-signed certificates.
-	secret := &corev1.Secret{}
-	var certVolCreated bool
-	_ = c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: secretName}, secret)
-	if _, ok := secret.Data["cacerts"]; ok {
-		volumes = append(volumes, corev1.Volume{
-			Name: fmt.Sprintf("%s-cert", volumeName),
-			VolumeSource: corev1.VolumeSource{
-				Secret: &corev1.SecretVolumeSource{
-					SecretName: secretName,
-					Items: []corev1.KeyToPath{
-						{
-							Key:  "cacerts",
-							Path: "cacert.crt",
-						},
-					},
-				},
-			},
-		})
-		volumeMounts = append(volumeMounts, corev1.VolumeMount{
-			Name:      fmt.Sprintf("%s-cert", volumeName),
-			MountPath: "/etc/ssl/certs",
-		})
-
-		certVolCreated = true
-	}
-
-	return volumes, volumeMounts, certVolCreated
-}
-
-func (r *GitJobReconciler) newGitCloner(
-	ctx context.Context,
-	obj *v1alpha1.GitRepo,
-	knownHosts string,
-) (corev1.Container, error) {
-	args := []string{"fleet", "gitcloner", obj.Spec.Repo, "/workspace"}
-	volumeMounts := []corev1.VolumeMount{
-		{
-			Name:      gitClonerVolumeName,
-			MountPath: "/workspace",
-		},
-		{
-			Name:      emptyDirVolumeName,
-			MountPath: "/tmp",
-		},
-	}
-
-	branch, rev := obj.Spec.Branch, obj.Spec.Revision
-	if branch != "" {
-		args = append(args, "--branch", branch)
-	} else if rev != "" {
-		args = append(args, "--revision", rev)
-	} else {
-		args = append(args, "--branch", "master")
-	}
-
-	secretName := obj.Spec.ClientSecretName
-	if secretName == "" {
-		secretName = config.DefaultGitCredentialsSecretName
-	}
-
-	var secret corev1.Secret
-	err := r.Get(ctx, types.NamespacedName{
-		Namespace: obj.Namespace,
-		Name:      secretName,
-	}, &secret)
-
-	if err != nil && secretName == obj.Spec.ClientSecretName {
-		// Only error if an explicitly referenced secret was not found;
-		// The absence of a default secret might simply mean that no credentials are needed.
-		return corev1.Container{}, err
-	}
-
-	if err == nil {
-		switch secret.Type {
-		case corev1.SecretTypeBasicAuth:
-			volumeMounts = append(volumeMounts, corev1.VolumeMount{
-				Name:      gitCredentialVolumeName,
-				MountPath: "/gitjob/credentials",
-			})
-			args = append(args, "--username", string(secret.Data[corev1.BasicAuthUsernameKey]))
-			args = append(args, "--password-file", "/gitjob/credentials/"+corev1.BasicAuthPasswordKey)
-		case corev1.SecretTypeSSHAuth:
-			volumeMounts = append(volumeMounts, corev1.VolumeMount{
-				Name:      gitCredentialVolumeName,
-				MountPath: "/gitjob/ssh",
-			})
-			args = append(args, "--ssh-private-key-file", "/gitjob/ssh/"+corev1.SSHAuthPrivateKey)
-		}
-	}
-
-	if obj.Spec.InsecureSkipTLSverify {
-		args = append(args, "--insecure-skip-tls")
-	}
-
-	var CABundleSecret corev1.Secret
-	err = r.Get(ctx, types.NamespacedName{
-		Namespace: obj.Namespace,
-		Name:      caBundleName(obj),
-	}, &CABundleSecret)
-	if client.IgnoreNotFound(err) != nil {
-		return corev1.Container{}, err
-	}
-
-	if !apierrors.IsNotFound(err) {
-		volumeMounts = append(volumeMounts, corev1.VolumeMount{
-			Name:      bundleCAVolumeName,
-			MountPath: "/gitjob/cabundle",
-		})
-		args = append(args, "--ca-bundle-file", "/gitjob/cabundle/"+bundleCAFile)
-	}
-
-	env := proxyEnvVars()
-
-	// If strict host key checks are enabled but no entries are available, another error will be shown by the known
-	// hosts getter, as that means that the Fleet deployment is incomplete.
-	// On the other hand, we do not want to feed entries to the cloner if strict host key checks are disabled, as that
-	// would lead it to unduly reject SSH connection attempts.
-	if r.KnownHosts.IsStrict() {
-		env = append(env, corev1.EnvVar{Name: ssh.KnownHostsEnvVar, Value: knownHosts})
-	}
-
-	return corev1.Container{
-		Command:      []string{"log.sh"},
-		Args:         args,
-		Image:        r.Image,
-		Name:         "gitcloner-initializer",
-		VolumeMounts: volumeMounts,
-		Env:          env,
-		SecurityContext: &corev1.SecurityContext{
-			AllowPrivilegeEscalation: &[]bool{false}[0],
-			ReadOnlyRootFilesystem:   &[]bool{true}[0],
-			Privileged:               &[]bool{false}[0],
-			Capabilities:             &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}},
-			RunAsNonRoot:             &[]bool{true}[0],
-			SeccompProfile: &corev1.SeccompProfile{
-				Type: corev1.SeccompProfileTypeRuntimeDefault,
-			},
-		},
-	}, nil
-}
-
-func proxyEnvVars() []corev1.EnvVar {
-	var envVars []corev1.EnvVar
-	for _, envVar := range []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"} {
-		if val, ok := os.LookupEnv(envVar); ok {
-			envVars = append(envVars, corev1.EnvVar{Name: envVar, Value: val})
-		}
-	}
-
-	return envVars
-}
-
 // repoPolled returns true if the git poller was executed and the repo should still be polled.
 func (r *GitJobReconciler) repoPolled(ctx context.Context, gitrepo *v1alpha1.GitRepo) (bool, error) {
 	if gitrepo.Spec.DisablePolling {
@@ -1355,14 +440,6 @@ func (r *GitJobReconciler) shouldRunPollingTask(gitrepo *v1alpha1.GitRepo) bool
 	return false
 }
 
-func getPollingIntervalDuration(gitrepo *v1alpha1.GitRepo) time.Duration {
-	if gitrepo.Spec.PollingInterval == nil || gitrepo.Spec.PollingInterval.Duration == 0 {
-		return defaultPollingSyncInterval
-	}
-
-	return gitrepo.Spec.PollingInterval.Duration
-}
-
 func (r *GitJobReconciler) result(gitrepo *v1alpha1.GitRepo) reconcile.Result {
 	// We always return a reconcile Result with RequeueAfter set to the polling interval
 	// unless polling is disabled.
@@ -1394,42 +471,32 @@ func (r *GitJobReconciler) result(gitrepo *v1alpha1.GitRepo) reconcile.Result {
 	return reconcile.Result{RequeueAfter: requeueAfter}
 }
 
-func webhookCommitChangedPredicate() predicate.Predicate {
-	return predicate.Funcs{
-		UpdateFunc: func(e event.UpdateEvent) bool {
-			oldGitRepo, ok := e.ObjectOld.(*v1alpha1.GitRepo)
-			if !ok {
-				return true
-			}
-			newGitRepo, ok := e.ObjectNew.(*v1alpha1.GitRepo)
-			if !ok {
-				return true
-			}
-			return oldGitRepo.Status.WebhookCommit != newGitRepo.Status.WebhookCommit
-		},
+// addJitter to the requeue time to avoid thundering herd
+// generate a random number between -10% and +10% of the duration
+func addJitter(d time.Duration) time.Duration {
+	if d <= 0 {
+		return d
 	}
+
+	return d + time.Duration(rand.Int64N(int64(d)/10)) // nolint:gosec // gosec G404 false positive, not used for crypto
 }
 
-func jobUpdatedPredicate() predicate.Funcs {
-	return predicate.Funcs{
-		CreateFunc: func(e event.CreateEvent) bool {
-			return false
-		},
-		UpdateFunc: func(e event.UpdateEvent) bool {
-			n, isJob := e.ObjectNew.(*batchv1.Job)
-			if !isJob {
-				return false
-			}
-			o := e.ObjectOld.(*batchv1.Job)
-			if n == nil || o == nil {
-				return false
-			}
-			return !reflect.DeepEqual(n.Status, o.Status)
-		},
-		DeleteFunc: func(e event.DeleteEvent) bool {
-			return false
-		},
+func generationChanged(r *v1alpha1.GitRepo) bool {
+	// checks if generation changed.
+	// it ignores the case when Status.ObservedGeneration=0 because that's
+	// the initial value of a just created GitRepo and the initial value
+	// for Generation in k8s is 1.
+	// If we don't ignore we would be deleting the gitjob that was just created
+	// until later we reconcile ObservedGeneration with Generation
+	return (r.Generation != r.Status.ObservedGeneration) && r.Status.ObservedGeneration > 0
+}
+
+func getPollingIntervalDuration(gitrepo *v1alpha1.GitRepo) time.Duration {
+	if gitrepo.Spec.PollingInterval == nil || gitrepo.Spec.PollingInterval.Duration == 0 {
+		return defaultPollingSyncInterval
 	}
+
+	return gitrepo.Spec.PollingInterval.Duration
 }
 
 // setStatusFromGitjob sets the status fields relative to the given job in the gitRepo
@@ -1587,48 +654,3 @@ func updateStatus(ctx context.Context, c client.Client, req types.NamespacedName
 		return nil
 	})
 }
-
-func gitSSHCommandEnvVar(strictChecks bool) corev1.EnvVar {
-	strictVal := "no"
-
-	if strictChecks {
-		strictVal = "yes"
-	}
-
-	return corev1.EnvVar{
-		Name:  "GIT_SSH_COMMAND",
-		Value: fmt.Sprintf("ssh -o stricthostkeychecking=%s", strictVal),
-	}
-}
-
-// getDrivenScanSeparator returns a separator that is valid for all the Bundle
-// definitions in the given GitRepo.
-// Since we cannot disregard the possibility that a user might have an
-// unavoidable need to use the character ":" (or another character typically not
-// used in directory or file paths), we need to find possible alternatives.
-// The function will search for simple characters from those in
-// bundleOptionsSeparatorChars, and if none of them can be used, it will return an error.
-func getDrivenScanSeparator(gitrepo v1alpha1.GitRepo) (string, error) {
-	for _, sep := range bundleOptionsSeparatorChars {
-		if !separatorInBundleDefinitions(gitrepo, sep) {
-			// We can safely use this separator
-			return string(sep), nil
-		}
-	}
-
-	return "", fmt.Errorf("bundle base and/or options paths contain all possible characters from %q, please update those paths to remedy this", bundleOptionsSeparatorChars)
-}
-
-func separatorInBundleDefinitions(gitrepo v1alpha1.GitRepo, sep rune) bool {
-	for _, b := range gitrepo.Spec.Bundles {
-		if strings.ContainsRune(b.Options, sep) {
-			return true
-		}
-
-		if strings.ContainsRune(b.Base, sep) {
-			return true
-		}
-	}
-
-	return false
-}
diff --git a/internal/cmd/controller/gitops/reconciler/predicate.go b/internal/cmd/controller/gitops/reconciler/predicate.go
index 864648c9..cbec5c95 100644
--- a/internal/cmd/controller/gitops/reconciler/predicate.go
+++ b/internal/cmd/controller/gitops/reconciler/predicate.go
@@ -3,6 +3,8 @@ package reconciler
 import (
 	"reflect"
 
+	v1alpha1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+	batchv1 "k8s.io/api/batch/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -49,3 +51,41 @@ func (TypedResourceVersionUnchangedPredicate[T]) Update(e event.TypedUpdateEvent
 func (TypedResourceVersionUnchangedPredicate[T]) Generic(e event.GenericEvent) bool {
 	return false
 }
+
+func jobUpdatedPredicate() predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			return false
+		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			n, isJob := e.ObjectNew.(*batchv1.Job)
+			if !isJob {
+				return false
+			}
+			o := e.ObjectOld.(*batchv1.Job)
+			if n == nil || o == nil {
+				return false
+			}
+			return !reflect.DeepEqual(n.Status, o.Status)
+		},
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			return false
+		},
+	}
+}
+
+func webhookCommitChangedPredicate() predicate.Predicate {
+	return predicate.Funcs{
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			oldGitRepo, ok := e.ObjectOld.(*v1alpha1.GitRepo)
+			if !ok {
+				return true
+			}
+			newGitRepo, ok := e.ObjectNew.(*v1alpha1.GitRepo)
+			if !ok {
+				return true
+			}
+			return oldGitRepo.Status.WebhookCommit != newGitRepo.Status.WebhookCommit
+		},
+	}
+}
diff --git a/internal/cmd/controller/gitops/reconciler/rbac.go b/internal/cmd/controller/gitops/reconciler/rbac.go
new file mode 100644
index 00000000..d692d8ec
--- /dev/null
+++ b/internal/cmd/controller/gitops/reconciler/rbac.go
@@ -0,0 +1,109 @@
+// Copyright (c) 2021-2025 SUSE LLC
+
+package reconciler
+
+import (
+	"context"
+
+	"github.com/rancher/fleet/internal/names"
+
+	v1alpha1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+)
+
+func (r *GitJobReconciler) createJobRBAC(ctx context.Context, gitRepo *v1alpha1.GitRepo) error {
+	saName := names.SafeConcatName("git", gitRepo.Name)
+
+	if err := r.createServiceAccount(ctx, gitRepo, saName); err != nil {
+		return err
+	}
+
+	if err := r.createOrUpdateRole(ctx, gitRepo, saName); err != nil {
+		return err
+	}
+
+	if err := r.createOrUpdateRoleBinding(ctx, gitRepo, saName); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (r *GitJobReconciler) createServiceAccount(ctx context.Context, gitRepo *v1alpha1.GitRepo, saName string) error {
+	sa := &corev1.ServiceAccount{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      saName,
+			Namespace: gitRepo.Namespace,
+		},
+	}
+	if err := controllerutil.SetControllerReference(gitRepo, sa, r.Scheme); err != nil {
+		return err
+	}
+	// No update needed, values are the same. So we ignore AlreadyExists.
+	if err := r.Create(ctx, sa); err != nil && !errors.IsAlreadyExists(err) {
+		return err
+	}
+	return nil
+}
+
+func (r *GitJobReconciler) createOrUpdateRole(ctx context.Context, gitRepo *v1alpha1.GitRepo, saName string) error {
+	role := &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: gitRepo.Namespace, Name: saName}}
+	if err := controllerutil.SetControllerReference(gitRepo, role, r.Scheme); err != nil {
+		return err
+	}
+	if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, role, func() error {
+		role.Rules = []rbacv1.PolicyRule{
+			{
+				Verbs:     []string{"get", "create", "update", "list", "delete"},
+				APIGroups: []string{"fleet.cattle.io"},
+				Resources: []string{"bundles", "imagescans"},
+			},
+			{
+				Verbs:     []string{"get"},
+				APIGroups: []string{"fleet.cattle.io"},
+				Resources: []string{"gitrepos"},
+			},
+			{
+				Verbs:     []string{"get", "create", "update", "delete"},
+				APIGroups: []string{""},
+				Resources: []string{"secrets"},
+			},
+		}
+		return nil
+	}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (r *GitJobReconciler) createOrUpdateRoleBinding(ctx context.Context, gitRepo *v1alpha1.GitRepo, saName string) error {
+	rb := &rbacv1.RoleBinding{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      saName,
+			Namespace: gitRepo.Namespace,
+		},
+	}
+	if err := controllerutil.SetControllerReference(gitRepo, rb, r.Scheme); err != nil {
+		return err
+	}
+	if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, rb, func() error {
+		rb.Subjects = []rbacv1.Subject{{
+			Kind:      "ServiceAccount",
+			Name:      saName,
+			Namespace: gitRepo.Namespace,
+		}}
+		rb.RoleRef = rbacv1.RoleRef{
+			APIGroup: "rbac.authorization.k8s.io",
+			Kind:     "Role",
+			Name:     saName,
+		}
+		return nil
+	}); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/internal/cmd/controller/gitops/reconciler/status_controller.go b/internal/cmd/controller/gitops/reconciler/status_controller.go
index c626840a..80f010f8 100644
--- a/internal/cmd/controller/gitops/reconciler/status_controller.go
+++ b/internal/cmd/controller/gitops/reconciler/status_controller.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"sort"
+	"time"
 
 	"github.com/rancher/fleet/internal/cmd/controller/status"
 	"github.com/rancher/fleet/internal/cmd/controller/summary"
@@ -12,19 +13,23 @@ import (
 	"github.com/rancher/fleet/pkg/durations"
 	"github.com/rancher/fleet/pkg/sharding"
 	"github.com/rancher/wrangler/v3/pkg/genericcondition"
-
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/util/workqueue"
 	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/builder"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/source"
 )
 
+// enqueueDelay is used as an artificial delay for enqueuing GitRepo status reconciliation requests
+// This allows aggregating multiple consecutive Bundle update events, reducing the number of GitRepo status changes at the cost of introducing a delay in the notification
+const enqueueDelay = 3 * time.Second
+
 type StatusReconciler struct {
 	client.Client
 	Scheme  *runtime.Scheme
@@ -35,10 +40,11 @@ type StatusReconciler struct {
 func (r *StatusReconciler) SetupWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewControllerManagedBy(mgr).
 		For(&fleet.GitRepo{}).
-		Watches(
+		WatchesRawSource(wrapSourceWithForcedDelay(enqueueDelay, source.TypedKind(
 			// Fan out from bundle to gitrepo
+			mgr.GetCache(),
 			&fleet.Bundle{},
-			handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []ctrl.Request {
+			handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, a *fleet.Bundle) []ctrl.Request {
 				repo := a.GetLabels()[fleet.RepoLabel]
 				if repo != "" {
 					return []ctrl.Request{{
@@ -51,8 +57,9 @@ func (r *StatusReconciler) SetupWithManager(mgr ctrl.Manager) error {
 
 				return []ctrl.Request{}
 			}),
-			builder.WithPredicates(status.BundleStatusChangedPredicate()),
-		).
+			sharding.TypedFilterByShardID[*fleet.Bundle](r.ShardID), // WatchesRawSources ignores event filters, we need to use a predicate
+			status.BundleStatusChangedPredicate(),
+		))).
 		WithEventFilter(sharding.FilterByShardID(r.ShardID)).
 		WithOptions(controller.Options{MaxConcurrentReconciles: r.Workers}).
 		Named("GitRepoStatus").
@@ -227,3 +234,25 @@ bundles:
 
 	return nil
 }
+
+type forcedDelayingSource[R comparable] struct {
+	source.TypedSource[R]
+	delay time.Duration
+}
+
+func wrapSourceWithForcedDelay[R comparable](delay time.Duration, delegate source.TypedSource[R]) source.TypedSource[R] {
+	return &forcedDelayingSource[R]{TypedSource: delegate, delay: delay}
+}
+
+func (s *forcedDelayingSource[R]) Start(ctx context.Context, delegate workqueue.TypedRateLimitingInterface[R]) error {
+	return s.TypedSource.Start(ctx, &forcedDelayingQueue[R]{delegate, s.delay})
+}
+
+type forcedDelayingQueue[R comparable] struct {
+	workqueue.TypedRateLimitingInterface[R]
+	delay time.Duration
+}
+
+func (f *forcedDelayingQueue[R]) Add(obj R) {
+	f.AddAfter(obj, f.delay)
+}
diff --git a/internal/cmd/controller/helmops/operator.go b/internal/cmd/controller/helmops/operator.go
index 40c915ba..09a4b653 100644
--- a/internal/cmd/controller/helmops/operator.go
+++ b/internal/cmd/controller/helmops/operator.go
@@ -90,9 +90,9 @@ func (g *HelmOperator) Run(cmd *cobra.Command, args []string) error {
 		LeaderElection:          g.EnableLeaderElection,
 		LeaderElectionID:        fmt.Sprintf("fleet-helmops-leader-election-shard%s", shardIDSuffix),
 		LeaderElectionNamespace: namespace,
-		LeaseDuration:           leaderOpts.LeaseDuration,
-		RenewDeadline:           leaderOpts.RenewDeadline,
-		RetryPeriod:             leaderOpts.RetryPeriod,
+		LeaseDuration:           &leaderOpts.LeaseDuration,
+		RenewDeadline:           &leaderOpts.RenewDeadline,
+		RetryPeriod:             &leaderOpts.RetryPeriod,
 	})
 
 	if err != nil {
diff --git a/internal/cmd/controller/helmops/reconciler/helmapp_status.go b/internal/cmd/controller/helmops/reconciler/helmapp_status.go
index 38431175..2f59ea5b 100644
--- a/internal/cmd/controller/helmops/reconciler/helmapp_status.go
+++ b/internal/cmd/controller/helmops/reconciler/helmapp_status.go
@@ -11,12 +11,12 @@ import (
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 	"github.com/rancher/fleet/pkg/durations"
 	"github.com/rancher/fleet/pkg/sharding"
+	"sigs.k8s.io/controller-runtime/pkg/source"
 
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/builder"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
@@ -33,10 +33,11 @@ type HelmAppStatusReconciler struct {
 func (r *HelmAppStatusReconciler) SetupWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewControllerManagedBy(mgr).
 		For(&fleet.HelmApp{}).
-		Watches(
-			// Fan out from bundle to HelmApp
+		// Fan out from bundle to HelmApp
+		WatchesRawSource(source.TypedKind(
+			mgr.GetCache(),
 			&fleet.Bundle{},
-			handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []ctrl.Request {
+			handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, a *fleet.Bundle) []ctrl.Request {
 				app := a.GetLabels()[fleet.HelmAppLabel]
 				if app != "" {
 					return []ctrl.Request{{
@@ -49,8 +50,9 @@ func (r *HelmAppStatusReconciler) SetupWithManager(mgr ctrl.Manager) error {
 
 				return []ctrl.Request{}
 			}),
-			builder.WithPredicates(status.BundleStatusChangedPredicate()),
-		).
+			sharding.TypedFilterByShardID[*fleet.Bundle](r.ShardID), // WatchesRawSources ignores event filters, we need to use a predicate
+			status.BundleStatusChangedPredicate(),
+		)).
 		WithEventFilter(sharding.FilterByShardID(r.ShardID)).
 		WithOptions(controller.Options{MaxConcurrentReconciles: r.Workers}).
 		Named("HelmAppStatus").
diff --git a/internal/cmd/controller/operator.go b/internal/cmd/controller/operator.go
index 18b8fdc4..b2d7a3d5 100644
--- a/internal/cmd/controller/operator.go
+++ b/internal/cmd/controller/operator.go
@@ -67,9 +67,9 @@ func start(
 		LeaderElection:          true,
 		LeaderElectionID:        fmt.Sprintf("fleet-controller-leader-election-shard%s", leaderElectionSuffix),
 		LeaderElectionNamespace: systemNamespace,
-		LeaseDuration:           leaderOpts.LeaseDuration,
-		RenewDeadline:           leaderOpts.RenewDeadline,
-		RetryPeriod:             leaderOpts.RetryPeriod,
+		LeaseDuration:           &leaderOpts.LeaseDuration,
+		RenewDeadline:           &leaderOpts.RenewDeadline,
+		RetryPeriod:             &leaderOpts.RetryPeriod,
 	})
 	if err != nil {
 		setupLog.Error(err, "unable to start manager")
diff --git a/internal/cmd/controller/status/status.go b/internal/cmd/controller/status/status.go
index 89e41289..94338508 100644
--- a/internal/cmd/controller/status/status.go
+++ b/internal/cmd/controller/status/status.go
@@ -12,23 +12,15 @@ import (
 
 // BundleStatusChangedPredicate returns true if the bundle
 // status has changed, or the bundle was created
-func BundleStatusChangedPredicate() predicate.Funcs {
-	return predicate.Funcs{
-		CreateFunc: func(e event.CreateEvent) bool {
+func BundleStatusChangedPredicate() predicate.TypedFuncs[*fleet.Bundle] {
+	return predicate.TypedFuncs[*fleet.Bundle]{
+		CreateFunc: func(e event.TypedCreateEvent[*fleet.Bundle]) bool {
 			return true
 		},
-		UpdateFunc: func(e event.UpdateEvent) bool {
-			n, isBundle := e.ObjectNew.(*fleet.Bundle)
-			if !isBundle {
-				return false
-			}
-			o := e.ObjectOld.(*fleet.Bundle)
-			if n == nil || o == nil {
-				return false
-			}
-			return !reflect.DeepEqual(n.Status, o.Status)
+		UpdateFunc: func(e event.TypedUpdateEvent[*fleet.Bundle]) bool {
+			return !reflect.DeepEqual(e.ObjectNew.Status, e.ObjectOld.Status)
 		},
-		DeleteFunc: func(e event.DeleteEvent) bool {
+		DeleteFunc: func(e event.TypedDeleteEvent[*fleet.Bundle]) bool {
 			return true
 		},
 	}
diff --git a/internal/cmd/options.go b/internal/cmd/options.go
index 01f5d78c..ab4ab411 100644
--- a/internal/cmd/options.go
+++ b/internal/cmd/options.go
@@ -13,42 +13,56 @@ type LeaderElectionOptions struct {
 	// LeaseDuration is the duration that non-leader candidates will
 	// wait to force acquire leadership. This is measured against time of
 	// last observed ack. Default is 15 seconds.
-	LeaseDuration *time.Duration
+	LeaseDuration time.Duration
 
 	// RenewDeadline is the duration that the acting controlplane will retry
 	// refreshing leadership before giving up. Default is 10 seconds.
-	RenewDeadline *time.Duration
+	RenewDeadline time.Duration
 
 	// RetryPeriod is the duration the LeaderElector clients should wait
 	// between tries of actions. Default is 2 seconds.
-	RetryPeriod *time.Duration
+	RetryPeriod time.Duration
 }
 
 // NewLeaderElectionOptions returns a new LeaderElectionOptions struct with the
 // values parsed from environment variables.
 func NewLeaderElectionOptions() (LeaderElectionOptions, error) {
-	leaderOpts := LeaderElectionOptions{}
-	if d := os.Getenv("CATTLE_ELECTION_LEASE_DURATION"); d != "" {
-		v, err := time.ParseDuration(d)
-		if err != nil {
-			return leaderOpts, fmt.Errorf("failed to parse CATTLE_ELECTION_LEASE_DURATION with duration %s: %w", d, err)
-		}
-		leaderOpts.LeaseDuration = &v
+	return NewLeaderElectionOptionsWithPrefix("CATTLE")
+}
+
+// NewLeaderElectionOptionsWithPrefix returns a new LeaderElectionOptions struct with the values
+// parsed from environment variables with the given prefix.
+//
+// The environment variables passed to the fleet agentmanagement container in the fleet controller
+// pod need to be prefixed differently to differentiate between those which are meant to configure
+// the fleet controller and those which are meant to configure the fleet agentmanagement container,
+// since the fleet agentmanagement controller creates the fleet deployment (for manager initiated
+// deployments).
+func NewLeaderElectionOptionsWithPrefix(prefix string) (LeaderElectionOptions, error) {
+	var (
+		leaderOpts LeaderElectionOptions
+		err        error
+		name       string
+	)
+
+	name = fmt.Sprintf("%s_ELECTION_LEASE_DURATION", prefix)
+	leaderOpts.LeaseDuration, err = parseEnvDuration(name)
+	if err != nil {
+		return leaderOpts, err
 	}
-	if d := os.Getenv("CATTLE_ELECTION_RENEW_DEADLINE"); d != "" {
-		v, err := time.ParseDuration(d)
-		if err != nil {
-			return leaderOpts, fmt.Errorf("failed to parse CATTLE_ELECTION_RENEW_DEADLINE with duration %s: %w", d, err)
-		}
-		leaderOpts.RenewDeadline = &v
+
+	name = fmt.Sprintf("%s_ELECTION_RENEW_DEADLINE", prefix)
+	leaderOpts.RenewDeadline, err = parseEnvDuration(name)
+	if err != nil {
+		return leaderOpts, err
 	}
-	if d := os.Getenv("CATTLE_ELECTION_RETRY_PERIOD"); d != "" {
-		v, err := time.ParseDuration(d)
-		if err != nil {
-			return leaderOpts, fmt.Errorf("failed to parse CATTLE_ELECTION_RETRY_PERIOD with duration %s: %w", d, err)
-		}
-		leaderOpts.RetryPeriod = &v
+
+	name = fmt.Sprintf("%s_ELECTION_RETRY_PERIOD", prefix)
+	leaderOpts.RetryPeriod, err = parseEnvDuration(name)
+	if err != nil {
+		return leaderOpts, err
 	}
+
 	return leaderOpts, nil
 }
 
@@ -75,3 +89,14 @@ func parseEnvInt32(envVar string) (int32, error) {
 	}
 	return 0, fmt.Errorf("environment variable %s not set", envVar)
 }
+
+func parseEnvDuration(envVar string) (time.Duration, error) {
+	if d := os.Getenv(envVar); d != "" {
+		v, err := time.ParseDuration(d)
+		if err != nil {
+			return 0, fmt.Errorf("failed to parse %s with duration %s: %w", envVar, d, err)
+		}
+		return v, nil
+	}
+	return 0, fmt.Errorf("environment variable %s not set", envVar)
+}
diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/gitrepo_types.go b/pkg/apis/fleet.cattle.io/v1alpha1/gitrepo_types.go
index 41098a35..23d86f05 100644
--- a/pkg/apis/fleet.cattle.io/v1alpha1/gitrepo_types.go
+++ b/pkg/apis/fleet.cattle.io/v1alpha1/gitrepo_types.go
@@ -51,7 +51,8 @@ type GitRepoList struct {
 
 type GitRepoSpec struct {
 	// Repo is a URL to a git repo to clone and index.
-	// +nullable
+	// +required
+	// +kubebuilder:validation:MinLength=1
 	Repo string `json:"repo,omitempty"`
 
 	// Branch The git branch to follow.
diff --git a/pkg/sharding/sharding.go b/pkg/sharding/sharding.go
index 3107dd73..65cb3246 100644
--- a/pkg/sharding/sharding.go
+++ b/pkg/sharding/sharding.go
@@ -1,6 +1,7 @@
 package sharding
 
 import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -15,9 +16,9 @@ const (
 	ShardingDefaultLabel string = "fleet.cattle.io/shard-default"
 )
 
-// FilterByShardID returns a predicate function that filters objects by the shard ID they reference
-func FilterByShardID(shardID string) predicate.Funcs {
-	matchesLabel := func(o client.Object) bool {
+// TypedFilterByShardID returns a predicate function that filters objects by the shard ID they reference
+func TypedFilterByShardID[T client.Object](shardID string) predicate.TypedFuncs[T] {
+	matchesLabel := func(o metav1.Object) bool {
 		label, hasLabel := o.GetLabels()[ShardingRefLabel]
 
 		if shardID == "" {
@@ -27,15 +28,20 @@ func FilterByShardID(shardID string) predicate.Funcs {
 		return label == shardID
 	}
 
-	return predicate.Funcs{
-		CreateFunc: func(e event.CreateEvent) bool {
+	return predicate.TypedFuncs[T]{
+		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
 			return matchesLabel(e.Object)
 		},
-		UpdateFunc: func(e event.UpdateEvent) bool {
+		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
 			return matchesLabel(e.ObjectNew)
 		},
-		DeleteFunc: func(e event.DeleteEvent) bool {
+		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
 			return matchesLabel(e.Object)
 		},
 	}
 }
+
+// FilterByShardID returns a predicate function that filters objects by the shard ID they reference
+func FilterByShardID(shardID string) predicate.Funcs {
+	return TypedFilterByShardID[client.Object](shardID)
+}



Test patch:
diff --git a/charts/fleet/tests/agent-leader-election.yaml b/charts/fleet/tests/agent-leader-election.yaml
new file mode 100644
index 00000000..819cedd1
--- /dev/null
+++ b/charts/fleet/tests/agent-leader-election.yaml
@@ -0,0 +1,25 @@
+suite: leader election values test for fleet agent
+templates:
+  - deployment.yaml
+tests:
+  - it: should set the environment variable to the duration
+    set:
+      agent:
+        leaderElection:
+          leaseDuration: 60s
+          retryPeriod: 5s
+          renewDeadline: 10s
+    asserts:
+      - isKind:
+          of: Deployment
+      - equal:
+          path: spec.template.spec.containers[?(@.name == 'fleet-agentmanagement')].env[?(@.name == 'FLEET_AGENT_ELECTION_LEASE_DURATION')].value
+          value: "60s"
+      - equal:
+          path: spec.template.spec.containers[?(@.name == 'fleet-agentmanagement')].env[?(@.name == 'FLEET_AGENT_ELECTION_RETRY_PERIOD')].value
+          value: "5s"
+      - equal:
+          path: spec.template.spec.containers[?(@.name == 'fleet-agentmanagement')].env[?(@.name == 'FLEET_AGENT_ELECTION_RENEW_DEADLINE')].value
+          value: "10s"
+
+
diff --git a/integrationtests/gitjob/controller/status_test.go b/integrationtests/gitjob/controller/status_test.go
index 0c006ab1..4d527a96 100644
--- a/integrationtests/gitjob/controller/status_test.go
+++ b/integrationtests/gitjob/controller/status_test.go
@@ -114,11 +114,14 @@ var _ = Describe("GitRepo Status Fields", func() {
 				return bundle.Status.Summary.Ready == 1
 			}).Should(BeTrue())
 
-			err = k8sClient.Get(ctx, gitrepoName, gitrepo)
-			Expect(err).ToNot(HaveOccurred())
-			Expect(gitrepo.Status.Summary.Ready).To(Equal(1))
-			Expect(gitrepo.Status.ReadyClusters).To(Equal(1))
-			Expect(gitrepo.Status.DesiredReadyClusters).To(Equal(1))
+			// waiting for the GitRepo to update
+			Eventually(func(g Gomega) {
+				err = k8sClient.Get(ctx, gitrepoName, gitrepo)
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(gitrepo.Status.Summary.Ready).To(Equal(1))
+				g.Expect(gitrepo.Status.ReadyClusters).To(Equal(1))
+				g.Expect(gitrepo.Status.DesiredReadyClusters).To(Equal(1))
+			}).Should(Succeed())
 
 			By("Deleting a bundle")
 			err = k8sClient.Delete(ctx, bundle)
diff --git a/integrationtests/helmops/controller/status_test.go b/integrationtests/helmops/controller/status_test.go
index 55c50b9d..f12be1e0 100644
--- a/integrationtests/helmops/controller/status_test.go
+++ b/integrationtests/helmops/controller/status_test.go
@@ -117,11 +117,14 @@ var _ = Describe("HelmApp Status Fields", func() {
 				return bundle.Status.Summary.Ready == 1
 			}).Should(BeTrue())
 
-			err = k8sClient.Get(ctx, helmAppName, helmapp)
-			Expect(err).ToNot(HaveOccurred())
-			Expect(helmapp.Status.Summary.Ready).To(Equal(1))
-			Expect(helmapp.Status.ReadyClusters).To(Equal(1))
-			Expect(helmapp.Status.DesiredReadyClusters).To(Equal(1))
+			// waiting for the Helmapp to update
+			Eventually(func(g Gomega) {
+				err = k8sClient.Get(ctx, helmAppName, helmapp)
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(helmapp.Status.Summary.Ready).To(Equal(1))
+				g.Expect(helmapp.Status.ReadyClusters).To(Equal(1))
+				g.Expect(helmapp.Status.DesiredReadyClusters).To(Equal(1))
+			}).Should(Succeed())
 
 			By("Deleting a bundle")
 			err = k8sClient.Delete(ctx, bundle)
diff --git a/internal/cmd/controller/agentmanagement/agent/manifest_test.go b/internal/cmd/controller/agentmanagement/agent/manifest_test.go
index 985d0ffa..ca342d6a 100644
--- a/internal/cmd/controller/agentmanagement/agent/manifest_test.go
+++ b/internal/cmd/controller/agentmanagement/agent/manifest_test.go
@@ -3,6 +3,7 @@ package agent_test
 import (
 	"reflect"
 	"testing"
+	"time"
 
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -11,11 +12,21 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 
+	"github.com/rancher/fleet/internal/cmd"
 	"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/agent"
 )
 
 const namespace = "fleet-system"
 
+var (
+	second     = time.Second
+	leaderOpts = cmd.LeaderElectionOptions{
+		LeaseDuration: second,
+		RenewDeadline: second,
+		RetryPeriod:   second,
+	}
+)
+
 func TestImageResolve(t *testing.T) {
 	tests := []struct {
 		systemDefaultRegistry string
@@ -57,6 +68,7 @@ func TestManifestAgentTolerations(t *testing.T) {
 		CheckinInterval:       "1s",
 		PrivateRepoURL:        "private.rancher.com:5000",
 		SystemDefaultRegistry: "default.rancher.com",
+		LeaderElectionOptions: leaderOpts,
 	}
 
 	// these tolerations should exist regardless of what user sent
@@ -117,6 +129,7 @@ func TestManifestAgentHostNetwork(t *testing.T) {
 		CheckinInterval:       "1s",
 		PrivateRepoURL:        "private.rancher.com:5000",
 		SystemDefaultRegistry: "default.rancher.com",
+		LeaderElectionOptions: leaderOpts,
 	}
 
 	for _, testCase := range []struct {
@@ -178,24 +191,38 @@ func TestManifestAgentAffinity(t *testing.T) {
 		}},
 	}}
 
+	baseOpts := agent.ManifestOptions{
+		LeaderElectionOptions: leaderOpts,
+	}
+
 	for _, testCase := range []struct {
 		name             string
 		getOpts          func() agent.ManifestOptions
 		expectedAffinity *corev1.Affinity
 	}{
 		{
-			name:             "Builtin Affinity",
-			getOpts:          func() agent.ManifestOptions { return agent.ManifestOptions{} },
+			name: "Builtin Affinity",
+			getOpts: func() agent.ManifestOptions {
+				return baseOpts
+			},
 			expectedAffinity: builtinAffinity,
 		},
 		{
-			name:             "Remove Affinity",
-			getOpts:          func() agent.ManifestOptions { return agent.ManifestOptions{AgentAffinity: &corev1.Affinity{}} },
+			name: "Remove Affinity",
+			getOpts: func() agent.ManifestOptions {
+				opts := baseOpts
+				opts.AgentAffinity = &corev1.Affinity{}
+				return opts
+			},
 			expectedAffinity: &corev1.Affinity{},
 		},
 		{
-			name:             "Override Affinity",
-			getOpts:          func() agent.ManifestOptions { return agent.ManifestOptions{AgentAffinity: customAffinity} },
+			name: "Override Affinity",
+			getOpts: func() agent.ManifestOptions {
+				opts := baseOpts
+				opts.AgentAffinity = customAffinity
+				return opts
+			},
 			expectedAffinity: customAffinity,
 		},
 	} {
@@ -228,26 +255,38 @@ func TestManifestAgentResources(t *testing.T) {
 		},
 	}
 
+	baseOpts := agent.ManifestOptions{
+		LeaderElectionOptions: leaderOpts,
+	}
+
 	for _, testCase := range []struct {
 		name              string
 		getOpts           func() agent.ManifestOptions
 		expectedResources corev1.ResourceRequirements
 	}{
 		{
-			name:              "Builtin Resources",
-			getOpts:           func() agent.ManifestOptions { return agent.ManifestOptions{} },
+			name: "Builtin Resources",
+			getOpts: func() agent.ManifestOptions {
+				return baseOpts
+			},
 			expectedResources: builtinResources,
 		},
 		{
 			name: "Remove Resources",
 			getOpts: func() agent.ManifestOptions {
-				return agent.ManifestOptions{AgentResources: &corev1.ResourceRequirements{}}
+				opts := baseOpts
+				opts.AgentResources = &corev1.ResourceRequirements{}
+				return opts
 			},
 			expectedResources: corev1.ResourceRequirements{},
 		},
 		{
-			name:              "Override Resources",
-			getOpts:           func() agent.ManifestOptions { return agent.ManifestOptions{AgentResources: &customResources} },
+			name: "Override Resources",
+			getOpts: func() agent.ManifestOptions {
+				opts := baseOpts
+				opts.AgentResources = &customResources
+				return opts
+			},
 			expectedResources: customResources,
 		},
 	} {