Repository: rancher/fleet
Pull request #3431 (2025-03-07 14:32:55)

Change namespace for dev infrastructure
The new namespace is default, so services are no longer started in the workspaces used for the tests.

This also makes sure, single-cluster tests run in "fleet-local", as their gitrepo resources use the default target.



Patch:
diff --git a/.github/scripts/create-secrets.sh b/.github/scripts/create-secrets.sh
index 9505c394..8fc33cbd 100755
--- a/.github/scripts/create-secrets.sh
+++ b/.github/scripts/create-secrets.sh
@@ -3,7 +3,7 @@
 dir=${1-'FleetCI-RootCA'}
 
 # Create secret with certs, needed by test git server
-kubectl -n fleet-local create secret generic git-server-certs \
+kubectl -n default create secret generic git-server-certs \
     --from-file=./"$dir"/helm.crt \
     --from-file=./"$dir"/helm.key
 
diff --git a/.github/scripts/create-zot-certs.sh b/.github/scripts/create-zot-certs.sh
index 47c8151a..54cf3e02 100755
--- a/.github/scripts/create-zot-certs.sh
+++ b/.github/scripts/create-zot-certs.sh
@@ -20,11 +20,6 @@ must_create_certs ()
     return $?
 }
 
-if [ -z $FLEET_E2E_NS ]; then
-    echo "Error: env var FLEET_E2E_NS is not set. Exiting."
-    exit 1
-fi
-
 must_create_certs
 if [ $? -eq 1 ]; then
     # generate private key for CA
@@ -43,9 +38,9 @@ if [ $? -eq 1 ]; then
     keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
     subjectAltName = @alt_names
     [alt_names]
-    DNS.1 = zot-service.$FLEET_E2E_NS.svc.cluster.local
-    DNS.2 = chartmuseum-service.$FLEET_E2E_NS.svc.cluster.local
-    DNS.3 = git-service.$FLEET_E2E_NS.svc.cluster.local
+    DNS.1 = zot-service.default.svc.cluster.local
+    DNS.2 = chartmuseum-service.default.svc.cluster.local
+    DNS.3 = git-service.default.svc.cluster.local
 EOF
 
     # sign Zot cert with CA root key
diff --git a/dev/README.md b/dev/README.md
index 5791768a..7bbfb329 100644
--- a/dev/README.md
+++ b/dev/README.md
@@ -224,10 +224,8 @@ To build and run the infra setup command do:
 ```
 dev/import-images-tests-k3d
 # ./dev/create-zot-certs 'FleetCI-RootCA'
-pushd e2e/testenv/infra
-  go build
-popd
-./e2e/testenv/infra/infra setup
+dev/create-secrets
+go run ./e2e/testenv/infra/main.go setup
 ```
 
 The resulting deployments use a loadbalancer service, which means the host must be able to reach the loadbalancer IP.
diff --git a/dev/setup-cluster-config b/dev/setup-cluster-config
index 18141ebf..c7b45567 100644
--- a/dev/setup-cluster-config
+++ b/dev/setup-cluster-config
@@ -3,7 +3,7 @@
 set -ex
 
 if [ "$1" = "teardown" ]; then
-    ./e2e/testenv/infra/infra teardown
+    go run ./e2e/testenv/infra/main.go teardown
     exit 0
 fi
 
diff --git a/dev/setup-multi-cluster b/dev/setup-multi-cluster
index 50f53550..9e923154 100755
--- a/dev/setup-multi-cluster
+++ b/dev/setup-multi-cluster
@@ -27,4 +27,4 @@ fi
 ./dev/import-images-tests-k3d
 ./dev/create-zot-certs 'FleetCI-RootCA' # for OCI tests
 ./dev/create-secrets 'FleetCI-RootCA'
-./e2e/testenv/infra/infra setup
+go run ./e2e/testenv/infra/main.go setup
diff --git a/dev/setup-single-cluster b/dev/setup-single-cluster
index 913c1efa..f960e3a2 100755
--- a/dev/setup-single-cluster
+++ b/dev/setup-single-cluster
@@ -40,4 +40,4 @@ fi
 ./dev/import-images-tests-k3d
 ./dev/create-zot-certs 'FleetCI-RootCA' # for OCI tests
 ./dev/create-secrets 'FleetCI-RootCA'
