Repository: rancher/fleet

Issue:
Display error message with empty `repo` field
### Is there an existing issue for this?

- [x] I have searched the existing issues

### Current Behavior

## Issue

While the UI prevents a user from creating a gitrepo with empty `repo` this can be achieved via CLI and no error will be displayed in the UI.


## Observed behavior
The user is not prevented from creating the repo
No error is shown in UI

![Image](https://github.com/user-attachments/assets/16648a92-d453-4499-8ca9-6c3fa61b8e9c)



## Additional notes:
The same happens with Helmops

### Expected Behavior
To display error 
_No response_

### Steps To Reproduce
- Deploy Rancher with continuous delivery
- Execute this via terminal with  `kubectl apply`:
```
kind: GitRepo
apiVersion: fleet.cattle.io/v1alpha1
metadata:
  name: simple
  namespace: fleet-local
spec:
  repo:
  paths:
  - simple
```



### Environment

```markdown
- Architecture: amd64
- Fleet Version: 106.0.0+up0.12.0-alpha.14
- Cluster:
  - Provider: k3s
  - Options:
  - Kubernetes Version:  v1.30.3 +k3s1
```

### Logs

```markdown

```

### Anything else?

_No response_

Discussion:
@weyfonk: ## Additional QA
 
### Problem
When using `kubectl apply`:
1. One could successfully create a `GitRepo` without any `repo` field.
2. Once that `GitRepo` had been created, it didn't display any error in the Rancher UI.

### Solution
We have added validation on that field, which should prevent creation of a `GitRepo`:
A. Without a `repo` field
B. With an empty `repo` field (minimum field length:  1 character, to enable the URL to be validated by the gitOps controller)

### Testing
### Engineering Testing
#### Manual Testing
Tried creating a `GitRepo` in cases A. and B. above, which failed with different error messages reflecting the reason of the failure.

#### Automated Testing
N/A

### QA Testing Considerations
Verify that, when creating a `GitRepo` with a 1-char (minimum length) URL, the Rancher UI displays an URL validation error for the `GitRepo`.
 
#### Regressions Considerations
(already covered by automated end-to-end tests) Creating a `GitRepo` with a valid URL should still succeed.@mmartin24: Tested in `v2.12-931975c2c5795027c16de804caf35bb5247d83b2-head` with fleet `107.0.0+up0.13.0-alpha.2`

---

- Verified empty gitrepo cannot be created:

![Image](https://github.com/user-attachments/assets/3142509f-878b-46e9-8a06-10e4b1dbd366)

![Image](https://github.com/user-attachments/assets/9ff9dfac-6e7a-4e69-ad02-db15f87b3892)

As opposed in previous version:
![Image](https://github.com/user-attachments/assets/b571423d-7756-496f-99ee-8156c1f70637)

- Verified gitrepo with single character can be created: 
![Image](https://github.com/user-attachments/assets/bf5c71cf-13cb-4559-8227-d501084af290)



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/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/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/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/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/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.



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/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,
 		},
 	} {