Repository: rancher/fleet
Pull request #1082 (2022-11-09 10:53:12)

Refactoring for Readability
This PR renames variables and changes some packages. It also adds comments to some functions.
No new functionality was added.


Patch:
diff --git a/.golangci.json b/.golangci.json
index a923484e..6455fbc9 100644
--- a/.golangci.json
+++ b/.golangci.json
@@ -27,12 +27,9 @@
     "disable-all": true,
     "enable": [
       "misspell",
-      "structcheck",
       "govet",
       "staticcheck",
-      "deadcode",
       "errcheck",
-      "varcheck",
       "unparam",
       "ineffassign",
       "nakedret",
diff --git a/cmd/fleetagent/main.go b/cmd/fleetagent/main.go
index da1654e9..ceaf4a99 100644
--- a/cmd/fleetagent/main.go
+++ b/cmd/fleetagent/main.go
@@ -31,7 +31,7 @@ type FleetAgent struct {
 
 func (a *FleetAgent) Run(cmd *cobra.Command, args []string) error {
 	go func() {
-		log.Println(http.ListenAndServe("localhost:6060", nil))
+		log.Println(http.ListenAndServe("localhost:6060", nil)) // nolint:gosec // Debugging only
 	}()
 
 	debugConfig.MustSetupDebug()
diff --git a/cmd/fleetcontroller/main.go b/cmd/fleetcontroller/main.go
index d0c1d5ab..6c77cc9a 100644
--- a/cmd/fleetcontroller/main.go
+++ b/cmd/fleetcontroller/main.go
@@ -29,7 +29,7 @@ type FleetManager struct {
 
 func (f *FleetManager) Run(cmd *cobra.Command, args []string) error {
 	go func() {
-		log.Println(http.ListenAndServe("localhost:6060", nil))
+		log.Println(http.ListenAndServe("localhost:6060", nil)) // nolint:gosec // Debugging only
 	}()
 	debugConfig.MustSetupDebug()
 	if err := fleetcontroller.Start(cmd.Context(), f.Namespace, f.Kubeconfig, f.DisableGitops); err != nil {
diff --git a/dev/build-fleet b/dev/build-fleet
index d049b92d..3a6715e5 100755
--- a/dev/build-fleet
+++ b/dev/build-fleet
@@ -8,7 +8,6 @@ if [ ! -d ./cmd/fleetcontroller ]; then
   exit 1
 fi
 
-export GOOS=linux
 export GOARCH="${GOARCH:-amd64}"
 export CGO_ENABLED=0
 
@@ -17,6 +16,7 @@ if ! git diff --quiet HEAD origin/master --  pkg/apis/fleet.cattle.io/v1alpha1;
   go generate
 fi
 
+export GOOS=linux
 # fleet
 go build -gcflags='all=-N -l' -o bin/fleetcontroller-linux-"$GOARCH" ./cmd/fleetcontroller
 docker build -f package/Dockerfile -t rancher/fleet:dev --build-arg="ARCH=$GOARCH"  .
diff --git a/modules/agent/pkg/deployer/manager.go b/modules/agent/pkg/deployer/manager.go
index fcca2626..6ae2817b 100644
--- a/modules/agent/pkg/deployer/manager.go
+++ b/modules/agent/pkg/deployer/manager.go
@@ -121,7 +121,8 @@ func (m *Manager) Resources(bd *fleet.BundleDeployment) (*helmdeployer.Resources
 	return resources, nil
 }
 
-// Deploy the bundle deployment, i.e. with helmdeployer
+// Deploy the bundle deployment, i.e. with helmdeployer.
+// This loads the manifest and the contents from the upstream cluster.
 func (m *Manager) Deploy(bd *fleet.BundleDeployment) (string, error) {
 	if bd.Spec.DeploymentID == bd.Status.AppliedDeploymentID {
 		if ok, err := m.deployer.EnsureInstalled(bd.Name, bd.Status.Release); err != nil {
diff --git a/modules/agent/pkg/register/register.go b/modules/agent/pkg/register/register.go
index 7a5cb24f..5d26fbb8 100644
--- a/modules/agent/pkg/register/register.go
+++ b/modules/agent/pkg/register/register.go
@@ -31,7 +31,6 @@ import (
 
 const (
 	CredName            = "fleet-agent"
-	BootstrapCredName   = "fleet-agent-bootstrap" // same as config.AgentBootstrapConfigName for fleet-controller
 	Kubeconfig          = "kubeconfig"
 	Token               = "token"
 	Values              = "values"
@@ -65,10 +64,10 @@ func Register(ctx context.Context, namespace, clusterID string, config *rest.Con
 
 // tryRegister makes sure the secret cattle-fleet-system/fleet-agent is
 // populated and the contained kubeconfig is working
-func tryRegister(ctx context.Context, namespace, clusterID string, config *rest.Config) (*AgentInfo, error) {
-	config = rest.CopyConfig(config)
-	config.RateLimiter = ratelimit.None
-	k8s, err := core.NewFactoryFromConfig(config)
+func tryRegister(ctx context.Context, namespace, clusterID string, cfg *rest.Config) (*AgentInfo, error) {
+	cfg = rest.CopyConfig(cfg)
+	cfg.RateLimiter = ratelimit.None
+	k8s, err := core.NewFactoryFromConfig(cfg)
 	if err != nil {
 		return nil, err
 	}
@@ -78,7 +77,7 @@ func tryRegister(ctx context.Context, namespace, clusterID string, config *rest.
 		// fallback to local cattle-fleet-system/fleet-agent-bootstrap
 		secret, err = runRegistration(ctx, k8s.Core().V1(), namespace, clusterID)
 		if err != nil {
-			return nil, fmt.Errorf("looking up secret %s/%s: %w", namespace, BootstrapCredName, err)
+			return nil, fmt.Errorf("looking up secret %s/%s: %w", namespace, config.AgentBootstrapConfigName, err)
 		}
 	} else if err != nil {
 		return nil, err
@@ -87,7 +86,7 @@ func tryRegister(ctx context.Context, namespace, clusterID string, config *rest.
 		logrus.Errorf("Current credential failed, failing back to reregistering: %v", err)
 		secret, err = runRegistration(ctx, k8s.Core().V1(), namespace, clusterID)
 		if err != nil {
-			return nil, fmt.Errorf("looking up secret %s/%s or %s/%s: %w", namespace, BootstrapCredName, namespace, CredName, err)
+			return nil, fmt.Errorf("looking up secret %s/%s or %s/%s: %w", namespace, config.AgentBootstrapConfigName, namespace, CredName, err)
 		}
 	}
 
@@ -97,7 +96,7 @@ func tryRegister(ctx context.Context, namespace, clusterID string, config *rest.
 	}
 
 	// delete the bootstrap cred
-	_ = k8s.Core().V1().Secret().Delete(namespace, BootstrapCredName, nil)
+	_ = k8s.Core().V1().Secret().Delete(namespace, config.AgentBootstrapConfigName, nil)
 	return &AgentInfo{
 		ClusterNamespace: string(secret.Data[ClusterNamespace]),
 		ClusterName:      string(secret.Data[ClusterName]),
@@ -107,9 +106,9 @@ func tryRegister(ctx context.Context, namespace, clusterID string, config *rest.
 
 func runRegistration(ctx context.Context, k8s corecontrollers.Interface, namespace, clusterID string) (*corev1.Secret, error) {
 	// read cattle-fleet-system/fleet-agent-bootstrap
-	secret, err := k8s.Secret().Get(namespace, BootstrapCredName, metav1.GetOptions{})
+	secret, err := k8s.Secret().Get(namespace, config.AgentBootstrapConfigName, metav1.GetOptions{})
 	if err != nil {
-		return nil, fmt.Errorf("looking up secret %s/%s: %w", namespace, BootstrapCredName, err)
+		return nil, fmt.Errorf("looking up secret %s/%s: %w", namespace, config.AgentBootstrapConfigName, err)
 	}
 	return createClusterSecret(ctx, clusterID, k8s, secret)
 }
diff --git a/modules/agent/pkg/simulator/simulator.go b/modules/agent/pkg/simulator/simulator.go
index 1536e994..262a2d37 100644
--- a/modules/agent/pkg/simulator/simulator.go
+++ b/modules/agent/pkg/simulator/simulator.go
@@ -103,7 +103,7 @@ func setupNamespace(ctx context.Context, kubeConfig, namespace, simNamespace str
 	clusterID := name.SafeConcatName(simNamespace, strings.SplitN(string(kubeSystem.UID), "-", 2)[0])
 
 	if _, err = k8s.CoreV1().Secrets(simNamespace).Get(ctx, register.CredName, metav1.GetOptions{}); err != nil {
-		secret, err := k8s.CoreV1().Secrets(namespace).Get(ctx, register.BootstrapCredName, metav1.GetOptions{})
+		secret, err := k8s.CoreV1().Secrets(namespace).Get(ctx, config.AgentBootstrapConfigName, metav1.GetOptions{})
 		if err != nil {
 			return "", err
 		}
diff --git a/modules/cli/apply/apply.go b/modules/cli/apply/apply.go
index 4f602652..bf5c8b8c 100644
--- a/modules/cli/apply/apply.go
+++ b/modules/cli/apply/apply.go
@@ -16,8 +16,8 @@ import (
 
 	"github.com/rancher/fleet/modules/cli/pkg/client"
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/bundle"
-	"github.com/rancher/fleet/pkg/bundleyaml"
+	"github.com/rancher/fleet/pkg/bundlereader"
+	"github.com/rancher/fleet/pkg/fleetyaml"
 
 	name2 "github.com/rancher/wrangler/pkg/name"
 	"github.com/rancher/wrangler/pkg/yaml"
@@ -47,7 +47,7 @@ type Options struct {
 	Paused          bool
 	Labels          map[string]string
 	SyncGeneration  int64
-	Auth            bundle.Auth
+	Auth            bundlereader.Auth
 }
 
 func globDirs(baseDir string) (result []string, err error) {
@@ -97,7 +97,7 @@ func Apply(ctx context.Context, client *client.Getter, repoName string, baseDirs
 					if !info.IsDir() {
 						return nil
 					}
-					if !bundleyaml.FoundFleetYamlInDirectory(path) {
+					if !fleetyaml.FoundFleetYamlInDirectory(path) {
 						return nil
 					}
 				}
@@ -155,18 +155,18 @@ func pruneBundlesNotFoundInRepo(client *client.Getter, repoName string, gitRepoB
 	return err
 }
 
-// readBundle reads bundle data from a source and return a bundle with the
+// readBundle reads bundle data from a source and returns a bundle with the
 // given name, or the name from the raw source file
-func readBundle(ctx context.Context, name, baseDir string, opts *Options) (*bundle.Bundle, error) {
+func readBundle(ctx context.Context, name, baseDir string, opts *Options) (*fleet.Bundle, []*fleet.ImageScan, error) {
 	if opts.BundleReader != nil {
-		var bundleResource fleet.Bundle
-		if err := json.NewDecoder(opts.BundleReader).Decode(&bundleResource); err != nil {
-			return nil, err
+		var bundle *fleet.Bundle
+		if err := json.NewDecoder(opts.BundleReader).Decode(bundle); err != nil {
+			return nil, nil, err
 		}
-		return bundle.New(&bundleResource)
+		return bundle, nil, nil
 	}
 
-	return bundle.Open(ctx, name, baseDir, opts.BundleFile, &bundle.Options{
+	return bundlereader.Open(ctx, name, baseDir, opts.BundleFile, &bundlereader.Options{
 		Compress:        opts.Compress,
 		Labels:          opts.Labels,
 		ServiceAccount:  opts.ServiceAccount,
@@ -228,12 +228,12 @@ func Dir(ctx context.Context, client *client.Getter, name, baseDir string, opts
 	if opts == nil {
 		opts = &Options{}
 	}
-	bundle, err := readBundle(ctx, createName(name, baseDir), baseDir, opts)
+	bundle, scans, err := readBundle(ctx, createName(name, baseDir), baseDir, opts)
 	if err != nil {
 		return err
 	}
 
-	def := bundle.Definition.DeepCopy()
+	def := bundle.DeepCopy()
 	def.Namespace = client.Namespace
 
 	if len(def.Spec.Resources) == 0 {
@@ -242,7 +242,7 @@ func Dir(ctx context.Context, client *client.Getter, name, baseDir string, opts
 	gitRepoBundlesMap[def.Name] = true
 
 	objects := []runtime.Object{def}
-	for _, scan := range bundle.Scans {
+	for _, scan := range scans {
 		objects = append(objects, scan)
 	}
 
@@ -252,7 +252,7 @@ func Dir(ctx context.Context, client *client.Getter, name, baseDir string, opts
 	}
 
 	if opts.Output == nil {
-		err = save(client, def, bundle.Scans...)
+		err = save(client, def, scans...)
 	} else {
 		_, err = opts.Output.Write(b)
 	}
diff --git a/modules/cli/match/match.go b/modules/cli/match/match.go
index 4a2103aa..8366eb2d 100644
--- a/modules/cli/match/match.go
+++ b/modules/cli/match/match.go
@@ -1,4 +1,7 @@
 // Package match is used to test matching a bundles to a target on the command line. (fleetapply)
+//
+// It's not used by fleet, but it is available in the fleet CLI as "test" sub
+// command. The tests in fleet-examples use it.
 package match
 
 import (
@@ -10,7 +13,8 @@ import (
 	"os"
 
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/bundle"
+	"github.com/rancher/fleet/pkg/bundlematcher"
+	"github.com/rancher/fleet/pkg/bundlereader"
 	"github.com/rancher/fleet/pkg/helmdeployer"
 	"github.com/rancher/fleet/pkg/manifest"
 	"github.com/rancher/fleet/pkg/options"
@@ -36,12 +40,12 @@ func Match(ctx context.Context, opts *Options) error {
 	}
 
 	var (
-		b   *bundle.Bundle
-		err error
+		bundle *fleet.Bundle
+		err    error
 	)
 
 	if opts.BundleFile == "" {
-		b, err = bundle.Open(ctx, "test", opts.BaseDir, opts.BundleSpec, nil)
+		bundle, _, err = bundlereader.Open(ctx, "test", opts.BaseDir, opts.BundleSpec, nil)
 		if err != nil {
 			return err
 		}
@@ -51,44 +55,44 @@ func Match(ctx context.Context, opts *Options) error {
 			return err
 		}
 
-		bundleConfig := &fleet.Bundle{}
-		if err := yaml.Unmarshal(data, bundleConfig); err != nil {
+		bundle = &fleet.Bundle{}
+		if err := yaml.Unmarshal(data, bundle); err != nil {
 			return err
 		}
+	}
 
-		b, err = bundle.New(bundleConfig)
-		if err != nil {
-			return err
-		}
+	bm, err := bundlematcher.New(bundle)
+	if err != nil {
+		return err
 	}
 
 	if opts.Target == "" {
-		m := b.Match(opts.ClusterName, map[string]map[string]string{
+		m := bm.Match(opts.ClusterName, map[string]map[string]string{
 			opts.ClusterGroup: opts.ClusterGroupLabels,
 		}, opts.ClusterLabels)
-		return printMatch(b, m, opts.Output)
+		return printMatch(bundle, m, opts.Output)
 	}
 
-	return printMatch(b, b.MatchForTarget(opts.Target), opts.Output)
+	return printMatch(bundle, bm.MatchForTarget(opts.Target), opts.Output)
 }
 
-func printMatch(bundle *bundle.Bundle, m *bundle.Match, output io.Writer) error {
-	if m == nil {
+func printMatch(bundle *fleet.Bundle, target *fleet.BundleTarget, output io.Writer) error {
+	if target == nil {
 		return errors.New("no match found")
 	}
-	fmt.Fprintf(os.Stderr, "# Matched: %s\n", m.Target.Name)
+	fmt.Fprintf(os.Stderr, "# Matched: %s\n", target.Name)
 	if output == nil {
 		return nil
 	}
 
-	opts := options.Calculate(&bundle.Definition.Spec, m.Target)
+	opts := options.Merge(bundle.Spec.BundleDeploymentOptions, target.BundleDeploymentOptions)
 
-	manifest, err := manifest.New(&bundle.Definition.Spec)
+	manifest, err := manifest.New(bundle.Spec.Resources)
 	if err != nil {
 		return err
 	}
 
-	objs, err := helmdeployer.Template(m.Bundle.Definition.Name, manifest, opts)
+	objs, err := helmdeployer.Template(bundle.Name, manifest, opts)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go
index f2c86368..248c31d5 100644
--- a/pkg/agent/agent.go
+++ b/pkg/agent/agent.go
@@ -147,7 +147,7 @@ func getToken(ctx context.Context, controllerNamespace, tokenName string, client
 		return nil, fmt.Errorf("failed to find token in values")
 	}
 
-	expectedNamespace := fleetns.RegistrationNamespace(controllerNamespace)
+	expectedNamespace := fleetns.SystemRegistrationNamespace(controllerNamespace)
 	actualNamespace := data["systemRegistrationNamespace"]
 	if actualNamespace != expectedNamespace {
 		return nil, fmt.Errorf("registration namespace (%s) from secret (%s/%s) does not match expected: %s", actualNamespace, secret.Namespace, secret.Name, expectedNamespace)
diff --git a/pkg/agent/config.go b/pkg/agent/config.go
index 582dcb45..dc54549c 100644
--- a/pkg/agent/config.go
+++ b/pkg/agent/config.go
@@ -4,9 +4,10 @@ import (
 	"context"
 
 	"github.com/rancher/fleet/modules/cli/pkg/client"
-	"github.com/rancher/fleet/pkg/basic"
 	"github.com/rancher/fleet/pkg/config"
 
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -44,7 +45,11 @@ func configObjects(controllerNamespace string, clusterLabels map[string]string,
 	}
 	cm.Name = "fleet-agent"
 	return []runtime.Object{
-		basic.Namespace(controllerNamespace),
+		&corev1.Namespace{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: controllerNamespace,
+			},
+		},
 		cm,
 	}, nil
 }
diff --git a/pkg/agent/manifest.go b/pkg/agent/manifest.go
index 7a6683ca..79fbefea 100644
--- a/pkg/agent/manifest.go
+++ b/pkg/agent/manifest.go
@@ -7,10 +7,10 @@ import (
 
 	"github.com/sirupsen/logrus"
 
-	"github.com/rancher/fleet/pkg/basic"
 	"github.com/rancher/fleet/pkg/config"
 	"github.com/rancher/wrangler/pkg/name"
 
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	networkv1 "k8s.io/api/networking/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
@@ -45,11 +45,11 @@ func Manifest(namespace string, agentScope string, opts ManifestOptions) []runti
 		opts.AgentImage = config.DefaultAgentImage
 	}
 
-	sa := basic.ServiceAccount(namespace, DefaultName)
+	sa := serviceAccount(namespace, DefaultName)
 
 	logrus.Debugf("Building manifest for fleet-agent in namespace %s (sa: %s)", namespace, sa.Name)
 
-	defaultSa := basic.ServiceAccount(namespace, "default")
+	defaultSa := serviceAccount(namespace, "default")
 	defaultSa.AutomountServiceAccountToken = new(bool)
 
 	clusterRole := []runtime.Object{
@@ -90,7 +90,7 @@ func Manifest(namespace string, agentScope string, opts ManifestOptions) []runti
 
 	// if debug is enabled in controller, enable in agent too
 	debug := logrus.IsLevelEnabled(logrus.DebugLevel)
-	dep := basic.Deployment(namespace, DefaultName, image, opts.AgentImagePullPolicy, DefaultName, false, debug)
+	dep := agentDeployment(namespace, DefaultName, image, opts.AgentImagePullPolicy, DefaultName, false, debug)
 	dep.Spec.Template.Spec.Containers[0].Env = append(dep.Spec.Template.Spec.Containers[0].Env,
 		corev1.EnvVar{
 			Name:  "AGENT_SCOPE",
@@ -171,3 +171,83 @@ func resolve(global, prefix, image string) string {
 
 	return image
 }
+
+func agentDeployment(namespace, name, image, imagePullPolicy, serviceAccount string, linuxOnly, debug bool) *appsv1.Deployment {
+	deployment := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      name,
+		},
+		Spec: appsv1.DeploymentSpec{
+			Selector: &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					"app": name,
+				},
+			},
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"app": name,
+					},
+				},
+				Spec: corev1.PodSpec{
+					ServiceAccountName: serviceAccount,
+					Containers: []corev1.Container{
+						{
+							Name:            name,
+							Image:           image,
+							ImagePullPolicy: corev1.PullPolicy(imagePullPolicy),
+							Env: []corev1.EnvVar{
+								{
+									Name: "NAMESPACE",
+									ValueFrom: &corev1.EnvVarSource{
+										FieldRef: &corev1.ObjectFieldSelector{
+											FieldPath: "metadata.namespace",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	if !debug {
+		for _, container := range deployment.Spec.Template.Spec.Containers {
+			container.SecurityContext = &corev1.SecurityContext{
+				AllowPrivilegeEscalation: &[]bool{false}[0],
+				ReadOnlyRootFilesystem:   &[]bool{true}[0],
+			}
+		}
+		deployment.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{
+			RunAsNonRoot: &[]bool{true}[0],
+			RunAsUser:    &[]int64{1000}[0],
+			RunAsGroup:   &[]int64{1000}[0],
+		}
+	}
+	if linuxOnly {
+		deployment.Spec.Template.Spec.NodeSelector = map[string]string{"kubernetes.io/os": "linux"}
+	}
+	deployment.Spec.Template.Spec.Tolerations = append(deployment.Spec.Template.Spec.Tolerations, corev1.Toleration{
+		Key:      "node.cloudprovider.kubernetes.io/uninitialized",
+		Operator: corev1.TolerationOpEqual,
+		Value:    "true",
+		Effect:   corev1.TaintEffectNoSchedule,
+	}, corev1.Toleration{
+		Key:      "cattle.io/os",
+		Operator: corev1.TolerationOpEqual,
+		Value:    "linux",
+		Effect:   corev1.TaintEffectNoSchedule,
+	})
+	return deployment
+}
+
+func serviceAccount(namespace, name string) *corev1.ServiceAccount {
+	return &corev1.ServiceAccount{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+	}
+}
diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/target.go b/pkg/apis/fleet.cattle.io/v1alpha1/target.go
index ba4bfb96..88ce191b 100644
--- a/pkg/apis/fleet.cattle.io/v1alpha1/target.go
+++ b/pkg/apis/fleet.cattle.io/v1alpha1/target.go
@@ -66,17 +66,24 @@ type ClusterSpec struct {
 	KubeConfigSecret        string      `json:"kubeConfigSecret,omitempty"`
 	RedeployAgentGeneration int64       `json:"redeployAgentGeneration,omitempty"`
 	AgentEnvVars            []v1.EnvVar `json:"agentEnvVars,omitempty"`
-	AgentNamespace          string      `json:"agentNamespace,omitempty"`
-	PrivateRepoURL          string      `json:"privateRepoURL,omitempty"`
+
+	// AgentNamespace defaults to the system namespace, e.g. cattle-fleet-system
+	AgentNamespace string `json:"agentNamespace,omitempty"`
+	PrivateRepoURL string `json:"privateRepoURL,omitempty"`
 }
 
 type ClusterStatus struct {
-	Conditions           []genericcondition.GenericCondition `json:"conditions,omitempty"`
-	Namespace            string                              `json:"namespace,omitempty"`
-	Summary              BundleSummary                       `json:"summary,omitempty"`
-	ResourceCounts       GitRepoResourceCounts               `json:"resourceCounts,omitempty"`
-	ReadyGitRepos        int                                 `json:"readyGitRepos"`
-	DesiredReadyGitRepos int                                 `json:"desiredReadyGitRepos"`
+	Conditions []genericcondition.GenericCondition `json:"conditions,omitempty"`
+
+	// Namespace is the cluster namespace, it contains the clusters service
+	// account as well as any bundledeployments. Example:
+	// "cluster-fleet-local-cluster-294db1acfa77-d9ccf852678f"
+	Namespace string `json:"namespace,omitempty"`
+
+	Summary              BundleSummary         `json:"summary,omitempty"`
+	ResourceCounts       GitRepoResourceCounts `json:"resourceCounts,omitempty"`
+	ReadyGitRepos        int                   `json:"readyGitRepos"`
+	DesiredReadyGitRepos int                   `json:"desiredReadyGitRepos"`
 
 	AgentEnvVarsHash        string `json:"agentEnvVarsHash,omitempty"`
 	AgentPrivateRepoURL     string `json:"agentPrivateRepoURL,omitempty"`
diff --git a/pkg/basic/objects.go b/pkg/basic/objects.go
deleted file mode 100644
index 856481c6..00000000
--- a/pkg/basic/objects.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Package basic provides basic resources, like deployments, services, etc. (fleetcontroller)
-package basic
-
-import (
-	appsv1 "k8s.io/api/apps/v1"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-func ConfigMap(namespace, name string, kvs ...string) *corev1.ConfigMap {
-	cm := &corev1.ConfigMap{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-		Data: map[string]string{},
-	}
-
-	for i := range kvs {
-		if i%2 != 0 {
-			continue
-		}
-		v := ""
-		if len(kvs) > i {
-			v = kvs[i+1]
-		}
-		cm.Data[kvs[i]] = v
-	}
-
-	return cm
-}
-
-func Namespace(name string) *corev1.Namespace {
-	return &corev1.Namespace{
-		ObjectMeta: metav1.ObjectMeta{
-			Name: name,
-		},
-	}
-
-}
-
-func Deployment(namespace, name, image, imagePullPolicy, serviceAccount string, linuxOnly, debug bool) *appsv1.Deployment {
-	deployment := &appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: namespace,
-			Name:      name,
-		},
-		Spec: appsv1.DeploymentSpec{
-			Selector: &metav1.LabelSelector{
-				MatchLabels: map[string]string{
-					"app": name,
-				},
-			},
-			Template: corev1.PodTemplateSpec{
-				ObjectMeta: metav1.ObjectMeta{
-					Labels: map[string]string{
-						"app": name,
-					},
-				},
-				Spec: corev1.PodSpec{
-					ServiceAccountName: serviceAccount,
-					Containers: []corev1.Container{
-						{
-							Name:            name,
-							Image:           image,
-							ImagePullPolicy: corev1.PullPolicy(imagePullPolicy),
-							Env: []corev1.EnvVar{
-								{
-									Name: "NAMESPACE",
-									ValueFrom: &corev1.EnvVarSource{
-										FieldRef: &corev1.ObjectFieldSelector{
-											FieldPath: "metadata.namespace",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	if !debug {
-		for _, container := range deployment.Spec.Template.Spec.Containers {
-			container.SecurityContext = &corev1.SecurityContext{
-				AllowPrivilegeEscalation: &[]bool{false}[0],
-				ReadOnlyRootFilesystem:   &[]bool{true}[0],
-			}
-		}
-		deployment.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{
-			RunAsNonRoot: &[]bool{true}[0],
-			RunAsUser:    &[]int64{1000}[0],
-			RunAsGroup:   &[]int64{1000}[0],
-		}
-	}
-	if linuxOnly {
-		deployment.Spec.Template.Spec.NodeSelector = map[string]string{"kubernetes.io/os": "linux"}
-	}
-	deployment.Spec.Template.Spec.Tolerations = append(deployment.Spec.Template.Spec.Tolerations, corev1.Toleration{
-		Key:      "node.cloudprovider.kubernetes.io/uninitialized",
-		Operator: corev1.TolerationOpEqual,
-		Value:    "true",
-		Effect:   corev1.TaintEffectNoSchedule,
-	}, corev1.Toleration{
-		Key:      "cattle.io/os",
-		Operator: corev1.TolerationOpEqual,
-		Value:    "linux",
-		Effect:   corev1.TaintEffectNoSchedule,
-	})
-	return deployment
-}
-
-func ServiceAccount(namespace, name string) *corev1.ServiceAccount {
-	return &corev1.ServiceAccount{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-	}
-}
-
diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go
deleted file mode 100644
index 880e1b87..00000000
--- a/pkg/bundle/bundle.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package bundle
-
-import (
-	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-)
-
-// Bundle struct extends the fleet.Bundle with cluster matches and ImageScan configuration
-type Bundle struct {
-	Definition *fleet.Bundle
-	Scans      []*fleet.ImageScan
-	matcher    *matcher
-}
-
-func New(bundle *fleet.Bundle, imageScan ...*fleet.ImageScan) (*Bundle, error) {
-	a := &Bundle{
-		Definition: bundle,
-		Scans:      imageScan,
-	}
-	return a, a.initMatcher()
-}
diff --git a/pkg/bundle/resources.go b/pkg/bundle/resources.go
deleted file mode 100644
index aa300b44..00000000
--- a/pkg/bundle/resources.go
+++ /dev/null
@@ -1,530 +0,0 @@
-package bundle
-
-import (
-	"context"
-	"crypto/sha256"
-	"crypto/tls"
-	"crypto/x509"
-	"encoding/base64"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"os"
-	"path/filepath"
-	"regexp"
-	"sort"
-	"strings"
-	"sync"
-	"unicode/utf8"
-
-	"github.com/hashicorp/go-getter"
-	"github.com/pkg/errors"
-	"golang.org/x/sync/errgroup"
-	"golang.org/x/sync/semaphore"
-	"helm.sh/helm/v3/pkg/cli"
-	"helm.sh/helm/v3/pkg/downloader"
-	helmgetter "helm.sh/helm/v3/pkg/getter"
-	"helm.sh/helm/v3/pkg/registry"
-	"helm.sh/helm/v3/pkg/repo"
-
-	"github.com/rancher/fleet/modules/cli/pkg/progress"
-	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/content"
-
-	"github.com/rancher/wrangler/pkg/data"
-
-	"sigs.k8s.io/yaml"
-)
-
-var hasOCIURL = regexp.MustCompile(`^oci:\/\/`)
-
-func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, base string, auth Auth) ([]fleet.BundleResource, error) {
-	var directories []directory
-
-	directories, err := addDirectory(directories, base, ".", ".")
-	if err != nil {
-		return nil, err
-	}
-
-	var chartDirs []*fleet.HelmOptions
-
-	if spec.Helm != nil && spec.Helm.Chart != "" {
-		if err := parseValueFiles(base, spec.Helm); err != nil {
-			return nil, err
-		}
-		chartDirs = append(chartDirs, spec.Helm)
-	}
-
-	for _, target := range spec.Targets {
-		if target.Helm != nil {
-			err := parseValueFiles(base, target.Helm)
-			if err != nil {
-				return nil, err
-			}
-			if target.Helm.Chart != "" {
-				chartDirs = append(chartDirs, target.Helm)
-			}
-		}
-	}
-
-	directories, err = addCharts(directories, base, chartDirs, auth)
-	if err != nil {
-		return nil, err
-	}
-
-	resources, err := readDirectories(ctx, compress, directories...)
-	if err != nil {
-		return nil, err
-	}
-
-	var result []fleet.BundleResource
-	for _, resources := range resources {
-		result = append(result, resources...)
-	}
-
-	sort.Slice(result, func(i, j int) bool {
-		return result[i].Name < result[j].Name
-	})
-
-	return result, nil
-}
-
-func checksum(helm *fleet.HelmOptions) string {
-	if helm == nil {
-		return "none"
-	}
-	return fmt.Sprintf(".chart/%x", sha256.Sum256([]byte(helm.Chart + ":" + helm.Repo + ":" + helm.Version)[:]))
-}
-
-type Auth struct {
-	Username      string
-	Password      string
-	CABundle      []byte
-	SSHPrivateKey []byte
-}
-
-func chartURL(location *fleet.HelmOptions, auth Auth) (string, error) {
-	// repos are not supported in case of OCI Charts
-	if hasOCIURL.MatchString(location.Chart) {
-		return location.Chart, nil
-	}
-
-	if location.Repo == "" {
-		return location.Chart, nil
-	}
-
-	if !strings.HasSuffix(location.Repo, "/") {
-		location.Repo = location.Repo + "/"
-	}
-
-	request, err := http.NewRequest("GET", location.Repo+"index.yaml", nil)
-	if err != nil {
-		return "", err
-	}
-
-	if auth.Username != "" && auth.Password != "" {
-		request.SetBasicAuth(auth.Username, auth.Password)
-	}
-	client := &http.Client{}
-	if auth.CABundle != nil {
-		pool, err := x509.SystemCertPool()
-		if err != nil {
-			pool = x509.NewCertPool()
-		}
-		pool.AppendCertsFromPEM(auth.CABundle)
-		transport := http.DefaultTransport.(*http.Transport).Clone()
-		transport.TLSClientConfig = &tls.Config{
-			RootCAs:    pool,
-			MinVersion: tls.VersionTLS12,
-		}
-		client.Transport = transport
-	}
-
-	resp, err := client.Do(request)
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-
-	bytes, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "", err
-	}
-
-	if resp.StatusCode != 200 {
-		return "", fmt.Errorf("failed to read helm repo from %s, error code: %v, response body: %s", location.Repo+"index.yaml", resp.StatusCode, bytes)
-	}
-
-	repo := &repo.IndexFile{}
-	if err := yaml.Unmarshal(bytes, repo); err != nil {
-		return "", err
-	}
-
-	repo.SortEntries()
-
-	chart, err := repo.Get(location.Chart, location.Version)
-	if err != nil {
-		return "", err
-	}
-
-	if len(chart.URLs) == 0 {
-		return "", fmt.Errorf("no URLs found for chart %s %s at %s", chart.Name, chart.Version, location.Repo)
-	}
-
-	chartURL, err := url.Parse(chart.URLs[0])
-	if err != nil {
-		return "", err
-	}
-
-	if chartURL.IsAbs() {
-		return chart.URLs[0], nil
-	}
-
-	repoURL, err := url.Parse(location.Repo)
-	if err != nil {
-		return "", err
-	}
-
-	return repoURL.ResolveReference(chartURL).String(), nil
-}
-
-func addCharts(directories []directory, base string, charts []*fleet.HelmOptions, auth Auth) ([]directory, error) {
-	for _, chart := range charts {
-		if _, err := os.Stat(filepath.Join(base, chart.Chart)); os.IsNotExist(err) || chart.Repo != "" {
-			chartURL, err := chartURL(chart, auth)
-			if err != nil {
-				return nil, err
-			}
-
-			directories = append(directories, directory{
-				prefix:  checksum(chart),
-				base:    base,
-				path:    chartURL,
-				key:     checksum(chart),
-				auth:    auth,
-				version: chart.Version,
-			})
-		}
-	}
-	return directories, nil
-}
-
-func addDirectory(directories []directory, base, customDir, defaultDir string) ([]directory, error) {
-	if customDir == "" {
-		if _, err := os.Stat(filepath.Join(base, defaultDir)); os.IsNotExist(err) {
-			return directories, nil
-		} else if err != nil {
-			return directories, err
-		}
-		customDir = defaultDir
-	}
-
-	return append(directories, directory{
-		prefix: defaultDir,
-		base:   base,
-		path:   customDir,
-		key:    defaultDir,
-	}), nil
-}
-
-type directory struct {
-	prefix  string
-	base    string
-	path    string
-	key     string
-	version string
-	auth    Auth
-}
-
-func readDirectories(ctx context.Context, compress bool, directories ...directory) (map[string][]fleet.BundleResource, error) {
-	var (
-		sem    = semaphore.NewWeighted(4)
-		result = map[string][]fleet.BundleResource{}
-		l      = sync.Mutex{}
-		p      = progress.NewProgress()
-	)
-	defer p.Close()
-
-	eg, ctx := errgroup.WithContext(ctx)
-
-	for _, dir := range directories {
-		if err := sem.Acquire(ctx, 1); err != nil {
-			return nil, err
-		}
-		dir := dir
-		eg.Go(func() error {
-			defer sem.Release(1)
-			resources, err := readDirectory(ctx, compress, dir.prefix, dir.base, dir.path, dir.version, dir.auth)
-			if err != nil {
-				return err
-			}
-
-			key := dir.key
-			if key == "" {
-				key = dir.path
-			}
-
-			l.Lock()
-			result[key] = resources
-			l.Unlock()
-			return nil
-		})
-	}
-
-	return result, eg.Wait()
-}
-
-func readDirectory(ctx context.Context, compress bool, prefix, base, name, version string, auth Auth) ([]fleet.BundleResource, error) {
-	var resources []fleet.BundleResource
-
-	files, err := readContent(ctx, base, name, version, auth)
-	if err != nil {
-		return nil, err
-	}
-
-	for k := range files {
-		resources = append(resources, fleet.BundleResource{
-			Name: k,
-		})
-	}
-
-	for i, resource := range resources {
-		data := files[resource.Name]
-		if compress || !utf8.Valid(data) {
-			content, err := content.Base64GZ(files[resource.Name])
-			if err != nil {
-				return nil, err
-			}
-			resources[i].Content = content
-			resources[i].Encoding = "base64+gz"
-		} else {
-			resources[i].Content = string(data)
-		}
-		if prefix != "" {
-			resources[i].Name = filepath.Join(prefix, resources[i].Name)
-		}
-	}
-
-	return resources, nil
-}
-
-func readContent(ctx context.Context, base, name, version string, auth Auth) (map[string][]byte, error) {
-	temp, err := os.MkdirTemp("", "fleet")
-	if err != nil {
-		return nil, err
-	}
-	defer os.RemoveAll(temp)
-
-	src := name
-
-	// go-getter does not support downloading OCI registry based files yet
-	// until this is implemented we use Helm to download charts from OCI based registries
-	// and provide the downloaded file to go-getter locally
-	if hasOCIURL.MatchString(name) {
-		src, err = downloadOCIChart(name, version, temp, auth)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	temp = filepath.Join(temp, "content")
-
-	base, err = filepath.Abs(base)
-	if err != nil {
-		return nil, err
-	}
-
-	if auth.SSHPrivateKey != nil {
-		if !strings.ContainsAny(src, "?") {
-			src += "?"
-		} else {
-			src += "&"
-		}
-		src += fmt.Sprintf("sshkey=%s", base64.StdEncoding.EncodeToString(auth.SSHPrivateKey))
-	}
-
-	// copy getter.Getters before changing
-	getters := map[string]getter.Getter{}
-	for k, v := range getter.Getters {
-		getters[k] = v
-	}
-
-	httpGetter := newHttpGetter(auth)
-	getters["http"] = httpGetter
-	getters["https"] = httpGetter
-
-	c := getter.Client{
-		Ctx:     ctx,
-		Src:     src,
-		Dst:     temp,
-		Pwd:     base,
-		Mode:    getter.ClientModeDir,
-		Getters: getters,
-		// TODO: why doesn't this work anymore
-		//ProgressListener: progress,
-	}
-
-	if err := c.Get(); err != nil {
-		return nil, err
-	}
-
-	files := map[string][]byte{}
-
-	// dereference link if possible
-	if dest, err := os.Readlink(temp); err == nil {
-		temp = dest
-	}
-
-	err = filepath.Walk(temp, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		if info.IsDir() {
-			if strings.HasPrefix(filepath.Base(path), ".") {
-				return filepath.SkipDir
-			}
-			return nil
-		}
-
-		name, err := filepath.Rel(temp, path)
-		if err != nil {
-			return err
-		}
-
-		if strings.HasPrefix(filepath.Base(name), ".") {
-			return nil
-		}
-
-		content, err := os.ReadFile(path)
-		if err != nil {
-			return err
-		}
-
-		files[name] = content
-		return nil
-	})
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to read %s relative to %s", name, base)
-	}
-
-	return files, nil
-}
-
-// downloadOciChart uses Helm to download charts from OCI based registries
-func downloadOCIChart(name, version, path string, auth Auth) (string, error) {
-	var registryClient *registry.Client
-	var requiresLogin bool = auth.Username != "" && auth.Password != ""
-
-	c := downloader.ChartDownloader{
-		Verify:  downloader.VerifyNever,
-		Getters: helmgetter.All(&cli.EnvSettings{}),
-	}
-	url, err := url.Parse(name)
-	if err != nil {
-		return "", err
-	}
-
-	// Helm does not support direct authentication for private OCI regstries when a chart is downloaded
-	// so it is necessary to login before via Helm which stores the registry token in a configuration
-	// file on the system
-	if requiresLogin {
-		registryClient, err = registry.NewClient()
-		if err != nil {
-			return "", err
-		}
-		err = registryClient.Login(url.Hostname(), registry.LoginOptInsecure(false), registry.LoginOptBasicAuth(auth.Username, auth.Password))
-		if err != nil {
-			return "", err
-		}
-	}
-
-	saved, _, err := c.DownloadTo(name, version, path)
-	if err != nil {
-		return "", err
-	}
-
-	// Logout to remove the token configuration file from the system again
-	if requiresLogin {
-		err = registryClient.Logout(url.Hostname())
-		if err != nil {
-			return "", err
-		}
-	}
-
-	return saved, nil
-}
-
-func newHttpGetter(auth Auth) *getter.HttpGetter {
-	httpGetter := &getter.HttpGetter{
-		Client: &http.Client{},
-	}
-
-	if auth.Username != "" && auth.Password != "" {
-		header := http.Header{}
-		header.Add("Authorization", "Basic "+basicAuth(auth.Username, auth.Password))
-		httpGetter.Header = header
-	}
-	if auth.CABundle != nil {
-		pool, err := x509.SystemCertPool()
-		if err != nil {
-			pool = x509.NewCertPool()
-		}
-		pool.AppendCertsFromPEM(auth.CABundle)
-		transport := http.DefaultTransport.(*http.Transport).Clone()
-		transport.TLSClientConfig = &tls.Config{
-			RootCAs:    pool,
-			MinVersion: tls.VersionTLS12,
-		}
-		httpGetter.Client.Transport = transport
-	}
-	return httpGetter
-}
-
-func parseValueFiles(base string, chart *fleet.HelmOptions) (err error) {
-	if len(chart.ValuesFiles) != 0 {
-		valuesMap, err := generateValues(base, chart)
-		if err != nil {
-			return err
-		}
-
-		if len(valuesMap.Data) != 0 {
-			chart.Values = valuesMap
-		}
-	}
-
-	return nil
-}
-
-func generateValues(base string, chart *fleet.HelmOptions) (valuesMap *fleet.GenericMap, err error) {
-	valuesMap = &fleet.GenericMap{}
-	if chart.Values != nil {
-		valuesMap = chart.Values
-	}
-	for _, value := range chart.ValuesFiles {
-		valuesByte, err := os.ReadFile(base + "/" + value)
-		if err != nil {
-			return nil, err
-		}
-		tmpDataOpt := &fleet.GenericMap{}
-		err = yaml.Unmarshal(valuesByte, tmpDataOpt)
-		if err != nil {
-			return nil, err
-		}
-		valuesMap = mergeGenericMap(valuesMap, tmpDataOpt)
-	}
-
-	return valuesMap, nil
-}
-
-func mergeGenericMap(first, second *fleet.GenericMap) *fleet.GenericMap {
-	result := &fleet.GenericMap{Data: make(map[string]interface{})}
-	result.Data = data.MergeMaps(first.Data, second.Data)
-	return result
-}
-
-func basicAuth(username, password string) string {
-	auth := username + ":" + password
-	return base64.StdEncoding.EncodeToString([]byte(auth))
-}
diff --git a/pkg/bundle/match.go b/pkg/bundlematcher/match.go
similarity index 66%
rename from pkg/bundle/match.go
rename to pkg/bundlematcher/match.go
index 5043d27e..05247eba 100644
--- a/pkg/bundle/match.go
+++ b/pkg/bundlematcher/match.go
@@ -1,29 +1,34 @@
-package bundle
+package bundlematcher
 
 import (
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 	"github.com/rancher/fleet/pkg/match"
 )
 
-type Match struct {
-	Target *fleet.BundleTarget
-	Bundle *Bundle
+// BundleMatch stores the bundle and the matcher for the bundle
+type BundleMatch struct {
+	bundle  *fleet.Bundle
+	matcher *matcher
 }
 
-func (a *Bundle) MatchForTarget(name string) *Match {
-	for i, target := range a.Definition.Spec.Targets {
+func New(bundle *fleet.Bundle) (*BundleMatch, error) {
+	bm := &BundleMatch{
+		bundle: bundle,
+	}
+	return bm, bm.initMatcher()
+}
+
+func (a *BundleMatch) MatchForTarget(name string) *fleet.BundleTarget {
+	for i, target := range a.bundle.Spec.Targets {
 		if target.Name != name {
 			continue
 		}
-		return &Match{
-			Target: &a.Definition.Spec.Targets[i],
-			Bundle: a,
-		}
+		return &a.bundle.Spec.Targets[i]
 	}
 	return nil
 }
 
-func (a *Bundle) Match(clusterName string, clusterGroups map[string]map[string]string, clusterLabels map[string]string) *Match {
+func (a *BundleMatch) Match(clusterName string, clusterGroups map[string]map[string]string, clusterLabels map[string]string) *fleet.BundleTarget {
 	for clusterGroup, clusterGroupLabels := range clusterGroups {
 		if m := a.matcher.Match(clusterName, clusterGroup, clusterGroupLabels, clusterLabels); m != nil {
 			return m
@@ -36,7 +41,7 @@ func (a *Bundle) Match(clusterName string, clusterGroups map[string]map[string]s
 }
 
 type targetMatch struct {
-	targetBundle *Match
+	bundleTarget *fleet.BundleTarget
 	criteria     *match.ClusterMatcher
 }
 
@@ -45,28 +50,25 @@ type matcher struct {
 	restrictions []*match.ClusterMatcher
 }
 
-func (a *Bundle) initMatcher() error {
+func (a *BundleMatch) initMatcher() error {
 	var (
 		m = &matcher{}
 	)
 
-	for i, target := range a.Definition.Spec.Targets {
+	for i, target := range a.bundle.Spec.Targets {
 		clusterMatcher, err := match.NewClusterMatcher(target.ClusterName, target.ClusterGroup, target.ClusterGroupSelector, target.ClusterSelector)
 		if err != nil {
 			return err
 		}
 		t := targetMatch{
-			targetBundle: &Match{
-				Target: &a.Definition.Spec.Targets[i],
-				Bundle: a,
-			},
-			criteria: clusterMatcher,
+			bundleTarget: &a.bundle.Spec.Targets[i],
+			criteria:     clusterMatcher,
 		}
 
 		m.matches = append(m.matches, t)
 	}
 
-	for _, target := range a.Definition.Spec.TargetRestrictions {
+	for _, target := range a.bundle.Spec.TargetRestrictions {
 		clusterMatcher, err := match.NewClusterMatcher(target.ClusterName, target.ClusterGroup, target.ClusterGroupSelector, target.ClusterSelector)
 		if err != nil {
 			return err
@@ -92,14 +94,14 @@ func (m *matcher) isRestricted(clusterName, clusterGroup string, clusterGroupLab
 	return true
 }
 
-func (m *matcher) Match(clusterName, clusterGroup string, clusterGroupLabels, clusterLabels map[string]string) *Match {
+func (m *matcher) Match(clusterName, clusterGroup string, clusterGroupLabels, clusterLabels map[string]string) *fleet.BundleTarget {
 	if m.isRestricted(clusterName, clusterGroup, clusterGroupLabels, clusterLabels) {
 		return nil
 	}
 
 	for _, targetMatch := range m.matches {
 		if targetMatch.criteria.Match(clusterName, clusterGroup, clusterGroupLabels, clusterLabels) {
-			return targetMatch.targetBundle
+			return targetMatch.bundleTarget
 		}
 	}
 
diff --git a/pkg/bundlereader/charturl.go b/pkg/bundlereader/charturl.go
new file mode 100644
index 00000000..b01982a7
--- /dev/null
+++ b/pkg/bundlereader/charturl.go
@@ -0,0 +1,102 @@
+package bundlereader
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
+	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+	"helm.sh/helm/v3/pkg/repo"
+	"sigs.k8s.io/yaml"
+)
+
+// chartURL returns the URL to the helm chart from a helm repo server, by
+// inspecting the repo's index.yaml
+func chartURL(location *fleet.HelmOptions, auth Auth) (string, error) {
+	// repos are not supported in case of OCI Charts
+	if hasOCIURL.MatchString(location.Chart) {
+		return location.Chart, nil
+	}
+
+	if location.Repo == "" {
+		return location.Chart, nil
+	}
+
+	if !strings.HasSuffix(location.Repo, "/") {
+		location.Repo = location.Repo + "/"
+	}
+
+	request, err := http.NewRequest("GET", location.Repo+"index.yaml", nil)
+	if err != nil {
+		return "", err
+	}
+
+	if auth.Username != "" && auth.Password != "" {
+		request.SetBasicAuth(auth.Username, auth.Password)
+	}
+	client := &http.Client{}
+	if auth.CABundle != nil {
+		pool, err := x509.SystemCertPool()
+		if err != nil {
+			pool = x509.NewCertPool()
+		}
+		pool.AppendCertsFromPEM(auth.CABundle)
+		transport := http.DefaultTransport.(*http.Transport).Clone()
+		transport.TLSClientConfig = &tls.Config{
+			RootCAs:    pool,
+			MinVersion: tls.VersionTLS12,
+		}
+		client.Transport = transport
+	}
+
+	resp, err := client.Do(request)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	bytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+
+	if resp.StatusCode != 200 {
+		return "", fmt.Errorf("failed to read helm repo from %s, error code: %v, response body: %s", location.Repo+"index.yaml", resp.StatusCode, bytes)
+	}
+
+	repo := &repo.IndexFile{}
+	if err := yaml.Unmarshal(bytes, repo); err != nil {
+		return "", err
+	}
+
+	repo.SortEntries()
+
+	chart, err := repo.Get(location.Chart, location.Version)
+	if err != nil {
+		return "", err
+	}
+
+	if len(chart.URLs) == 0 {
+		return "", fmt.Errorf("no URLs found for chart %s %s at %s", chart.Name, chart.Version, location.Repo)
+	}
+
+	chartURL, err := url.Parse(chart.URLs[0])
+	if err != nil {
+		return "", err
+	}
+
+	if chartURL.IsAbs() {
+		return chart.URLs[0], nil
+	}
+
+	repoURL, err := url.Parse(location.Repo)
+	if err != nil {
+		return "", err
+	}
+
+	return repoURL.ResolveReference(chartURL).String(), nil
+}
diff --git a/pkg/bundlereader/loaddirectory.go b/pkg/bundlereader/loaddirectory.go
new file mode 100644
index 00000000..2ca9d96b
--- /dev/null
+++ b/pkg/bundlereader/loaddirectory.go
@@ -0,0 +1,237 @@
+package bundlereader
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/base64"
+	"fmt"
+	"net/http"
+	"net/url"
+	"os"
+	"path/filepath"
+	"strings"
+	"unicode/utf8"
+
+	"github.com/hashicorp/go-getter"
+	"github.com/pkg/errors"
+	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+	"github.com/rancher/fleet/pkg/content"
+	"helm.sh/helm/v3/pkg/cli"
+	"helm.sh/helm/v3/pkg/downloader"
+	helmgetter "helm.sh/helm/v3/pkg/getter"
+	"helm.sh/helm/v3/pkg/registry"
+)
+
+func loadDirectory(ctx context.Context, compress bool, prefix, base, source, version string, auth Auth) ([]fleet.BundleResource, error) {
+	var resources []fleet.BundleResource
+
+	files, err := getContent(ctx, base, source, version, auth)
+	if err != nil {
+		return nil, err
+	}
+
+	for k := range files {
+		resources = append(resources, fleet.BundleResource{
+			Name: k,
+		})
+	}
+
+	for i, resource := range resources {
+		data := files[resource.Name]
+		if compress || !utf8.Valid(data) {
+			content, err := content.Base64GZ(files[resource.Name])
+			if err != nil {
+				return nil, err
+			}
+			resources[i].Content = content
+			resources[i].Encoding = "base64+gz"
+		} else {
+			resources[i].Content = string(data)
+		}
+		if prefix != "" {
+			resources[i].Name = filepath.Join(prefix, resources[i].Name)
+		}
+	}
+
+	return resources, nil
+}
+
+// getContent uses go-getter (and helm for oci) to read the files from directories and servers
+func getContent(ctx context.Context, base, source, version string, auth Auth) (map[string][]byte, error) {
+	temp, err := os.MkdirTemp("", "fleet")
+	if err != nil {
+		return nil, err
+	}
+	defer os.RemoveAll(temp)
+
+	orgSource := source
+
+	// go-getter does not support downloading OCI registry based files yet
+	// until this is implemented we use Helm to download charts from OCI based registries
+	// and provide the downloaded file to go-getter locally
+	if hasOCIURL.MatchString(source) {
+		source, err = downloadOCIChart(source, version, temp, auth)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	temp = filepath.Join(temp, "content")
+
+	base, err = filepath.Abs(base)
+	if err != nil {
+		return nil, err
+	}
+
+	if auth.SSHPrivateKey != nil {
+		if !strings.ContainsAny(source, "?") {
+			source += "?"
+		} else {
+			source += "&"
+		}
+		source += fmt.Sprintf("sshkey=%s", base64.StdEncoding.EncodeToString(auth.SSHPrivateKey))
+	}
+
+	// copy getter.Getters before changing
+	getters := map[string]getter.Getter{}
+	for k, v := range getter.Getters {
+		getters[k] = v
+	}
+
+	httpGetter := newHttpGetter(auth)
+	getters["http"] = httpGetter
+	getters["https"] = httpGetter
+
+	c := getter.Client{
+		Ctx:     ctx,
+		Src:     source,
+		Dst:     temp,
+		Pwd:     base,
+		Mode:    getter.ClientModeDir,
+		Getters: getters,
+		// TODO: why doesn't this work anymore
+		//ProgressListener: progress,
+	}
+
+	if err := c.Get(); err != nil {
+		return nil, err
+	}
+
+	files := map[string][]byte{}
+
+	// dereference link if possible
+	if dest, err := os.Readlink(temp); err == nil {
+		temp = dest
+	}
+
+	err = filepath.Walk(temp, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			if strings.HasPrefix(filepath.Base(path), ".") {
+				return filepath.SkipDir
+			}
+			return nil
+		}
+
+		name, err := filepath.Rel(temp, path)
+		if err != nil {
+			return err
+		}
+
+		if strings.HasPrefix(filepath.Base(name), ".") {
+			return nil
+		}
+
+		content, err := os.ReadFile(path)
+		if err != nil {
+			return err
+		}
+
+		files[name] = content
+		return nil
+	})
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to read %s relative to %s", orgSource, base)
+	}
+
+	return files, nil
+}
+
+// downloadOciChart uses Helm to download charts from OCI based registries
+func downloadOCIChart(name, version, path string, auth Auth) (string, error) {
+	var registryClient *registry.Client
+	var requiresLogin bool = auth.Username != "" && auth.Password != ""
+
+	c := downloader.ChartDownloader{
+		Verify:  downloader.VerifyNever,
+		Getters: helmgetter.All(&cli.EnvSettings{}),
+	}
+	url, err := url.Parse(name)
+	if err != nil {
+		return "", err
+	}
+
+	// Helm does not support direct authentication for private OCI regstries when a chart is downloaded
+	// so it is necessary to login before via Helm which stores the registry token in a configuration
+	// file on the system
+	if requiresLogin {
+		registryClient, err = registry.NewClient()
+		if err != nil {
+			return "", err
+		}
+		err = registryClient.Login(url.Hostname(), registry.LoginOptInsecure(false), registry.LoginOptBasicAuth(auth.Username, auth.Password))
+		if err != nil {
+			return "", err
+		}
+	}
+
+	saved, _, err := c.DownloadTo(name, version, path)
+	if err != nil {
+		return "", err
+	}
+
+	// Logout to remove the token configuration file from the system again
+	if requiresLogin {
+		err = registryClient.Logout(url.Hostname())
+		if err != nil {
+			return "", err
+		}
+	}
+
+	return saved, nil
+}
+
+func newHttpGetter(auth Auth) *getter.HttpGetter {
+	httpGetter := &getter.HttpGetter{
+		Client: &http.Client{},
+	}
+
+	if auth.Username != "" && auth.Password != "" {
+		header := http.Header{}
+		header.Add("Authorization", "Basic "+basicAuth(auth.Username, auth.Password))
+		httpGetter.Header = header
+	}
+	if auth.CABundle != nil {
+		pool, err := x509.SystemCertPool()
+		if err != nil {
+			pool = x509.NewCertPool()
+		}
+		pool.AppendCertsFromPEM(auth.CABundle)
+		transport := http.DefaultTransport.(*http.Transport).Clone()
+		transport.TLSClientConfig = &tls.Config{
+			RootCAs:    pool,
+			MinVersion: tls.VersionTLS12,
+		}
+		httpGetter.Client.Transport = transport
+	}
+	return httpGetter
+}
+
+func basicAuth(username, password string) string {
+	auth := username + ":" + password
+	return base64.StdEncoding.EncodeToString([]byte(auth))
+}
diff --git a/pkg/bundle/read.go b/pkg/bundlereader/read.go
similarity index 65%
rename from pkg/bundle/read.go
rename to pkg/bundlereader/read.go
index 8b1334d3..31d024ab 100644
--- a/pkg/bundle/read.go
+++ b/pkg/bundlereader/read.go
@@ -1,4 +1,6 @@
-package bundle
+// Package bundlereader creates a bundle from a source and adds all the
+// referenced resources, as well as image scans.
+package bundlereader
 
 import (
 	"bytes"
@@ -12,7 +14,7 @@ import (
 	"strconv"
 
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/bundleyaml"
+	"github.com/rancher/fleet/pkg/fleetyaml"
 
 	name1 "github.com/rancher/wrangler/pkg/name"
 
@@ -31,9 +33,10 @@ type Options struct {
 	Auth            Auth
 }
 
-// Open reads the content, from stdin, or basedir, or a file in basedir. It
-// returns a bundle with the given name
-func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*Bundle, error) {
+// Open reads the fleet.yaml, from stdin, or basedir, or a file in basedir.
+// Then it reads/downloads all referenced resources. It returns the populated
+// bundle and any existing imagescans.
+func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*fleet.Bundle, []*fleet.ImageScan, error) {
 	if baseDir == "" {
 		baseDir = "."
 	}
@@ -48,7 +51,7 @@ func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*Bund
 
 	if file == "" {
 		if file, err := setupIOReader(baseDir); err != nil {
-			return nil, err
+			return nil, nil, err
 		} else if file != nil {
 			in = file
 			defer file.Close()
@@ -59,7 +62,7 @@ func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*Bund
 	} else {
 		f, err := os.Open(filepath.Join(baseDir, file))
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		defer f.Close()
 		in = f
@@ -72,14 +75,14 @@ func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*Bund
 // try the fallback extension. If we receive "IsNotExist" errors for both file extensions, then we return a "nil" file
 // and a "nil" error. If either return a non-"IsNotExist" error, then we return the error immediately.
 func setupIOReader(baseDir string) (*os.File, error) {
-	if file, err := os.Open(bundleyaml.GetFleetYamlPath(baseDir, false)); err != nil && !os.IsNotExist(err) {
+	if file, err := os.Open(fleetyaml.GetFleetYamlPath(baseDir, false)); err != nil && !os.IsNotExist(err) {
 		return nil, err
 	} else if err == nil {
 		// File must be closed in the parent function.
 		return file, nil
 	}
 
-	if file, err := os.Open(bundleyaml.GetFleetYamlPath(baseDir, true)); err != nil && !os.IsNotExist(err) {
+	if file, err := os.Open(fleetyaml.GetFleetYamlPath(baseDir, true)); err != nil && !os.IsNotExist(err) {
 		return nil, err
 	} else if err == nil {
 		// File must be closed in the parent function.
@@ -89,25 +92,25 @@ func setupIOReader(baseDir string) (*os.File, error) {
 	return nil, nil
 }
 
-func mayCompress(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader, opts *Options) (*Bundle, error) {
+func mayCompress(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader, opts *Options) (*fleet.Bundle, []*fleet.ImageScan, error) {
 	if opts == nil {
 		opts = &Options{}
 	}
 
 	data, err := io.ReadAll(bundleSpecReader)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	bundle, err := read(ctx, name, baseDir, bytes.NewBuffer(data), opts)
+	bundle, scans, err := read(ctx, name, baseDir, bytes.NewBuffer(data), opts)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	if size, err := size(bundle.Definition); err != nil {
-		return nil, err
+	if size, err := size(bundle); err != nil {
+		return nil, nil, err
 	} else if size < 1000000 {
-		return bundle, nil
+		return bundle, scans, nil
 	}
 
 	newOpts := *opts
@@ -123,7 +126,7 @@ func size(bundle *fleet.Bundle) (int, error) {
 	return len(marshalled), nil
 }
 
-type localSpec struct {
+type fleetYAML struct {
 	Name   string            `json:"name,omitempty"`
 	Labels map[string]string `json:"labels,omitempty"`
 	fleet.BundleSpec
@@ -136,7 +139,8 @@ type imageScan struct {
 	fleet.ImageScanSpec
 }
 
-func read(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader, opts *Options) (*Bundle, error) {
+// read reads the fleet.yaml from the bundleSpecReader and loads all resources
+func read(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader, opts *Options) (*fleet.Bundle, []*fleet.ImageScan, error) {
 	if opts == nil {
 		opts = &Options{}
 	}
@@ -147,21 +151,21 @@ func read(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader,
 
 	bytes, err := io.ReadAll(bundleSpecReader)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	bundle := &localSpec{}
-	if err := yaml.Unmarshal(bytes, bundle); err != nil {
-		return nil, err
+	fy := &fleetYAML{}
+	if err := yaml.Unmarshal(bytes, fy); err != nil {
+		return nil, nil, err
 	}
 
 	var scans []*fleet.ImageScan
-	for i, scan := range bundle.ImageScans {
+	for i, scan := range fy.ImageScans {
 		if scan.Image == "" {
 			continue
 		}
 		if scan.TagName == "" {
-			return nil, errors.New("the name of scan is required")
+			return nil, nil, errors.New("the name of scan is required")
 		}
 
 		scans = append(scans, &fleet.ImageScan{
@@ -172,62 +176,62 @@ func read(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader,
 		})
 	}
 
-	bundle.BundleSpec.Targets = append(bundle.BundleSpec.Targets, bundle.TargetCustomizations...)
+	fy.BundleSpec.Targets = append(fy.BundleSpec.Targets, fy.TargetCustomizations...)
 
 	meta, err := readMetadata(bytes)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	meta.Name = name
-	if bundle.Name != "" {
-		meta.Name = bundle.Name
+	if fy.Name != "" {
+		meta.Name = fy.Name
 	}
 
-	setTargetNames(&bundle.BundleSpec)
+	setTargetNames(&fy.BundleSpec)
 
-	propagateHelmChartProperties(&bundle.BundleSpec)
+	propagateHelmChartProperties(&fy.BundleSpec)
 
-	resources, err := readResources(ctx, &bundle.BundleSpec, opts.Compress, baseDir, opts.Auth)
+	resources, err := readResources(ctx, &fy.BundleSpec, opts.Compress, baseDir, opts.Auth)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	bundle.Resources = resources
+	fy.Resources = resources
 
-	def := &fleet.Bundle{
+	bundle := &fleet.Bundle{
 		ObjectMeta: meta.ObjectMeta,
-		Spec:       bundle.BundleSpec,
+		Spec:       fy.BundleSpec,
 	}
 
 	for k, v := range opts.Labels {
-		if def.Labels == nil {
-			def.Labels = make(map[string]string)
+		if bundle.Labels == nil {
+			bundle.Labels = make(map[string]string)
 		}
-		def.Labels[k] = v
+		bundle.Labels[k] = v
 	}
 
 	// apply additional labels from spec
-	for k, v := range bundle.Labels {
-		if def.Labels == nil {
-			def.Labels = make(map[string]string)
+	for k, v := range fy.Labels {
+		if bundle.Labels == nil {
+			bundle.Labels = make(map[string]string)
 		}
-		def.Labels[k] = v
+		bundle.Labels[k] = v
 	}
 
 	if opts.ServiceAccount != "" {
-		def.Spec.ServiceAccount = opts.ServiceAccount
+		bundle.Spec.ServiceAccount = opts.ServiceAccount
 	}
 
-	def.Spec.ForceSyncGeneration = opts.SyncGeneration
+	bundle.Spec.ForceSyncGeneration = opts.SyncGeneration
 
-	def, err = appendTargets(def, opts.TargetsFile)
+	bundle, err = appendTargets(bundle, opts.TargetsFile)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	if len(def.Spec.Targets) == 0 {
-		def.Spec.Targets = []fleet.BundleTarget{
+	if len(bundle.Spec.Targets) == 0 {
+		bundle.Spec.Targets = []fleet.BundleTarget{
 			{
 				Name:         "default",
 				ClusterGroup: "default",
@@ -236,20 +240,21 @@ func read(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader,
 	}
 
 	if opts.TargetNamespace != "" {
-		def.Spec.TargetNamespace = opts.TargetNamespace
-		for i := range def.Spec.Targets {
-			def.Spec.Targets[i].TargetNamespace = opts.TargetNamespace
+		bundle.Spec.TargetNamespace = opts.TargetNamespace
+		for i := range bundle.Spec.Targets {
+			bundle.Spec.Targets[i].TargetNamespace = opts.TargetNamespace
 		}
 	}
 
 	if opts.Paused {
-		def.Spec.Paused = true
+		bundle.Spec.Paused = true
 	}
 
-	return New(def, scans...)
+	return bundle, scans, nil
 }
 
 // propagateHelmChartProperties propagates root Helm chart properties to the child targets.
+// This is necessary, so we can download the correct chart version for each target.
 func propagateHelmChartProperties(spec *fleet.BundleSpec) {
 	// Check if there is anything to propagate
 	if spec.Helm == nil {
diff --git a/pkg/bundlereader/resources.go b/pkg/bundlereader/resources.go
new file mode 100644
index 00000000..7465c76f
--- /dev/null
+++ b/pkg/bundlereader/resources.go
@@ -0,0 +1,219 @@
+package bundlereader
+
+import (
+	"context"
+	"crypto/sha256"
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"sync"
+
+	"github.com/rancher/fleet/modules/cli/pkg/progress"
+	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+	"golang.org/x/sync/errgroup"
+	"golang.org/x/sync/semaphore"
+
+	"github.com/rancher/wrangler/pkg/data"
+
+	"sigs.k8s.io/yaml"
+)
+
+var hasOCIURL = regexp.MustCompile(`^oci:\/\/`)
+
+type Auth struct {
+	Username      string
+	Password      string
+	CABundle      []byte
+	SSHPrivateKey []byte
+}
+
+// readResources reads and downloads all resources from the bundle
+func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, base string, auth Auth) ([]fleet.BundleResource, error) {
+	var directories []directory
+
+	directories, err := addDirectory(directories, base, ".", ".")
+	if err != nil {
+		return nil, err
+	}
+
+	var chartDirs []*fleet.HelmOptions
+
+	if spec.Helm != nil && spec.Helm.Chart != "" {
+		if err := parseValueFiles(base, spec.Helm); err != nil {
+			return nil, err
+		}
+		chartDirs = append(chartDirs, spec.Helm)
+	}
+
+	for _, target := range spec.Targets {
+		if target.Helm != nil {
+			err := parseValueFiles(base, target.Helm)
+			if err != nil {
+				return nil, err
+			}
+			if target.Helm.Chart != "" {
+				chartDirs = append(chartDirs, target.Helm)
+			}
+		}
+	}
+
+	directories, err = addRemoteCharts(directories, base, chartDirs, auth)
+	if err != nil {
+		return nil, err
+	}
+
+	resources, err := loadDirectories(ctx, compress, directories...)
+	if err != nil {
+		return nil, err
+	}
+
+	var result []fleet.BundleResource
+	for _, resources := range resources {
+		result = append(result, resources...)
+	}
+
+	sort.Slice(result, func(i, j int) bool {
+		return result[i].Name < result[j].Name
+	})
+
+	return result, nil
+}
+
+type directory struct {
+	prefix  string
+	base    string
+	source  string
+	key     string
+	version string
+	auth    Auth
+}
+
+func addDirectory(directories []directory, base, customDir, defaultDir string) ([]directory, error) {
+	if customDir == "" {
+		if _, err := os.Stat(filepath.Join(base, defaultDir)); os.IsNotExist(err) {
+			return directories, nil
+		} else if err != nil {
+			return directories, err
+		}
+		customDir = defaultDir
+	}
+
+	return append(directories, directory{
+		prefix: defaultDir,
+		base:   base,
+		source: customDir,
+		key:    defaultDir,
+	}), nil
+}
+
+func parseValueFiles(base string, chart *fleet.HelmOptions) (err error) {
+	if len(chart.ValuesFiles) != 0 {
+		valuesMap, err := generateValues(base, chart)
+		if err != nil {
+			return err
+		}
+
+		if len(valuesMap.Data) != 0 {
+			chart.Values = valuesMap
+		}
+	}
+
+	return nil
+}
+
+func generateValues(base string, chart *fleet.HelmOptions) (valuesMap *fleet.GenericMap, err error) {
+	valuesMap = &fleet.GenericMap{}
+	if chart.Values != nil {
+		valuesMap = chart.Values
+	}
+	for _, value := range chart.ValuesFiles {
+		valuesByte, err := os.ReadFile(base + "/" + value)
+		if err != nil {
+			return nil, err
+		}
+		tmpDataOpt := &fleet.GenericMap{}
+		err = yaml.Unmarshal(valuesByte, tmpDataOpt)
+		if err != nil {
+			return nil, err
+		}
+		valuesMap = mergeGenericMap(valuesMap, tmpDataOpt)
+	}
+
+	return valuesMap, nil
+}
+
+func mergeGenericMap(first, second *fleet.GenericMap) *fleet.GenericMap {
+	result := &fleet.GenericMap{Data: make(map[string]interface{})}
+	result.Data = data.MergeMaps(first.Data, second.Data)
+	return result
+}
+
+// addRemoteCharts gets the chart url from a helm repo server and returns a `directory` struct.
+// For every chart that is not on disk, create a directory struct that contains the charts URL as path.
+func addRemoteCharts(directories []directory, base string, charts []*fleet.HelmOptions, auth Auth) ([]directory, error) {
+	for _, chart := range charts {
+		if _, err := os.Stat(filepath.Join(base, chart.Chart)); os.IsNotExist(err) || chart.Repo != "" {
+			chartURL, err := chartURL(chart, auth)
+			if err != nil {
+				return nil, err
+			}
+
+			directories = append(directories, directory{
+				prefix:  checksum(chart),
+				base:    base,
+				source:  chartURL,
+				key:     checksum(chart),
+				auth:    auth,
+				version: chart.Version,
+			})
+		}
+	}
+	return directories, nil
+}
+
+func checksum(helm *fleet.HelmOptions) string {
+	if helm == nil {
+		return "none"
+	}
+	return fmt.Sprintf(".chart/%x", sha256.Sum256([]byte(helm.Chart + ":" + helm.Repo + ":" + helm.Version)[:]))
+}
+
+func loadDirectories(ctx context.Context, compress bool, directories ...directory) (map[string][]fleet.BundleResource, error) {
+	var (
+		sem    = semaphore.NewWeighted(4)
+		result = map[string][]fleet.BundleResource{}
+		l      = sync.Mutex{}
+		p      = progress.NewProgress()
+	)
+	defer p.Close()
+
+	eg, ctx := errgroup.WithContext(ctx)
+
+	for _, dir := range directories {
+		if err := sem.Acquire(ctx, 1); err != nil {
+			return nil, err
+		}
+		dir := dir
+		eg.Go(func() error {
+			defer sem.Release(1)
+			resources, err := loadDirectory(ctx, compress, dir.prefix, dir.base, dir.source, dir.version, dir.auth)
+			if err != nil {
+				return err
+			}
+
+			key := dir.key
+			if key == "" {
+				key = dir.source
+			}
+
+			l.Lock()
+			result[key] = resources
+			l.Unlock()
+			return nil
+		})
+	}
+
+	return result, eg.Wait()
+}
diff --git a/pkg/bundle/style.go b/pkg/bundlereader/style.go
similarity index 99%
rename from pkg/bundle/style.go
rename to pkg/bundlereader/style.go
index ca9296dc..1c043e36 100644
--- a/pkg/bundle/style.go
+++ b/pkg/bundlereader/style.go
@@ -1,4 +1,4 @@
-package bundle
+package bundlereader
 
 import (
 	"path/filepath"
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 40d2d633..b558366f 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -20,7 +20,10 @@ const (
 	AgentConfigName          = "fleet-agent"
 	AgentBootstrapConfigName = "fleet-agent-bootstrap"
 	Key                      = "config"
-	DefaultNamespace         = "cattle-fleet-system"
+
+	// DefaultNamespace is the default for the system namespace, which
+	// contains the manager and agent
+	DefaultNamespace = "cattle-fleet-system"
 )
 
 var (
diff --git a/pkg/controllers/bundle/controller.go b/pkg/controllers/bundle/controller.go
index db2872e3..f34e178b 100644
--- a/pkg/controllers/bundle/controller.go
+++ b/pkg/controllers/bundle/controller.go
@@ -173,23 +173,38 @@ func (h *handler) OnBundleChange(bundle *fleet.Bundle, status fleet.BundleStatus
 	logrus.Debugf("OnBundleChange for bundle '%s', checking targets, calculating changes, building objects", bundle.Name)
 	start := time.Now()
 
-	targets, err := h.targets.Targets(bundle)
+	manifest, err := manifest.New(bundle.Spec.Resources)
 	if err != nil {
 		return nil, status, err
 	}
 
-	if err := h.calculateChanges(&status, targets); err != nil {
+	// this does not need to happen after merging the
+	// BundleDeploymentOptions, since 'fleet apply' already put the right
+	// resources into bundle.Spec.Resources
+	if _, err := h.targets.StoreManifest(manifest); err != nil {
 		return nil, status, err
 	}
 
-	if err := setResourceKey(&status, bundle, h.isNamespaced, status.ObservedGeneration != bundle.Generation); err != nil {
+	matchedTargets, err := h.targets.Targets(bundle, manifest)
+	if err != nil {
+		return nil, status, err
+	}
+
+	// NOTE this mutates allTargets and adds new deployments
+	if err := h.calculateChanges(&status, matchedTargets); err != nil {
 		return nil, status, err
 	}
 
+	if status.ObservedGeneration != bundle.Generation {
+		if err := setResourceKey(&status, bundle, manifest, h.isNamespaced); err != nil {
+			return nil, status, err
+		}
+	}
+
 	summary.SetReadyConditions(&status, "Cluster", status.Summary)
 	status.ObservedGeneration = bundle.Generation
 
-	objs := toRuntimeObjects(targets, bundle)
+	objs := bundleDeployments(matchedTargets, bundle)
 
 	elapsed := time.Since(start)
 
@@ -211,19 +226,12 @@ func (h *handler) isNamespaced(gvk schema.GroupVersionKind) bool {
 	return mapping.Scope.Name() == meta.RESTScopeNameNamespace
 }
 
-func setResourceKey(status *fleet.BundleStatus, bundle *fleet.Bundle, isNSed func(schema.GroupVersionKind) bool, set bool) error {
-	if !set {
-		return nil
-	}
+// setResourceKey runs helm template to set up all resource keys in the BundleStatus passed in as argument.
+func setResourceKey(status *fleet.BundleStatus, bundle *fleet.Bundle, manifest *manifest.Manifest, isNSed func(schema.GroupVersionKind) bool) error {
 	bundleMap := map[fleet.ResourceKey]struct{}{}
-	m, err := manifest.New(&bundle.Spec)
-	if err != nil {
-		return err
-	}
-
 	for i := range bundle.Spec.Targets {
-		opts := options.Calculate(&bundle.Spec, &bundle.Spec.Targets[i])
-		objs, err := helmdeployer.Template(bundle.Name, m, opts)
+		opts := options.Merge(bundle.Spec.BundleDeploymentOptions, bundle.Spec.Targets[i].BundleDeploymentOptions)
+		objs, err := helmdeployer.Template(bundle.Name, manifest, opts)
 		if err != nil {
 			return err
 		}
@@ -249,6 +257,7 @@ func setResourceKey(status *fleet.BundleStatus, bundle *fleet.Bundle, isNSed fun
 			bundleMap[key] = struct{}{}
 		}
 	}
+
 	keys := []fleet.ResourceKey{}
 	for k := range bundleMap {
 		keys = append(keys, k)
@@ -275,11 +284,13 @@ func setResourceKey(status *fleet.BundleStatus, bundle *fleet.Bundle, isNSed fun
 	return nil
 }
 
-func toRuntimeObjects(targets []*target.Target, bundle *fleet.Bundle) (result []runtime.Object) {
+// bundleDeployments converts the targets to BundleDeployment resources
+func bundleDeployments(targets []*target.Target, bundle *fleet.Bundle) (result []runtime.Object) {
 	for _, target := range targets {
 		if target.Deployment == nil {
 			continue
 		}
+		// NOTE we don't use the existing BundleDeployment, we discard annotations, status, etc
 		dp := &fleet.BundleDeployment{
 			ObjectMeta: v1.ObjectMeta{
 				Name:      target.Deployment.Name,
@@ -295,6 +306,9 @@ func toRuntimeObjects(targets []*target.Target, bundle *fleet.Bundle) (result []
 	return
 }
 
+// calculateChanges calculates the changes to the targets, if a target is
+// without a deployment, a new BundleDeployment is build
+// This func mutates status and allTargets.
 func (h *handler) calculateChanges(status *fleet.BundleStatus, allTargets []*target.Target) (err error) {
 	// reset
 	status.MaxNew = maxNew
@@ -326,12 +340,14 @@ func (h *handler) calculateChanges(status *fleet.BundleStatus, allTargets []*tar
 				newTarget(target, status)
 			}
 			if target.Deployment != nil {
+				// NOTE merged options from targets.Targets() are set to be staged
 				target.Deployment.Spec.StagedOptions = target.Options
 				target.Deployment.Spec.StagedDeploymentID = target.DeploymentID
 			}
 		}
 
 		for _, currentTarget := range partition.Targets {
+			// NOTE this will propagate the merged options to the current deployment
 			updateManifest(currentTarget, status, &partition.Status)
 		}
 
@@ -351,6 +367,8 @@ func (h *handler) calculateChanges(status *fleet.BundleStatus, allTargets []*tar
 	return nil
 }
 
+// updateManifest will update DeploymentID and Options for the target to the
+// staging values, if it's in a deployable state
 func updateManifest(t *target.Target, status *fleet.BundleStatus, partitionStatus *fleet.PartitionStatus) {
 	if t.Deployment != nil &&
 		// Not Paused
@@ -363,6 +381,7 @@ func updateManifest(t *target.Target, status *fleet.BundleStatus, partitionStatu
 		(status.Unavailable < status.MaxUnavailable || target.IsUnavailable(t.Deployment)) &&
 		// Partition max unavailable not reached
 		(partitionStatus.Unavailable < partitionStatus.MaxUnavailable || target.IsUnavailable(t.Deployment)) {
+
 		if !target.IsUnavailable(t.Deployment) {
 			// If this was previously available, now increment unavailable count. "Upgrading" is treated as unavailable.
 			status.Unavailable++
diff --git a/pkg/controllers/cluster/controller.go b/pkg/controllers/cluster/controller.go
index e9155e09..9c23654e 100644
--- a/pkg/controllers/cluster/controller.go
+++ b/pkg/controllers/cluster/controller.go
@@ -75,7 +75,7 @@ func Register(ctx context.Context,
 func (h *handler) ensureNSDeleted(key string, obj *fleet.Cluster) (*fleet.Cluster, error) {
 	if obj == nil {
 		logrus.Debugf("Cluster %s deleted, enqueue cluster namespace deletion", key)
-		h.namespaces.Enqueue(clusterToNamespace(kv.Split(key, "/")))
+		h.namespaces.Enqueue(clusterNamespace(kv.Split(key, "/")))
 	}
 	return obj, nil
 }
@@ -106,9 +106,10 @@ func (h *handler) findClusters(namespaces corecontrollers.NamespaceCache) relate
 	}
 }
 
-// clusterToNamespace returns the namespace name for a given cluster name, e.g.:
-// cluster-fleet-local-cluster-294db1acfa77-d9ccf852678f
-func clusterToNamespace(clusterNamespace, clusterName string) string {
+// clusterNamespace returns the cluster namespace name
+// for a given cluster name, e.g.:
+// "cluster-fleet-local-cluster-294db1acfa77-d9ccf852678f"
+func clusterNamespace(clusterNamespace, clusterName string) string {
 	return name.SafeConcatName("cluster",
 		clusterNamespace,
 		clusterName,
@@ -136,7 +137,7 @@ func (h *handler) OnClusterChanged(cluster *fleet.Cluster, status fleet.ClusterS
 	}
 
 	if status.Namespace == "" {
-		status.Namespace = clusterToNamespace(cluster.Namespace, cluster.Name)
+		status.Namespace = clusterNamespace(cluster.Namespace, cluster.Name)
 	}
 
 	bundleDeployments, err := h.bundleDeployment.List(status.Namespace, labels.Everything())
diff --git a/pkg/controllers/cluster/import.go b/pkg/controllers/cluster/import.go
index 3c2fc71b..0cf71b8b 100644
--- a/pkg/controllers/cluster/import.go
+++ b/pkg/controllers/cluster/import.go
@@ -33,7 +33,6 @@ import (
 
 var (
 	ImportTokenPrefix = "import-token-"
-	ImportTokenTTL    = durations.ClusterImportTokenTTL
 )
 
 type importHandler struct {
@@ -235,7 +234,7 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust
 				},
 			},
 			Spec: fleet.ClusterRegistrationTokenSpec{
-				TTL: &metav1.Duration{Duration: ImportTokenTTL},
+				TTL: &metav1.Duration{Duration: durations.ClusterImportTokenTTL},
 			},
 		})
 		i.clusters.EnqueueAfter(cluster.Namespace, cluster.Name, durations.TokenClusterEnqueueDelay)
@@ -313,7 +312,7 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust
 
 		// Clean up the leftover clusters namespace if it exists.
 		// We want to keep the DefaultNamespace alive, but not the clusters namespace.
-		err = kc.CoreV1().Namespaces().Delete(i.ctx, fleetns.RegistrationNamespace(config.DefaultNamespace), metav1.DeleteOptions{})
+		err = kc.CoreV1().Namespaces().Delete(i.ctx, fleetns.SystemRegistrationNamespace(config.DefaultNamespace), metav1.DeleteOptions{})
 		if err != nil && !apierrors.IsNotFound(err) {
 			return status, err
 		}
diff --git a/pkg/controllers/clusterregistration/controller.go b/pkg/controllers/clusterregistration/controller.go
index 72b9df05..1a1199f5 100644
--- a/pkg/controllers/clusterregistration/controller.go
+++ b/pkg/controllers/clusterregistration/controller.go
@@ -225,7 +225,7 @@ func (h *handler) OnChange(request *fleet.ClusterRegistration, status fleet.Clus
 		request.Namespace, request.Name, cluster.Namespace, cluster.Name, status.Granted)
 
 	status.ClusterName = cluster.Name
-	// e.g. request- in cluster-registration-namespace
+	// e.g. request- in the cluster namespace
 	return append(objects,
 		&v1.ServiceAccount{
 			ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/controllers/controllers.go b/pkg/controllers/controllers.go
index 00ee15ba..149325c9 100644
--- a/pkg/controllers/controllers.go
+++ b/pkg/controllers/controllers.go
@@ -77,9 +77,9 @@ func Register(ctx context.Context, systemNamespace string, cfg clientcmd.ClientC
 		return err
 	}
 
-	systemRegistrationNamespace := fleetns.RegistrationNamespace(systemNamespace)
+	systemRegistrationNamespace := fleetns.SystemRegistrationNamespace(systemNamespace)
 
-	if err := addData(systemNamespace, systemRegistrationNamespace, appCtx); err != nil {
+	if err := applyBootstrapResources(systemNamespace, systemRegistrationNamespace, appCtx); err != nil {
 		return err
 	}
 
diff --git a/pkg/controllers/data.go b/pkg/controllers/data.go
index 3b5c696b..38a45304 100644
--- a/pkg/controllers/data.go
+++ b/pkg/controllers/data.go
@@ -9,8 +9,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-func addData(systemNamespace, systemRegistrationNamespace string, appCtx *appContext) error {
-
+func applyBootstrapResources(systemNamespace, systemRegistrationNamespace string, appCtx *appContext) error {
 	return appCtx.Apply.
 		WithSetID("fleet-bootstrap-data").
 		WithDynamicLookup().
diff --git a/pkg/bundleyaml/bundleyaml.go b/pkg/fleetyaml/fleetyaml.go
similarity index 85%
rename from pkg/bundleyaml/bundleyaml.go
rename to pkg/fleetyaml/fleetyaml.go
index 08dbdae7..15bd4dce 100644
--- a/pkg/bundleyaml/bundleyaml.go
+++ b/pkg/fleetyaml/fleetyaml.go
@@ -1,4 +1,6 @@
-package bundleyaml
+// Package fleetyaml provides utilities for working with fleet.yaml files,
+// which are the central yaml files for bundles.
+package fleetyaml
 
 import (
 	"os"
diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go
deleted file mode 100644
index b8954609..00000000
--- a/pkg/helm/helm.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package helm
-
-import (
-	"path/filepath"
-	"strings"
-
-	"helm.sh/helm/v3/pkg/chart"
-
-	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/bundle"
-	"github.com/rancher/fleet/pkg/bundleyaml"
-	"github.com/rancher/fleet/pkg/manifest"
-	"github.com/rancher/fleet/pkg/rawyaml"
-
-	"github.com/rancher/wrangler/pkg/kv"
-
-	"sigs.k8s.io/yaml"
-)
-
-func Process(name string, m *manifest.Manifest, style bundle.Style) (*manifest.Manifest, error) {
-	newManifest := toChart(m, style)
-	if !style.HasChartYAML {
-		return addChartYAML(name, m, newManifest)
-	}
-	return newManifest, nil
-}
-
-func move(m *manifest.Manifest, from, to string) (result []fleet.BundleResource) {
-	if from == "." {
-		from = ""
-	} else if from != "" {
-		from += "/"
-	}
-	for _, resource := range m.Resources {
-		if strings.HasPrefix(resource.Name, from) {
-			resource.Name = to + strings.TrimPrefix(resource.Name, from)
-			result = append(result, resource)
-		}
-	}
-	return result
-}
-
-func manifests(m *manifest.Manifest) (result []fleet.BundleResource) {
-	var ignorePrefix []string
-	for _, resource := range m.Resources {
-		if bundleyaml.IsFleetYamlSuffix(resource.Name) ||
-			strings.HasSuffix(resource.Name, "/Chart.yaml") {
-			ignorePrefix = append(ignorePrefix, filepath.Dir(resource.Name)+"/")
-		}
-	}
-
-outer:
-	for _, resource := range m.Resources {
-		if bundleyaml.IsFleetYaml(resource.Name) {
-			continue
-		}
-		if !strings.HasSuffix(resource.Name, ".yaml") &&
-			!strings.HasSuffix(resource.Name, ".json") &&
-			!strings.HasSuffix(resource.Name, ".yml") {
-			continue
-		}
-		for _, prefix := range ignorePrefix {
-			if strings.HasPrefix(resource.Name, prefix) {
-				continue outer
-			}
-		}
-		if strings.HasPrefix(resource.Name, "templates/") {
-			resource.Name = "chart/" + resource.Name
-		} else {
-			resource.Name = rawyaml.YAMLPrefix + resource.Name
-		}
-		result = append(result, resource)
-	}
-
-	return result
-}
-
-func toChart(m *manifest.Manifest, style bundle.Style) *manifest.Manifest {
-	var (
-		resources []fleet.BundleResource
-	)
-
-	if style.ChartPath != "" {
-		resources = move(m, filepath.Dir(style.ChartPath), "chart/")
-	} else if style.IsRawYAML() {
-		resources = manifests(m)
-	}
-
-	return &manifest.Manifest{
-		Resources: resources,
-		Commit:    m.Commit,
-	}
-}
-
-func addChartYAML(name string, m, newManifest *manifest.Manifest) (*manifest.Manifest, error) {
-	_, hash, err := m.Content()
-	if err != nil {
-		return nil, err
-	}
-
-	if newManifest.Commit != "" && len(newManifest.Commit) > 12 {
-		hash = "git-" + newManifest.Commit[:12]
-	}
-
-	_, chartName := kv.RSplit(name, "/")
-	metadata := chart.Metadata{
-		Name:       chartName,
-		Version:    "v0.0.0+" + hash,
-		APIVersion: "v2",
-	}
-
-	chart, err := yaml.Marshal(metadata)
-	if err != nil {
-		return nil, err
-	}
-
-	newManifest.Resources = append(newManifest.Resources, fleet.BundleResource{
-		Name:    "chart/Chart.yaml",
-		Content: string(chart),
-	})
-
-	return newManifest, nil
-}
diff --git a/pkg/helmdeployer/deployer.go b/pkg/helmdeployer/deployer.go
index c690e528..6147862d 100644
--- a/pkg/helmdeployer/deployer.go
+++ b/pkg/helmdeployer/deployer.go
@@ -171,7 +171,7 @@ func (h *Helm) Deploy(bundleID string, manifest *manifest.Manifest, options flee
 		options.Kustomize = &fleet.KustomizeOptions{}
 	}
 
-	tar, err := render.ToChart(bundleID, manifest, options)
+	tar, err := render.HelmChart(bundleID, manifest, options)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/helmdeployer/template.go b/pkg/helmdeployer/template.go
index d2f897df..082977be 100644
--- a/pkg/helmdeployer/template.go
+++ b/pkg/helmdeployer/template.go
@@ -17,6 +17,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 )
 
+// Template runs helm template and returns the resources as a list of objects, without applying them.
 func Template(bundleID string, manifest *manifest.Manifest, options fleet.BundleDeploymentOptions) ([]runtime.Object, error) {
 	h := &Helm{
 		globalCfg:    action.Configuration{},
diff --git a/pkg/manifest/lookup.go b/pkg/manifest/lookup.go
index eb8d136f..75881deb 100644
--- a/pkg/manifest/lookup.go
+++ b/pkg/manifest/lookup.go
@@ -31,5 +31,5 @@ func (l *lookup) Get(id string) (*Manifest, error) {
 		return nil, err
 	}
 
-	return ReadManifest(bytes, id)
+	return readManifest(bytes, id)
 }
diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go
index 30058515..035f2c89 100644
--- a/pkg/manifest/manifest.go
+++ b/pkg/manifest/manifest.go
@@ -20,14 +20,14 @@ type Manifest struct {
 	digest    string
 }
 
-func New(spec *fleet.BundleSpec) (*Manifest, error) {
+func New(resources []fleet.BundleResource) (*Manifest, error) {
 	m := &Manifest{
-		Resources: spec.Resources,
+		Resources: resources,
 	}
 	return m, nil
 }
 
-func ReadManifest(data []byte, digest string) (*Manifest, error) {
+func readManifest(data []byte, digest string) (*Manifest, error) {
 	if digest != "" {
 		if _, err := sha256Matches(bytes.NewReader(data), digest); err != nil {
 			return nil, err
diff --git a/pkg/namespace/util.go b/pkg/namespace/util.go
index 08166699..8c798c07 100644
--- a/pkg/namespace/util.go
+++ b/pkg/namespace/util.go
@@ -1,4 +1,11 @@
-// Package namespace generates the name of the cluster registration namespace. (fleetcontroller)
+// Package namespace generates the name of the system registration namespace. (fleetcontroller)
+//
+// Special namespaces in fleet:
+// * system namespace: cattle-fleet-system
+// * system registration namespace: cattle-fleet-clusters-system
+// * cluster registration namespace or "workspace": fleet-local
+// * cluster namespace: cluster-${namespace}-${cluster}-${random}
+
 package namespace
 
 import (
@@ -16,10 +23,13 @@ func GVK() schema.GroupVersionKind {
 	}
 }
 
-func RegistrationNamespace(systemNamespace string) string {
-	systemRegistrationNamespace := strings.ReplaceAll(systemNamespace, "-system", "-clusters-system")
-	if systemRegistrationNamespace == systemNamespace {
+// SystemRegistrationNamespace generates the name of the system registration
+// namespace from the configured system namespace, e.g.:
+// cattle-fleet-system -> cattle-fleet-clusters-system
+func SystemRegistrationNamespace(systemNamespace string) string {
+	ns := strings.ReplaceAll(systemNamespace, "-system", "-clusters-system")
+	if ns == systemNamespace {
 		return systemNamespace + "-clusters-system"
 	}
-	return systemRegistrationNamespace
+	return ns
 }
diff --git a/pkg/options/calculate.go b/pkg/options/calculate.go
index 8fe3e040..2a89645d 100644
--- a/pkg/options/calculate.go
+++ b/pkg/options/calculate.go
@@ -1,3 +1,4 @@
+// Package options merges the BundleDeploymentOptions
 package options
 
 import (
@@ -10,6 +11,7 @@ import (
 	"github.com/rancher/wrangler/pkg/data"
 )
 
+// DeploymentID hashes the options to a string
 func DeploymentID(manifest *manifest.Manifest, opts fleet.BundleDeploymentOptions) (string, error) {
 	_, digest, err := manifest.Content()
 	if err != nil {
@@ -24,11 +26,8 @@ func DeploymentID(manifest *manifest.Manifest, opts fleet.BundleDeploymentOption
 	return digest + ":" + hex.EncodeToString(h.Sum(nil)), nil
 }
 
-func Calculate(spec *fleet.BundleSpec, target *fleet.BundleTarget) fleet.BundleDeploymentOptions {
-	return merge(spec.BundleDeploymentOptions, target.BundleDeploymentOptions)
-}
-
-func merge(base, next fleet.BundleDeploymentOptions) fleet.BundleDeploymentOptions {
+// Merge overrides the 'base' options with the 'next' options, if 'next' is present.
+func Merge(base, next fleet.BundleDeploymentOptions) fleet.BundleDeploymentOptions { // nolint: gocyclo // business logic
 	result := *base.DeepCopy()
 	if next.DefaultNamespace != "" {
 		result.DefaultNamespace = next.DefaultNamespace
diff --git a/pkg/render/helm.go b/pkg/render/helm.go
index 14cd5ed0..2c05e87c 100644
--- a/pkg/render/helm.go
+++ b/pkg/render/helm.go
@@ -2,17 +2,28 @@ package render
 
 import (
 	"io"
+	"path/filepath"
+	"strings"
+
+	"helm.sh/helm/v3/pkg/chart"
 
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/bundle"
-	"github.com/rancher/fleet/pkg/helm"
+	"github.com/rancher/fleet/pkg/bundlereader"
+	"github.com/rancher/fleet/pkg/fleetyaml"
 	"github.com/rancher/fleet/pkg/manifest"
 	"github.com/rancher/fleet/pkg/patch"
+	"github.com/rancher/fleet/pkg/rawyaml"
+
+	"github.com/rancher/wrangler/pkg/kv"
+
+	"sigs.k8s.io/yaml"
 )
 
-func ToChart(name string, m *manifest.Manifest, options fleet.BundleDeploymentOptions) (io.Reader, error) {
+// HelmChart applies overlays to "manifest"-style gitrepos and transforms the
+// manifest into a helm chart tgz
+func HelmChart(name string, m *manifest.Manifest, options fleet.BundleDeploymentOptions) (io.Reader, error) {
 	var (
-		style = bundle.DetermineStyle(m, options)
+		style = bundlereader.DetermineStyle(m, options)
 		err   error
 	)
 
@@ -27,10 +38,118 @@ func ToChart(name string, m *manifest.Manifest, options fleet.BundleDeploymentOp
 		}
 	}
 
-	m, err = helm.Process(name, m, style)
+	m, err = process(name, m, style)
 	if err != nil {
 		return nil, err
 	}
 
 	return m.ToTarGZ()
 }
+
+// process filters the manifests resources and adds a Chart.yaml if missing
+func process(name string, m *manifest.Manifest, style bundlereader.Style) (*manifest.Manifest, error) {
+	newManifest := toChart(m, style)
+	if !style.HasChartYAML {
+		return addChartYAML(name, m, newManifest)
+	}
+	return newManifest, nil
+}
+
+func move(m *manifest.Manifest, from, to string) (result []fleet.BundleResource) {
+	if from == "." {
+		from = ""
+	} else if from != "" {
+		from += "/"
+	}
+	for _, resource := range m.Resources {
+		if strings.HasPrefix(resource.Name, from) {
+			resource.Name = to + strings.TrimPrefix(resource.Name, from)
+			result = append(result, resource)
+		}
+	}
+	return result
+}
+
+// manifests returns a filtered list of BundleResources
+// It also treats the 'templates/' directory as a special case.
+func manifests(m *manifest.Manifest) (result []fleet.BundleResource) {
+	var ignorePrefix []string
+	for _, resource := range m.Resources {
+		if fleetyaml.IsFleetYamlSuffix(resource.Name) ||
+			strings.HasSuffix(resource.Name, "/Chart.yaml") {
+			ignorePrefix = append(ignorePrefix, filepath.Dir(resource.Name)+"/")
+		}
+	}
+
+outer:
+	for _, resource := range m.Resources {
+		if fleetyaml.IsFleetYaml(resource.Name) {
+			continue
+		}
+		if !strings.HasSuffix(resource.Name, ".yaml") &&
+			!strings.HasSuffix(resource.Name, ".json") &&
+			!strings.HasSuffix(resource.Name, ".yml") {
+			continue
+		}
+		for _, prefix := range ignorePrefix {
+			if strings.HasPrefix(resource.Name, prefix) {
+				continue outer
+			}
+		}
+		if strings.HasPrefix(resource.Name, "templates/") {
+			resource.Name = "chart/" + resource.Name
+		} else {
+			resource.Name = rawyaml.YAMLPrefix + resource.Name
+		}
+		result = append(result, resource)
+	}
+
+	return result
+}
+
+func toChart(m *manifest.Manifest, style bundlereader.Style) *manifest.Manifest {
+	var (
+		resources []fleet.BundleResource
+	)
+
+	if style.ChartPath != "" {
+		resources = move(m, filepath.Dir(style.ChartPath), "chart/")
+	} else if style.IsRawYAML() {
+		resources = manifests(m)
+	}
+
+	return &manifest.Manifest{
+		Resources: resources,
+		Commit:    m.Commit,
+	}
+}
+
+func addChartYAML(name string, m, newManifest *manifest.Manifest) (*manifest.Manifest, error) {
+	_, hash, err := m.Content()
+	if err != nil {
+		return nil, err
+	}
+
+	if newManifest.Commit != "" && len(newManifest.Commit) > 12 {
+		hash = "git-" + newManifest.Commit[:12]
+	}
+
+	_, chartName := kv.RSplit(name, "/")
+	metadata := chart.Metadata{
+		Name:       chartName,
+		Version:    "v0.0.0+" + hash,
+		APIVersion: "v2",
+	}
+
+	chart, err := yaml.Marshal(metadata)
+	if err != nil {
+		return nil, err
+	}
+
+	newManifest.Resources = append(newManifest.Resources, fleet.BundleResource{
+		Name:    "chart/Chart.yaml",
+		Content: string(chart),
+	})
+
+	return newManifest, nil
+}
diff --git a/pkg/target/partition.go b/pkg/target/partition.go
index 90852d2f..54033d49 100644
--- a/pkg/target/partition.go
+++ b/pkg/target/partition.go
@@ -13,6 +13,7 @@ type Partition struct {
 	Targets []*Target
 }
 
+// Partitions distributes targets into partitions based on the rollout strategy
 func Partitions(targets []*Target) ([]Partition, error) {
 	rollout := getRollout(targets)
 	if len(rollout.Partitions) == 0 {
diff --git a/pkg/target/target.go b/pkg/target/target.go
index 494f6d5a..f7d4cf81 100644
--- a/pkg/target/target.go
+++ b/pkg/target/target.go
@@ -1,4 +1,8 @@
-// Package target provides a functions to match bundles and clusters and to list the bundledeployments for that match. (fleetcontroller)
+// Package target provides functionality around building and deploying bundledeployments. (fleetcontroller)
+//
+// Each "Target" represents a bundle, cluster pair and will be transformed into a bundledeployment.
+// The manifest, persisted in the content resource, contains the resources available to
+// these bundledeployments.
 package target
 
 import (
@@ -11,7 +15,7 @@ import (
 	"github.com/sirupsen/logrus"
 
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
-	"github.com/rancher/fleet/pkg/bundle"
+	"github.com/rancher/fleet/pkg/bundlematcher"
 	fleetcontrollers "github.com/rancher/fleet/pkg/generated/controllers/fleet.cattle.io/v1alpha1"
 	"github.com/rancher/fleet/pkg/manifest"
 	"github.com/rancher/fleet/pkg/options"
@@ -70,6 +74,12 @@ func (m *Manager) BundleFromDeployment(bd *fleet.BundleDeployment) (string, stri
 		bd.Labels["fleet.cattle.io/bundle-name"]
 }
 
+// StoreManifest stores the manifest as a content resource and returns the name.
+// It copies the resources from the bundle to the content resource.
+func (m *Manager) StoreManifest(manifest *manifest.Manifest) (string, error) {
+	return m.contentStore.Store(manifest)
+}
+
 func clusterGroupsToLabelMap(cgs []*fleet.ClusterGroup) map[string]map[string]string {
 	result := map[string]map[string]string{}
 	for _, cg := range cgs {
@@ -150,7 +160,7 @@ func (m *Manager) BundlesForCluster(cluster *fleet.Cluster) (bundlesToRefresh, b
 	}
 
 	for _, app := range bundles {
-		bundle, err := bundle.New(app)
+		bm, err := bundlematcher.New(app)
 		if err != nil {
 			logrus.Errorf("ignore bad app %s/%s: %v", app.Namespace, app.Name, err)
 			continue
@@ -161,7 +171,7 @@ func (m *Manager) BundlesForCluster(cluster *fleet.Cluster) (bundlesToRefresh, b
 			return nil, nil, err
 		}
 
-		match := bundle.Match(cluster.Name, clusterGroupsToLabelMap(cgs), cluster.Labels)
+		match := bm.Match(cluster.Name, clusterGroupsToLabelMap(cgs), cluster.Labels)
 		if match != nil {
 			bundlesToRefresh = append(bundlesToRefresh, app)
 		} else {
@@ -187,16 +197,17 @@ func (m *Manager) GetBundleDeploymentsForBundleInCluster(app *fleet.Bundle, clus
 	return result, nil
 }
 
-// getNamespacesForBundle returns the namespaces that the bundle should be
-// deployed to. Which is the bundles namespace and every namespace from the
-// bundle's namespace mappings.
-func (m *Manager) getNamespacesForBundle(fleetBundle *fleet.Bundle) ([]string, error) {
-	mappings, err := m.bundleNamespaceMappingCache.List(fleetBundle.Namespace, labels.Everything())
+// getNamespacesForBundle returns the namespaces that bundledeployments could
+// be created in.
+// These are the bundle's namespace, e.g. "fleet-local", and every namespace
+// matched by a bundle namespace mapping resource.
+func (m *Manager) getNamespacesForBundle(bundle *fleet.Bundle) ([]string, error) {
+	mappings, err := m.bundleNamespaceMappingCache.List(bundle.Namespace, labels.Everything())
 	if err != nil {
 		return nil, err
 	}
 
-	nses := sets.NewString(fleetBundle.Namespace)
+	nses := sets.NewString(bundle.Namespace)
 	for _, mapping := range mappings {
 		matcher, err := NewBundleMapping(mapping, m.namespaceCache, m.bundleCache)
 		if err != nil {
@@ -216,27 +227,24 @@ func (m *Manager) getNamespacesForBundle(fleetBundle *fleet.Bundle) ([]string, e
 	return nses.List(), nil
 }
 
-// Targets returns all targets for a bundle, so we can create bundledeployments for each
-func (m *Manager) Targets(fleetBundle *fleet.Bundle) (result []*Target, _ error) {
-	bundle, err := bundle.New(fleetBundle)
+// Targets returns all targets for a bundle, so we can create bundledeployments for each.
+// This is done by checking all namespaces for clusters matching the bundle's
+// BundleTarget matchers.
+//
+// The returned target structs contain merged BundleDeploymentOptions.
+// Finally all existing bundledeployments are added to the targets.
+func (m *Manager) Targets(bundle *fleet.Bundle, manifest *manifest.Manifest) ([]*Target, error) {
+	bm, err := bundlematcher.New(bundle)
 	if err != nil {
 		return nil, err
 	}
 
-	manifest, err := manifest.New(&bundle.Definition.Spec)
-	if err != nil {
-		return nil, err
-	}
-
-	if _, err := m.contentStore.Store(manifest); err != nil {
-		return nil, err
-	}
-
-	namespaces, err := m.getNamespacesForBundle(fleetBundle)
+	namespaces, err := m.getNamespacesForBundle(bundle)
 	if err != nil {
 		return nil, err
 	}
 
+	var targets []*Target
 	for _, namespace := range namespaces {
 		clusters, err := m.clusters.List(namespace, labels.Everything())
 		if err != nil {
@@ -249,12 +257,12 @@ func (m *Manager) Targets(fleetBundle *fleet.Bundle) (result []*Target, _ error)
 				return nil, err
 			}
 
-			match := bundle.Match(cluster.Name, clusterGroupsToLabelMap(clusterGroups), cluster.Labels)
-			if match == nil {
+			target := bm.Match(cluster.Name, clusterGroupsToLabelMap(clusterGroups), cluster.Labels)
+			if target == nil {
 				continue
 			}
 
-			opts := options.Calculate(&fleetBundle.Spec, match.Target)
+			opts := options.Merge(bundle.Spec.BundleDeploymentOptions, target.BundleDeploymentOptions)
 			err = addClusterLabels(&opts, cluster.Labels)
 			if err != nil {
 				return nil, err
@@ -265,22 +273,22 @@ func (m *Manager) Targets(fleetBundle *fleet.Bundle) (result []*Target, _ error)
 				return nil, err
 			}
 
-			result = append(result, &Target{
+			targets = append(targets, &Target{
 				ClusterGroups: clusterGroups,
 				Cluster:       cluster,
-				Target:        match.Target,
-				Bundle:        fleetBundle,
+				Target:        target, // contains the unmerged BundleDeploymentOptions
+				Bundle:        bundle,
 				Options:       opts,
 				DeploymentID:  deploymentID,
 			})
 		}
 	}
 
-	sort.Slice(result, func(i, j int) bool {
-		return result[i].Cluster.Name < result[j].Cluster.Name
+	sort.Slice(targets, func(i, j int) bool {
+		return targets[i].Cluster.Name < targets[j].Cluster.Name
 	})
 
-	return result, m.foldInDeployments(fleetBundle, result)
+	return targets, m.foldInDeployments(bundle, targets)
 }
 
 func addClusterLabels(opts *fleet.BundleDeploymentOptions, labels map[string]string) (err error) {
@@ -328,15 +336,16 @@ func addClusterLabels(opts *fleet.BundleDeploymentOptions, labels map[string]str
 
 }
 
-func (m *Manager) foldInDeployments(app *fleet.Bundle, targets []*Target) error {
-	bundleDeployments, err := m.bundleDeploymentCache.List("", labels.SelectorFromSet(deploymentLabelsForSelector(app)))
+// foldInDeployments adds the existing bundledeployments to the targets.
+func (m *Manager) foldInDeployments(bundle *fleet.Bundle, targets []*Target) error {
+	bundleDeployments, err := m.bundleDeploymentCache.List("", labels.SelectorFromSet(deploymentLabelsForSelector(bundle)))
 	if err != nil {
 		return err
 	}
 
 	byNamespace := map[string]*fleet.BundleDeployment{}
-	for _, appDep := range bundleDeployments {
-		byNamespace[appDep.Namespace] = appDep.DeepCopy()
+	for _, bd := range bundleDeployments {
+		byNamespace[bd.Namespace] = bd.DeepCopy()
 	}
 
 	for _, target := range targets {
@@ -346,23 +355,23 @@ func (m *Manager) foldInDeployments(app *fleet.Bundle, targets []*Target) error
 	return nil
 }
 
-func deploymentLabelsForNewBundle(app *fleet.Bundle) map[string]string {
-	labels := yaml.CleanAnnotationsForExport(app.Labels)
-	for k, v := range app.Labels {
+func deploymentLabelsForNewBundle(bundle *fleet.Bundle) map[string]string {
+	labels := yaml.CleanAnnotationsForExport(bundle.Labels)
+	for k, v := range bundle.Labels {
 		if strings.HasPrefix(k, "fleet.cattle.io/") {
 			labels[k] = v
 		}
 	}
-	for k, v := range deploymentLabelsForSelector(app) {
+	for k, v := range deploymentLabelsForSelector(bundle) {
 		labels[k] = v
 	}
 	return labels
 }
 
-func deploymentLabelsForSelector(app *fleet.Bundle) map[string]string {
+func deploymentLabelsForSelector(bundle *fleet.Bundle) map[string]string {
 	return map[string]string{
-		"fleet.cattle.io/bundle-name":      app.Name,
-		"fleet.cattle.io/bundle-namespace": app.Namespace,
+		"fleet.cattle.io/bundle-name":      bundle.Name,
+		"fleet.cattle.io/bundle-namespace": bundle.Namespace,
 	}
 }
 
@@ -381,6 +390,7 @@ func (t *Target) IsPaused() bool {
 		t.Bundle.Spec.Paused
 }
 
+// AssignNewDeployment builds a new BundleDeployment for the target.
 func (t *Target) AssignNewDeployment() {
 	labels := map[string]string{}
 	for k, v := range deploymentLabelsForNewBundle(t.Bundle) {


Test patch:
diff --git a/pkg/bundle/resources_test.go b/pkg/bundlereader/resources_test.go
similarity index 99%
rename from pkg/bundle/resources_test.go
rename to pkg/bundlereader/resources_test.go
index ad3dd274..96115463 100644
--- a/pkg/bundle/resources_test.go
+++ b/pkg/bundlereader/resources_test.go
@@ -1,4 +1,4 @@
-package bundle
+package bundlereader
 
 import (
 	"testing"
diff --git a/pkg/bundleyaml/bundleyaml_test.go b/pkg/fleetyaml/fleetyaml_test.go
similarity index 97%
rename from pkg/bundleyaml/bundleyaml_test.go
rename to pkg/fleetyaml/fleetyaml_test.go
index 95ab868b..19b8516a 100644
--- a/pkg/bundleyaml/bundleyaml_test.go
+++ b/pkg/fleetyaml/fleetyaml_test.go
@@ -1,7 +1,7 @@
 //go:build !windows
 // +build !windows
 
-package bundleyaml
+package fleetyaml
 
 import (
 	"path/filepath"