-./e2e/testenv/infra/infra setup
+go run ./e2e/testenv/infra/main.go setup
diff --git a/internal/cmd/agent/clusterstatus.go b/internal/cmd/agent/clusterstatus.go
index 0475a534..bf6bf569 100644
--- a/internal/cmd/agent/clusterstatus.go
+++ b/internal/cmd/agent/clusterstatus.go
@@ -35,6 +35,7 @@ import (
 type ClusterStatus struct {
 	UpstreamOptions
 	CheckinInterval string `usage:"How often to post cluster status" env:"CHECKIN_INTERVAL"`
+	AgentInfo       *register.AgentInfo
 }
 
 func (cs *ClusterStatus) Start(ctx context.Context) error {
@@ -63,20 +64,15 @@ func (cs *ClusterStatus) Start(ctx context.Context) error {
 
 	// cannot start without kubeconfig for upstream cluster
 	setupLog.Info("Fetching kubeconfig for upstream cluster from registration", "namespace", cs.Namespace)
-	agentInfo, _, err := register.Get(ctx, cs.Namespace, localConfig)
-	if err != nil {
-		setupLog.Error(err, "failed to get kubeconfig from upstream cluster")
-		return err
-	}
 
 	// set up factory for upstream cluster
-	fleetNamespace, _, err := agentInfo.ClientConfig.Namespace()
+	fleetNamespace, _, err := cs.AgentInfo.ClientConfig.Namespace()
 	if err != nil {
 		setupLog.Error(err, "failed to get namespace from upstream cluster")
 		return err
 	}
 
-	fleetRESTConfig, err := agentInfo.ClientConfig.ClientConfig()
+	fleetRESTConfig, err := cs.AgentInfo.ClientConfig.ClientConfig()
 	if err != nil {
 		setupLog.Error(err, "failed to get kubeconfig from upstream cluster")
 		return err
@@ -118,13 +114,13 @@ func (cs *ClusterStatus) Start(ctx context.Context) error {
 		return err
 	}
 
-	setupLog.Info("Starting cluster status ticker", "checkin interval", checkinInterval.String(), "cluster namespace", agentInfo.ClusterNamespace, "cluster name", agentInfo.ClusterName)
+	setupLog.Info("Starting cluster status ticker", "checkin interval", checkinInterval.String(), "cluster namespace", cs.AgentInfo.ClusterNamespace, "cluster name", cs.AgentInfo.ClusterName)
 
 	go func() {
 		clusterstatus.Ticker(ctx,
 			cs.Namespace,
-			agentInfo.ClusterNamespace,
-			agentInfo.ClusterName,
+			cs.AgentInfo.ClusterNamespace,
+			cs.AgentInfo.ClusterName,
 			checkinInterval,
 			coreFactory.Core().V1().Node(),
 			fleetFactory.Fleet().V1alpha1().Cluster(),
diff --git a/internal/cmd/agent/operator.go b/internal/cmd/agent/operator.go
index 81d6acc2..ee15bbc7 100644
--- a/internal/cmd/agent/operator.go
+++ b/internal/cmd/agent/operator.go
@@ -3,6 +3,7 @@ package agent
 import (
 	"context"
 	"flag"
+	"fmt"
 	"os"
 
 	"github.com/rancher/fleet/internal/cmd/agent/controller"
@@ -20,6 +21,9 @@ import (
 
 	"helm.sh/helm/v3/pkg/cli"
 
+	"github.com/rancher/wrangler/v3/pkg/generated/controllers/core"
+	"github.com/rancher/wrangler/v3/pkg/ratelimit"
+
 	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/client-go/dynamic"
@@ -59,18 +63,25 @@ func start(
 	agentScope string,
 	workersOpts AgentReconcilerWorkers,
 	clusterStatus *ClusterStatus,
+	agentInfo *register.AgentInfo,
 ) error {
-	// Registration is done before start is called. If we are here, we are already registered.
-	// Retrieve the existing config from the registration.  Cannot start without kubeconfig for
-	// upstream cluster:
-	upstreamConfig, agentConfig, fleetNamespace, clusterName, err := loadRegistration(ctx, systemNamespace, localConfig)
+	upstreamConfig, err := agentInfo.ClientConfig.ClientConfig()
 	if err != nil {
-		setupLog.Error(err, "unable to load registration and start manager")
-		return err
+		return fmt.Errorf("failed to get client config: %w", err)
+	}
+	agentConfig, err := getAgentConfig(ctx, systemNamespace, localConfig)
+	if err != nil {
+		return fmt.Errorf("failed to get agent config: %w", err)
+	}
+
+	// fleetNamespace is the upstream cluster namespace from AgentInfo, e.g. cluster-fleet-ID
+	fleetNamespace, _, err := agentInfo.ClientConfig.Namespace()
+	if err != nil {
+		return fmt.Errorf("failed to get namespace from upstream cluster: %w", err)
 	}
 
 	// Start manager for upstream cluster, we do not use leader election
-	setupLog.Info("listening for changes on upstream cluster", "cluster", clusterName, "namespace", fleetNamespace)
+	setupLog.Info("listening for changes on upstream cluster", "cluster", agentInfo.ClusterName, "namespace", fleetNamespace)
 
 	metricsAddr := ":8080"
 	probeAddr := ":8081"
@@ -103,7 +114,7 @@ func start(
 		systemNamespace,
 		fleetNamespace,
 		agentScope,
-		agentConfig,
+		*agentConfig,
 		driftChan,
 		workersOpts.BundleDeployment,
 	)
@@ -161,38 +172,6 @@ func start(
 	return nil
 }
 
-func loadRegistration(
-	ctx context.Context,
-	systemNamespace string,
-	localConfig *rest.Config,
-) (*rest.Config, config.Config, string, string, error) {
-	agentInfo, agentConfig, err := register.Get(ctx, systemNamespace, localConfig)
-	if err != nil {
-		setupLog.Error(err, "cannot get registration info for upstream cluster")
-		return nil, config.Config{}, "", "", err
-	}
-
-	// fleetNamespace is the upstream cluster namespace from AgentInfo, e.g. cluster-fleet-ID
-	fleetNamespace, _, err := agentInfo.ClientConfig.Namespace()
-	if err != nil {
-		setupLog.Error(err, "cannot get upstream namespace from registration")
-		return nil, config.Config{}, "", "", err
-	}
-
-	upstreamConfig, err := agentInfo.ClientConfig.ClientConfig()
-	if err != nil {
-		setupLog.Error(err, "cannot get upstream kubeconfig from registration")
-		return nil, config.Config{}, "", "", err
-	}
-
-	if agentConfig == nil {
-		setupLog.Error(err, "unexpected nil agent config")
-		return nil, config.Config{}, "", "", err
-	}
-
-	return upstreamConfig, *agentConfig, fleetNamespace, agentInfo.ClusterName, nil
-}
-
 func newReconciler(
 	ctx context.Context,
 	localCtx context.Context,
@@ -325,3 +304,29 @@ func newCluster(ctx context.Context, config *rest.Config, options manager.Option
 
 	return cluster, nil
 }
+
+func getAgentConfig(ctx context.Context, namespace string, cfg *rest.Config) (agentConfig *config.Config, err error) {
+	cfg = rest.CopyConfig(cfg)
+	// disable the rate limiter
+	cfg.RateLimiter = ratelimit.None
+	k8s, err := core.NewFactoryFromConfig(cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	agentConfig, err = config.Lookup(ctx, namespace, config.AgentConfigName, k8s.Core().V1().ConfigMap())
+	if err != nil {
+		return nil, fmt.Errorf(
+			"failed to look up client config %s/%s: %w",
+			namespace,
+			config.AgentConfigName,
+			err,
+		)
+	}
+
+	if agentConfig.AgentTLSMode == config.AgentTLSModeStrict {
+		config.BypassSystemCAStore()
+	}
+
+	return agentConfig, nil
+}
diff --git a/internal/cmd/agent/register.go b/internal/cmd/agent/register.go
index d520f9dd..3e66cb19 100644
--- a/internal/cmd/agent/register.go
+++ b/internal/cmd/agent/register.go
@@ -2,6 +2,7 @@ package agent
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/rancher/fleet/internal/cmd/agent/register"
 
@@ -16,14 +17,14 @@ type Register struct {
 	UpstreamOptions
 }
 
-func (r *Register) RegisterAgent(ctx context.Context) error {
+func (r *Register) RegisterAgent(ctx context.Context) (*register.AgentInfo, error) {
 	ctrl.SetLogger(zap.New(zap.UseFlagOptions(zopts)))
 	ctx = log.IntoContext(ctx, ctrl.Log)
 
 	clientConfig := kubeconfig.GetNonInteractiveClientConfig(r.Kubeconfig)
 	kc, err := clientConfig.ClientConfig()
 	if err != nil {
-		return err
+		return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
 	}
 
 	setupLog.Info("starting registration on upstream cluster", "namespace", r.Namespace)
@@ -35,23 +36,20 @@ func (r *Register) RegisterAgent(ctx context.Context) error {
 	// a kubeconfig for the upstream cluster
 	agentInfo, err := register.Register(ctx, r.Namespace, kc)
 	if err != nil {
-		setupLog.Error(err, "failed to register with upstream cluster")
-		return err
+		return nil, fmt.Errorf("failed to register with upstream cluster: %w", err)
 	}
 
 	ns, _, err := agentInfo.ClientConfig.Namespace()
 	if err != nil {
-		setupLog.Error(err, "failed to get namespace from upstream cluster")
-		return err
+		return nil, fmt.Errorf("failed to get namespace from upstream cluster: %w", err)
 	}
 
 	_, err = agentInfo.ClientConfig.ClientConfig()
 	if err != nil {
-		setupLog.Error(err, "failed to get kubeconfig from upstream cluster")
-		return err
+		return nil, fmt.Errorf("failed to get kubeconfig from upstream cluster: %w", err)
 	}
 
 	setupLog.Info("successfully registered with upstream cluster", "namespace", ns)
 
-	return nil
+	return agentInfo, nil
 }
diff --git a/internal/cmd/agent/register/register.go b/internal/cmd/agent/register/register.go
index 9eac5626..a69bc5a4 100644
--- a/internal/cmd/agent/register/register.go
+++ b/internal/cmd/agent/register/register.go
@@ -47,53 +47,6 @@ type AgentInfo struct {
 	ClientConfig     clientcmd.ClientConfig
 }
 
-// Get a registration token from the local cluster and fail if it does not exist.
-// This does not create a new registration process and only works after
-// registration has been completed.
-func Get(ctx context.Context, namespace string, cfg *rest.Config) (*AgentInfo, *config.Config, error) {
-	cfg = rest.CopyConfig(cfg)
-	// disable the rate limiter
-	cfg.RateLimiter = ratelimit.None
-	k8s, err := core.NewFactoryFromConfig(cfg)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	agentConfig, err := config.Lookup(ctx, namespace, config.AgentConfigName, k8s.Core().V1().ConfigMap())
-	if err != nil {
-		return nil, nil, fmt.Errorf(
-			"failed to look up client config %s/%s: %w",
-			namespace,
-			config.AgentConfigName,
-			err,
-		)
-	}
-
-	if agentConfig.AgentTLSMode == config.AgentTLSModeStrict {
-		config.BypassSystemCAStore()
-	}
-
-	secret, err := k8s.Core().V1().Secret().Get(namespace, CredName, metav1.GetOptions{})
-	if apierrors.IsNotFound(err) {
-		logrus.Warn("Cannot find fleet-agent secret")
-		return nil, nil, err
-	} else if err != nil {
-		logrus.Error("Cannot get fleet-agent secret")
-		return nil, nil, err
-	}
-
-	clientConfig, err := clientcmd.NewClientConfigFromBytes(secret.Data[Kubeconfig])
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return &AgentInfo{
-		ClusterNamespace: string(secret.Data[ClusterNamespace]),
-		ClusterName:      string(secret.Data[ClusterName]),
-		ClientConfig:     clientConfig,
-	}, agentConfig, nil
-}
-
 // Register creates a fleet-agent secret with the upstream kubeconfig, by
 // running the registration process with the upstream cluster.
 // For the initial registration, the fleet-agent-bootstrap secret must exist
@@ -136,7 +89,7 @@ func tryRegister(ctx context.Context, namespace string, cfg *rest.Config) (*Agen
 		return nil, err
 	} else if err := testClientConfig(secret.Data[Kubeconfig]); err != nil {
 		// skip testClientConfig check if previous error, or IsNotFound fallback succeeded
-		logrus.Errorf("Current credential failed, failing back to reregistering: %v", err)
+		logrus.Errorf("Current credential failed, falling back to reregistering: %v", err)
 		secret, err = runRegistration(ctx, k8s.Core().V1(), namespace)
 		if err != nil {
 			return nil, fmt.Errorf("re-registration failed: %w", err)
diff --git a/internal/cmd/agent/root.go b/internal/cmd/agent/root.go
index 42259cc6..26264153 100644
--- a/internal/cmd/agent/root.go
+++ b/internal/cmd/agent/root.go
@@ -131,7 +131,8 @@ func (a *FleetAgent) Run(cmd *cobra.Command, args []string) error {
 						Namespace: a.Namespace,
 					},
 				}
-				err := r.RegisterAgent(ctx)
+
+				agentInfo, err := r.RegisterAgent(ctx)
 				if err != nil {
 					setupLog.Error(err, "failed to register with upstream cluster")
 					return
@@ -161,9 +162,10 @@ func (a *FleetAgent) Run(cmd *cobra.Command, args []string) error {
 						Namespace:  a.Namespace,
 					},
 					CheckinInterval: a.CheckinInterval,
+					AgentInfo:       agentInfo,
 				}
 
-				if err := start(ctx, localConfig, a.Namespace, a.AgentScope, workersOpts, clusterStatus); err != nil {
+				if err := start(ctx, localConfig, a.Namespace, a.AgentScope, workersOpts, clusterStatus, agentInfo); err != nil {
 					setupLog.Error(err, "failed to start agent")
 				}
 			},
diff --git a/internal/cmd/cli/gitcloner/cloner.go b/internal/cmd/cli/gitcloner/cloner.go
index 78286b8f..3744de69 100644
--- a/internal/cmd/cli/gitcloner/cloner.go
+++ b/internal/cmd/cli/gitcloner/cloner.go
@@ -3,6 +3,7 @@ package gitcloner
 import (
 	"fmt"
 	"os"
+	"strings"
 
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
@@ -38,6 +39,16 @@ func New() *Cloner {
 }
 
 func (c *Cloner) CloneRepo(opts *GitCloner) error {
+	url, err := giturls.Parse(opts.Repo)
+	if err != nil {
+		return fmt.Errorf("failed to parse git URL: %w", err)
+	}
+	if strings.HasPrefix(url.String(), "ssh://") {
+		if opts.SSHPrivateKeyFile == "" {
+			return fmt.Errorf("SSH private key file is required for SSH/SCP-style URLs: %s", url)
+		}
+	}
+
 	// Azure DevOps requires capabilities multi_ack / multi_ack_detailed,
 	// which are not fully implemented and by default are included in
 	// transport.UnsupportedCapabilities.
@@ -49,11 +60,11 @@ func (c *Cloner) CloneRepo(opts *GitCloner) error {
 	}
 	auth, err := createAuthFromOpts(opts)
 	if err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to create auth from options for %s: %w", repo(opts), err)
 	}
 	caBundle, err := getCABundleFromFile(opts.CABundleFile)
 	if err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to read CA bundle from file for %s: %w", repo(opts), err)
 	}
 
 	if opts.Branch == "" && opts.Revision == "" {
@@ -83,7 +94,7 @@ func cloneBranch(opts *GitCloner, auth transport.AuthMethod, caBundle []byte) er
 	})
 
 	if err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to clone repo from branch %s: %w", repo(opts), err)
 	}
 	return nil
 }
@@ -97,19 +108,19 @@ func cloneRevision(opts *GitCloner, auth transport.AuthMethod, caBundle []byte)
 		RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
 	})
 	if err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to clone repo from revision %s: %w", repo(opts), err)
 	}
 	h, err := r.ResolveRevision(plumbing.Revision(opts.Revision))
 	if err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to resolve revision %s: %w", repo(opts), err)
 	}
 	w, err := r.Worktree()
 	if err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to get filesystem worktree for %s: %w", repo(opts), err)
 	}
 
 	if err := w.Checkout(&git.CheckoutOptions{Hash: *h}); err != nil {
-		return fmt.Errorf("%s: %w", gitClonerErrorContext(opts), err)
+		return fmt.Errorf("failed to checkout in worktree %s: %w", repo(opts), err)
 	}
 
 	return nil
@@ -188,12 +199,6 @@ func createKnownHostsCallBack(knownHosts []byte) (ssh.HostKeyCallback, error) {
 	return gossh.NewKnownHostsCallback(f.Name())
 }
 
-func gitClonerErrorContext(opts *GitCloner) string {
-	return fmt.Sprintf(
-		"failed checkout of: repo=[%s] branch=[%s] revision=[%s] path=[%s]",
-		opts.Repo,
-		opts.Branch,
-		opts.Revision,
-		opts.Path,
-	)
+func repo(opts *GitCloner) string {
+	return fmt.Sprintf("repo=%q branch=%q revision=%q path=%q", opts.Repo, opts.Branch, opts.Revision, opts.Path)
 }
diff --git a/pkg/git/remote.go b/pkg/git/remote.go
index 3f00c97f..75fde074 100644
--- a/pkg/git/remote.go
+++ b/pkg/git/remote.go
@@ -2,6 +2,7 @@ package git
 
 import (
 	"fmt"
+	"strings"
 	"time"
 
 	gogit "github.com/go-git/go-git/v5"
@@ -9,6 +10,7 @@ import (
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/storage/memory"
 	"github.com/go-logr/logr"
+	giturls "github.com/rancher/fleet/pkg/git-urls"
 	corev1 "k8s.io/api/core/v1"
 )
 
@@ -74,6 +76,17 @@ func NewRemote(url string, opts *options) (*Remote, error) {
 	if err != nil {
 		return nil, err
 	}
+
+	// parse the url to make sure it is valid and convert scp-like urls to ssh
+	u, err := giturls.Parse(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if auth == nil && strings.HasPrefix(u.String(), "ssh://") {
+		return nil, fmt.Errorf("SSH private key file is required for SSH/SCP-style URLs: %s", url)
+	}
+
 	return &Remote{
 		URL:     url,
 		Options: opts,


Test patch:
diff --git a/.github/workflows/e2e-ci.yml b/.github/workflows/e2e-ci.yml
index a8126707..c9c0d44c 100644
--- a/.github/workflows/e2e-ci.yml
+++ b/.github/workflows/e2e-ci.yml
@@ -103,8 +103,6 @@ jobs:
       -
         name: Create Zot certificates for OCI tests
         if: ${{ matrix.test_type.name == 'infra-setup' }}
-        env:
-          FLEET_E2E_NS: fleet-local
         run: |
           ./.github/scripts/create-zot-certs.sh "FleetCI-RootCA"
           ./.github/scripts/create-secrets.sh 'FleetCI-RootCA'
diff --git a/.github/workflows/e2e-nightly-ci.yml b/.github/workflows/e2e-nightly-ci.yml
index 3d45bf6b..257e9f28 100644
--- a/.github/workflows/e2e-nightly-ci.yml
+++ b/.github/workflows/e2e-nightly-ci.yml
@@ -76,8 +76,6 @@ jobs:
       -
         name: Create Zot certificates for OCI tests
         if: ${{ matrix.test_type == 'e2e-nosecrets' }}
-        env:
-          FLEET_E2E_NS: fleet-local
         run: |
           # Generate cert and key for TLS
           ./.github/scripts/create-zot-certs.sh "FleetCI-RootCA"
diff --git a/e2e/assets/helm/repo/http-with-auth-chart-path/fleet.yaml b/e2e/assets/helm/repo/http-with-auth-chart-path/fleet.yaml
index 3e25d344..0ec9a00f 100644
--- a/e2e/assets/helm/repo/http-with-auth-chart-path/fleet.yaml
+++ b/e2e/assets/helm/repo/http-with-auth-chart-path/fleet.yaml
@@ -9,7 +9,7 @@ helm:
   # The release name to use. If empty a generated release name will be used
   releaseName: sleeper-chart
 
-  chart: "https://chartmuseum-service.fleet-local.svc.cluster.local:8081/charts/sleeper-chart-0.1.0.tgz"
+  chart: "https://chartmuseum-service.default.svc.cluster.local:8081/charts/sleeper-chart-0.1.0.tgz"
 
   # Used if repo is set to look up the version of the chart
   version: "0.1.0"
diff --git a/e2e/assets/helm/repo/http-with-auth-repo-path/fleet.yaml b/e2e/assets/helm/repo/http-with-auth-repo-path/fleet.yaml
index 24c0786b..b1ceb508 100644
--- a/e2e/assets/helm/repo/http-with-auth-repo-path/fleet.yaml
+++ b/e2e/assets/helm/repo/http-with-auth-repo-path/fleet.yaml
@@ -10,7 +10,7 @@ helm:
   releaseName: sleeper-chart
 
   chart: "sleeper-chart"
-  repo: "https://chartmuseum-service.fleet-local.svc.cluster.local:8081"
+  repo: "https://chartmuseum-service.default.svc.cluster.local:8081"
 
   # Used if repo is set to look up the version of the chart
   version: "0.1.0"
diff --git a/e2e/assets/helm/repo/oci-with-auth/fleet.yaml b/e2e/assets/helm/repo/oci-with-auth/fleet.yaml
index c8a5d362..fea77e03 100644
--- a/e2e/assets/helm/repo/oci-with-auth/fleet.yaml
+++ b/e2e/assets/helm/repo/oci-with-auth/fleet.yaml
@@ -12,7 +12,7 @@ helm:
   # The directory of the chart in the repo.  Any valid go-getter supported
   # URL can also be used here to specify where to download the chart from.
   # If repo below is set, this value is the chart name in the repo.
-  chart: "oci://zot-service.fleet-local.svc.cluster.local:8082/sleeper-chart"
+  chart: "oci://zot-service.default.svc.cluster.local:8082/sleeper-chart"
 
   # Used if repo is set to look up the version of the chart
   version: "0.1.0"
diff --git a/e2e/require-secrets/gitrepo_test.go b/e2e/require-secrets/gitrepo_test.go
index 53ed7b5e..4214eb2f 100644
--- a/e2e/require-secrets/gitrepo_test.go
+++ b/e2e/require-secrets/gitrepo_test.go
@@ -27,52 +27,92 @@ var _ = Describe("Git Repo", func() {
 		repo    *git.Repository
 	)
 
-	replace := func(path string, s string, r string) {
-		b, err := os.ReadFile(path)
-		Expect(err).ToNot(HaveOccurred())
-
-		b = bytes.ReplaceAll(b, []byte(s), []byte(r))
-
-		err = os.WriteFile(path, b, 0644)
-		Expect(err).ToNot(HaveOccurred())
-	}
-
-	BeforeEach(func() {
-		k = env.Kubectl.Namespace(env.Namespace)
-		gh = githelper.NewSSH()
-
-		out, err := k.Create(
-			"secret", "generic", "git-auth", "--type", "kubernetes.io/ssh-auth",
-			"--from-file=ssh-privatekey="+os.Getenv("GIT_SSH_KEY"),
-			"--from-file=ssh-publickey="+os.Getenv("GIT_SSH_PUBKEY"),
-		)
-		Expect(err).ToNot(HaveOccurred(), out)
-
-		err = testenv.ApplyTemplate(k, testenv.AssetPath("gitrepo/gitrepo_with_auth.yaml"), struct {
-			Repo            string
-			Branch          string
-			PollingInterval string
-		}{
-			gh.GetURL(),
-			gh.Branch,
-			"15s", // default
+	When("using scp syntax for the gitrepo URL", func() {
+		BeforeEach(func() {
+			k = env.Kubectl.Namespace(env.Namespace)
+
+			out, err := k.Create(
+				"secret", "generic", "git-auth", "--type", "kubernetes.io/ssh-auth",
+				"--from-file=ssh-privatekey="+os.Getenv("GIT_SSH_KEY"),
+				"--from-file=ssh-publickey="+os.Getenv("GIT_SSH_PUBKEY"),
+			)
+			Expect(err).ToNot(HaveOccurred(), out)
+
+			url := os.Getenv("GIT_REPO_URL")
+			Expect(url).To(HavePrefix("git@"))
+
+			err = testenv.ApplyTemplate(k, testenv.AssetPath("gitrepo/gitrepo_with_auth.yaml"), struct {
+				Repo            string
+				Branch          string
+				PollingInterval string
+			}{
+				url,
+				"main",
+				"15s", // default
+			})
+			Expect(err).ToNot(HaveOccurred(), out)
+
+			DeferCleanup(func() {
+				_, _ = k.Delete("gitrepo", "gitrepo-test")
+				_, _ = k.Delete("secret", "git-auth")
+			})
 		})
-		Expect(err).ToNot(HaveOccurred(), out)
 
-		tmpdir, _ = os.MkdirTemp("", "fleet-")
-		repodir = path.Join(tmpdir, "repo")
-		repo, err = gh.Create(repodir, testenv.AssetPath("gitrepo/sleeper-chart"), "examples")
-		Expect(err).ToNot(HaveOccurred())
-
-	})
-
-	AfterEach(func() {
-		os.RemoveAll(tmpdir)
-		_, _ = k.Delete("secret", "git-auth")
-		_, _ = k.Delete("gitrepo", "gitrepo-test")
+		It("clones the repo", func() {
+			By("checking just the clone was attempted and successful")
+			Eventually(func() string {
+				out, _ := k.Get("gitrepo", "gitrepo-test", `-o=jsonpath={.status.conditions[?(@.type=="Stalled")].message}`)
+				return out
+			}).Should(ContainSubstring("no resource found at the following paths to deploy"))
+		})
 	})
 
 	When("updating a git repository", func() {
+		replace := func(path string, s string, r string) {
+			b, err := os.ReadFile(path)
+			Expect(err).ToNot(HaveOccurred())
+
+			b = bytes.ReplaceAll(b, []byte(s), []byte(r))
+
+			err = os.WriteFile(path, b, 0644)
+			Expect(err).ToNot(HaveOccurred())
+		}
+
+		BeforeEach(func() {
+			k = env.Kubectl.Namespace(env.Namespace)
+			gh = githelper.NewSSH()
+
+			out, err := k.Create(
+				"secret", "generic", "git-auth", "--type", "kubernetes.io/ssh-auth",
+				"--from-file=ssh-privatekey="+os.Getenv("GIT_SSH_KEY"),
+				"--from-file=ssh-publickey="+os.Getenv("GIT_SSH_PUBKEY"),
+			)
+			Expect(err).ToNot(HaveOccurred(), out)
+
+			err = testenv.ApplyTemplate(k, testenv.AssetPath("gitrepo/gitrepo_with_auth.yaml"), struct {
+				Repo            string
+				Branch          string
+				PollingInterval string
+			}{
+				gh.GetURL(),
+				gh.Branch,
+				"15s", // default
+			})
+			Expect(err).ToNot(HaveOccurred(), out)
+
+			tmpdir, _ = os.MkdirTemp("", "fleet-")
+			repodir = path.Join(tmpdir, "repo")
+			repo, err = gh.Create(repodir, testenv.AssetPath("gitrepo/sleeper-chart"), "examples")
+			Expect(err).ToNot(HaveOccurred())
+
+		})
+
+		AfterEach(func() {
+			os.RemoveAll(tmpdir)
+			_, _ = k.Delete("secret", "git-auth")
+			_, _ = k.Delete("gitrepo", "gitrepo-test")
+		})
+
 		It("updates the deployment", func() {
 			By("checking the pod exists")
 			Eventually(func() string {
diff --git a/e2e/single-cluster/gitrepo_polling_disabled_test.go b/e2e/single-cluster/gitrepo_polling_disabled_test.go
index eb6f1f91..5c2dce23 100644
--- a/e2e/single-cluster/gitrepo_polling_disabled_test.go
+++ b/e2e/single-cluster/gitrepo_polling_disabled_test.go
@@ -34,8 +34,7 @@ var _ = Describe("GitRepoPollingDisabled", Label("infra-setup"), func() {
 
 	JustBeforeEach(func() {
 		// Build git repo URL reachable _within_ the cluster, for the GitRepo
-		host, err := githelper.BuildGitHostname(env.Namespace)
-		Expect(err).ToNot(HaveOccurred())
+		host := githelper.BuildGitHostname()
 
 		addr, err := githelper.GetExternalRepoAddr(env, port, repoName)
 		Expect(err).ToNot(HaveOccurred())
diff --git a/e2e/single-cluster/gitrepo_test.go b/e2e/single-cluster/gitrepo_test.go
index e9b894d2..42035b2d 100644
--- a/e2e/single-cluster/gitrepo_test.go
+++ b/e2e/single-cluster/gitrepo_test.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/rancher/fleet/e2e/testenv"
 	"github.com/rancher/fleet/e2e/testenv/githelper"
+	"github.com/rancher/fleet/e2e/testenv/infra/cmd"
 	"github.com/rancher/fleet/e2e/testenv/kubectl"
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 	"github.com/rancher/wrangler/v3/pkg/genericcondition"
@@ -71,8 +72,7 @@ var _ = Describe("Monitoring Git repos via HTTP for change", Label("infra-setup"
 		JustBeforeEach(func() {
 
 			// Build git repo URL reachable _within_ the cluster, for the GitRepo
-			host, err := githelper.BuildGitHostname(env.Namespace)
-			Expect(err).ToNot(HaveOccurred())
+			host := githelper.BuildGitHostname()
 
 			addr, err := githelper.GetExternalRepoAddr(env, c.port, repoName)
 			Expect(err).ToNot(HaveOccurred())
@@ -237,9 +237,10 @@ var _ = Describe("Monitoring Git repos via HTTP for change", Label("infra-setup"
 				var (
 					out string
 					err error
+					kg  = k.Namespace(cmd.InfraNamespace)
 				)
 				Eventually(func() string {
-					out, err = k.Get("pod", "-l", "app=git-server", "-o", "name")
+					out, err = kg.Get("pod", "-l", "app=git-server", "-o", "name")
 					if err != nil {
 						fmt.Printf("%v\n", err)
 						return ""
@@ -260,7 +261,7 @@ var _ = Describe("Monitoring Git repos via HTTP for change", Label("infra-setup"
 				Expect(err).ToNot(HaveOccurred())
 
 				// Create a git repo, erasing a previous repo with the same name if any
-				out, err = k.Run(
+				out, err = kg.Run(
 					"exec",
 					gitServerPod,
 					"--",
@@ -277,13 +278,13 @@ var _ = Describe("Monitoring Git repos via HTTP for change", Label("infra-setup"
 				hookPathInRepo := fmt.Sprintf("/srv/git/%s/hooks/post-receive", repoName)
 
 				Eventually(func() error {
-					out, err = k.Run("cp", hookScript, fmt.Sprintf("%s:%s", gitServerPod, hookPathInRepo))
+					out, err = kg.Run("cp", hookScript, fmt.Sprintf("%s:%s", gitServerPod, hookPathInRepo))
 					return err
 				}).Should(Not(HaveOccurred()), out)
 
 				// Make hook script executable
 				Eventually(func() error {
-					out, err = k.Run("exec", gitServerPod, "--", "chmod", "+x", hookPathInRepo)
+					out, err = kg.Run("exec", gitServerPod, "--", "chmod", "+x", hookPathInRepo)
 					return err
 				}).ShouldNot(HaveOccurred(), out)
 
diff --git a/e2e/single-cluster/helm_auth_test.go b/e2e/single-cluster/helm_auth_test.go
index caff82f2..cc06dd68 100644
--- a/e2e/single-cluster/helm_auth_test.go
+++ b/e2e/single-cluster/helm_auth_test.go
@@ -25,13 +25,12 @@ var _ = Describe("GitRepo using Helm chart with auth", Label("infra-setup"), fun
 
 	JustBeforeEach(func() {
 		// Build git repo URL reachable _within_ the cluster, for the GitRepo
-		host, err := githelper.BuildGitHostname(env.Namespace)
-		Expect(err).ToNot(HaveOccurred())
+		host := githelper.BuildGitHostname()
 
 		inClusterRepoURL := gh.GetInClusterURL(host, port, repoName)
 
 		gitrepo := path.Join(tmpdir, "gitrepo.yaml")
-		err = testenv.Template(gitrepo, testenv.AssetPath("single-cluster/helm-with-auth.yaml"), struct {
+		err := testenv.Template(gitrepo, testenv.AssetPath("single-cluster/helm-with-auth.yaml"), struct {
 			Repo       string
 			Path       string
 			SecretName string
diff --git a/e2e/single-cluster/helm_dependencies_test.go b/e2e/single-cluster/helm_dependencies_test.go
index 7aaf6801..cab55ae7 100644
--- a/e2e/single-cluster/helm_dependencies_test.go
+++ b/e2e/single-cluster/helm_dependencies_test.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/rancher/fleet/e2e/testenv"
 	"github.com/rancher/fleet/e2e/testenv/githelper"
+	"github.com/rancher/fleet/e2e/testenv/infra/cmd"
 	"github.com/rancher/fleet/e2e/testenv/kubectl"
 
 	. "github.com/onsi/ginkgo/v2"
@@ -22,7 +23,7 @@ func getChartMuseumExternalAddr(env *testenv.Env) string {
 	passwd := os.Getenv("GIT_HTTP_PASSWORD")
 	Expect(username).ToNot(Equal(""))
 	Expect(passwd).ToNot(Equal(""))
-	return fmt.Sprintf("https://%s:%s@chartmuseum-service.%s.svc.cluster.local:8081", username, passwd, env.Namespace)
+	return fmt.Sprintf("https://%s:%s@chartmuseum-service.%s.svc.cluster.local:8081", username, passwd, cmd.InfraNamespace)
 }
 
 func setupChartDepsInTmpDir(chartDir string, tmpDir string, namespace string, disableDependencyUpdate bool, env *testenv.Env) {
@@ -70,8 +71,7 @@ var _ = Describe("Helm dependency update tests", Label("infra-setup", "helm-regi
 	JustBeforeEach(func() {
 		k = env.Kubectl.Namespace(env.Namespace)
 		// Build git repo URL reachable _within_ the cluster, for the GitRepo
-		host, err := githelper.BuildGitHostname(env.Namespace)
-		Expect(err).ToNot(HaveOccurred())
+		host := githelper.BuildGitHostname()
 
 		addr, err := githelper.GetExternalRepoAddr(env, port, repoName)
 		Expect(err).ToNot(HaveOccurred())
diff --git a/e2e/single-cluster/imagescan_test.go b/e2e/single-cluster/imagescan_test.go
index 795c054a..e65dbc74 100644
--- a/e2e/single-cluster/imagescan_test.go
+++ b/e2e/single-cluster/imagescan_test.go
@@ -224,8 +224,7 @@ func setupRepo(k kubectl.Command, tmpdir, clonedir, repoDir string) (*git.Reposi
 	Expect(err).ToNot(HaveOccurred())
 
 	// Build git repo URL reachable _within_ the cluster, for the GitRepo
-	host, err := githelper.BuildGitHostname(env.Namespace)
-	Expect(err).ToNot(HaveOccurred())
+	host := githelper.BuildGitHostname()
 
 	inClusterRepoURL := gh.GetInClusterURL(host, port, repoName)
 
diff --git a/e2e/single-cluster/oci_registry_test.go b/e2e/single-cluster/oci_registry_test.go
index cdc60e5b..109c1717 100644
--- a/e2e/single-cluster/oci_registry_test.go
+++ b/e2e/single-cluster/oci_registry_test.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"github.com/rancher/fleet/e2e/testenv"
+	"github.com/rancher/fleet/e2e/testenv/infra/cmd"
 	"github.com/rancher/fleet/e2e/testenv/kubectl"
 
 	. "github.com/onsi/ginkgo/v2"
@@ -129,7 +130,7 @@ var _ = Describe("Single Cluster Deployments using OCI registry", Label("oci-reg
 	BeforeEach(func() {
 		k = env.Kubectl.Namespace(env.Namespace)
 		tempDir = GinkgoT().TempDir()
-		externalIP, err := k.Get("service", "zot-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
+		externalIP, err := k.Namespace(cmd.InfraNamespace).Get("service", "zot-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
 		Expect(err).ToNot(HaveOccurred(), externalIP)
 		Expect(net.ParseIP(externalIP)).ShouldNot(BeNil())
 		ociRegistry = fmt.Sprintf("%s:8082", externalIP)
diff --git a/e2e/single-cluster/release_cleanup_test.go b/e2e/single-cluster/release_cleanup_test.go
index 45c277de..d2b8a49c 100644
--- a/e2e/single-cluster/release_cleanup_test.go
+++ b/e2e/single-cluster/release_cleanup_test.go
@@ -121,8 +121,7 @@ var _ = Describe("Monitoring Helm releases along GitRepo/bundle release name upd
 
 	JustBeforeEach(func() {
 		// Build git repo URL reachable _within_ the cluster, for the GitRepo
-		host, err := githelper.BuildGitHostname(env.Namespace)
-		Expect(err).ToNot(HaveOccurred())
+		host := githelper.BuildGitHostname()
 
 		addr, err := githelper.GetExternalRepoAddr(env, port, repoName)
 		Expect(err).ToNot(HaveOccurred())
diff --git a/e2e/single-cluster/status_test.go b/e2e/single-cluster/status_test.go
index fbc8bbf0..0d69187d 100644
--- a/e2e/single-cluster/status_test.go
+++ b/e2e/single-cluster/status_test.go
@@ -139,8 +139,7 @@ var _ = Describe("Checks that template errors are shown in bundles and gitrepos"
 
 	JustBeforeEach(func() {
 		// Build git repo URL reachable _within_ the cluster, for the GitRepo
-		host, err := githelper.BuildGitHostname(env.Namespace)
-		Expect(err).ToNot(HaveOccurred())
+		host := githelper.BuildGitHostname()
 
 		addr, err := githelper.GetExternalRepoAddr(env, port, repoName)
 		Expect(err).ToNot(HaveOccurred())
diff --git a/e2e/single-cluster/suite_test.go b/e2e/single-cluster/suite_test.go
index f8f85f43..155e0eaf 100644
--- a/e2e/single-cluster/suite_test.go
+++ b/e2e/single-cluster/suite_test.go
@@ -26,4 +26,6 @@ var _ = BeforeSuite(func() {
 	testenv.SetRoot("../..")
 
 	env = testenv.New()
+
+	Expect(env.Namespace).To(Equal("fleet-local"), "The single-cluster test assets target the default clustergroup and only work in fleet-local")
 })
diff --git a/e2e/testenv/githelper/git.go b/e2e/testenv/githelper/git.go
index 7f7394cb..b7dcb3f4 100644
--- a/e2e/testenv/githelper/git.go
+++ b/e2e/testenv/githelper/git.go
@@ -2,7 +2,6 @@ package githelper
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 	"os"
 	"os/exec"
@@ -22,6 +21,7 @@ import (
 	"github.com/go-git/go-git/v5/storage/filesystem"
 
 	"github.com/rancher/fleet/e2e/testenv"
+	"github.com/rancher/fleet/e2e/testenv/infra/cmd"
 )
 
 type gitAuth interface {
@@ -299,12 +299,8 @@ func CreateKnownHosts(path string, host string) (string, error) {
 }
 
 // BuildGitHostname builds the hostname of a cluster-local git repo from the provided namespace.
-func BuildGitHostname(ns string) (string, error) {
-	if ns == "" {
-		return "", errors.New("namespace is required")
-	}
-
-	return fmt.Sprintf("git-service.%s.svc.cluster.local", ns), nil
+func BuildGitHostname() string {
+	return fmt.Sprintf("git-service.%s.svc.cluster.local", cmd.InfraNamespace)
 }
 
 // GetExternalRepoAddr retrieves the external URL where our local git server can be reached, based on the provided port
@@ -314,11 +310,11 @@ func GetExternalRepoAddr(env *testenv.Env, port int, repoName string) (string, e
 		return fmt.Sprintf("http://%s:8080/%s", v, repoName), nil
 	}
 
-	systemk := env.Kubectl.Namespace(env.Namespace)
+	systemk := env.Kubectl.Namespace(cmd.InfraNamespace)
 
 	externalIP, err := systemk.Get("service", "git-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
 	if err != nil {
-		return "", fmt.Errorf("failed to get ingress ip for git-service in %s: %w", env.Namespace, err)
+		return "", fmt.Errorf("failed to get ingress ip for git-service in %s: %w", cmd.InfraNamespace, err)
 	}
 
 	return fmt.Sprintf("http://%s:%d/%s", externalIP, port, repoName), nil
diff --git a/e2e/testenv/infra/cmd/setup.go b/e2e/testenv/infra/cmd/setup.go
index 18018b57..a7f52db0 100644
--- a/e2e/testenv/infra/cmd/setup.go
+++ b/e2e/testenv/infra/cmd/setup.go
@@ -22,6 +22,8 @@ import (
 
 var timeoutDuration = 10 * time.Minute // default timeout duration
 
+const InfraNamespace = "default"
+
 func eventually(f func() (string, error)) string {
 	ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
 	defer cancel()
@@ -84,7 +86,7 @@ var setupCmd = &cobra.Command{
 		// enables infra setup to be run from any location within the repo
 		testenv.SetRoot(strings.TrimSpace(string(repoRoot)))
 
-		k := env.Kubectl.Namespace(env.Namespace)
+		k := env.Kubectl.Namespace(InfraNamespace)
 
 		if err := packageHelmChart(); err != nil {
 			fail(fmt.Errorf("package Helm chart: %v", err))
@@ -114,7 +116,7 @@ var setupCmd = &cobra.Command{
 				fail(fmt.Errorf("create helm-tls secret: %s with error %v", out, err))
 			}
 
-			out, err = k.Create(
+			out, err = k.Namespace(env.Namespace).Create(
 				"secret", "generic", "helm-secret",
 				"--from-literal=username="+os.Getenv("CI_OCI_USERNAME"),
 				"--from-literal=password="+os.Getenv("CI_OCI_PASSWORD"),
diff --git a/e2e/testenv/infra/cmd/teardown.go b/e2e/testenv/infra/cmd/teardown.go
index de6d12e5..3a6193b4 100644
--- a/e2e/testenv/infra/cmd/teardown.go
+++ b/e2e/testenv/infra/cmd/teardown.go
@@ -18,7 +18,7 @@ var teardownCmd = &cobra.Command{
 		fmt.Println("Tearing down test environment...")
 
 		env := testenv.New()
-		k := env.Kubectl.Namespace(env.Namespace)
+		k := env.Kubectl.Namespace(InfraNamespace)
 
 		// Only act on specified components, unless none is specified in which case all are affected.
 		if !withGitServer && !withHelmRegistry && !withOCIRegistry {
@@ -46,7 +46,7 @@ var teardownCmd = &cobra.Command{
 
 		if withHelmRegistry && withOCIRegistry {
 			_, _ = k.Delete("secret", "helm-tls")
-			_, _ = k.Delete("secret", "helm-secret")
+			_, _ = k.Namespace(env.Namespace).Delete("secret", "helm-secret")
 		}
 
 	},
diff --git a/e2e/testenv/zothelper/zothelper.go b/e2e/testenv/zothelper/zothelper.go
index a4852fe1..602024ac 100644
--- a/e2e/testenv/zothelper/zothelper.go
+++ b/e2e/testenv/zothelper/zothelper.go
@@ -4,11 +4,12 @@ import (
 	"fmt"
 	"net"
 
+	"github.com/rancher/fleet/e2e/testenv/infra/cmd"
 	"github.com/rancher/fleet/e2e/testenv/kubectl"
 )
 
 func GetOCIReference(k kubectl.Command) (string, error) {
-	externalIP, err := k.Get("service", "zot-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
+	externalIP, err := k.Namespace(cmd.InfraNamespace).Get("service", "zot-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
 	if err != nil {
 		return "", err
 	}
diff --git a/internal/cmd/cli/gitcloner/cloner_test.go b/internal/cmd/cli/gitcloner/cloner_test.go
index c268832e..8a4baadf 100644
--- a/internal/cmd/cli/gitcloner/cloner_test.go
+++ b/internal/cmd/cli/gitcloner/cloner_test.go
@@ -75,12 +75,12 @@ udiSlDctMM/X3ZM2JN5M1rtAJ2WR3ZQtmWbOjZAbG2Eq
 	}{
 		"branch no auth": {
 			opts: &GitCloner{
-				Repo:   "repo",
+				Repo:   "https://repo",
 				Path:   "path",
 				Branch: "master",
 			},
 			expectedCloneOpts: &git.CloneOptions{
-				URL:               "repo",
+				URL:               "https://repo",
 				SingleBranch:      true,
 				ReferenceName:     "master",
 				RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
@@ -88,14 +88,14 @@ udiSlDctMM/X3ZM2JN5M1rtAJ2WR3ZQtmWbOjZAbG2Eq
 		},
 		"branch basic auth": {
 			opts: &GitCloner{
-				Repo:         "repo",
+				Repo:         "https://repo",
 				Path:         "path",
 				Branch:       "master",
 				Username:     "user",
 				PasswordFile: passwordFile,
 			},
 			expectedCloneOpts: &git.CloneOptions{
-				URL:           "repo",
+				URL:           "https://repo",
 				SingleBranch:  true,
 				ReferenceName: "master",
 				Auth: &httpgit.BasicAuth{
@@ -122,31 +122,31 @@ udiSlDctMM/X3ZM2JN5M1rtAJ2WR3ZQtmWbOjZAbG2Eq
 		},
 		"password file does not exist": {
 			opts: &GitCloner{
-				Repo:         "repo",
+				Repo:         "https://repo",
 				Branch:       "master",
 				PasswordFile: "doesntexist",
 				Username:     "user",
 			},
 			expectedCloneOpts: nil,
-			expectedErr:       errors.New("failed checkout of: repo=[repo] branch=[master] revision=[] path=[]: file not found"),
+			expectedErr:       errors.New(`failed to create auth from options for repo="https://repo" branch="master" revision="" path="": file not found`),
 		},
 		"ca file does not exist": {
 			opts: &GitCloner{
-				Repo:         "repo",
+				Repo:         "https://repo",
 				Branch:       "master",
 				CABundleFile: "doesntexist",
 			},
 			expectedCloneOpts: nil,
-			expectedErr:       errors.New("failed checkout of: repo=[repo] branch=[master] revision=[] path=[]: file not found"),
+			expectedErr:       errors.New(`failed to read CA bundle from file for repo="https://repo" branch="master" revision="" path="": file not found`),
 		},
 		"ssh private key file does not exist": {
 			opts: &GitCloner{
-				Repo:              "repo",
+				Repo:              "https://repo",
 				Branch:            "master",
 				SSHPrivateKeyFile: "doesntexist",
 			},
 			expectedCloneOpts: nil,
-			expectedErr:       errors.New("failed checkout of: repo=[repo] branch=[master] revision=[] path=[]: file not found"),
+			expectedErr:       errors.New(`failed to create auth from options for repo="https://repo" branch="master" revision="" path="": file not found`),
 		},
 	}
 
diff --git a/pkg/git-urls/urls_test.go b/pkg/git-urls/urls_test.go
new file mode 100644
index 00000000..de69d462
--- /dev/null
+++ b/pkg/git-urls/urls_test.go
@@ -0,0 +1,59 @@
+package giturls_test
+
+import (
+	"testing"
+
+	giturls "github.com/rancher/fleet/pkg/git-urls"
+)
+
+func TestParse(t *testing.T) {
+	tests := []struct {
+		name string
+		url  string
+		want string
+	}{
+		{
+			name: "ssh",
+			url:  "ssh://example.com/foo/bar",
+			want: "ssh://example.com/foo/bar",
+		},
+		{
+			name: "git",
+			url:  "git://example.com/foo/bar",
+			want: "git://example.com/foo/bar",
+		},
+		{
+			name: "git+ssh",
+			url:  "git+ssh://example.com/foo/bar",
+			want: "git+ssh://example.com/foo/bar",
+		},
+		{
+			name: "http",
+			url:  "http://example.com/foo/bar",
+			want: "http://example.com/foo/bar",
+		},
+		{
+			name: "https",
+			url:  "https://example.com/foo/bar",
+
+			want: "https://example.com/foo/bar",
+		},
+		{
+			name: "scp",
+			url:  "git@example.com:foo/bar",
+			want: "ssh://git@example.com/foo/bar",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			u, err := giturls.Parse(tt.url)
+			if err != nil {
+				t.Errorf("unexpected error: %v", err)
+			}
+			if u.String() != tt.want {
+				t.Errorf("got %q, want %q", u.String(), tt.want)
+			}
+		})
+	}
+}