Repository: rancher/fleet

Issue:
Feature Request: reduce too frequent BundleDeployment updates from deployed resource events
### Is your feature request related to a problem?

Fleet agents' drift detection works by monitoring every deployed resources and enqueuing a reconciliation of the parent `BundleDeployment` when those produce an event.
Non-ready resources often produce multiple successive Kubernetes events in a short period of time, which would cause unnecessary noise and stress not only for the `BundleDeployment` reconciler running in the agent and downstream Kubernetes API, but also in the upstream, since the `Bundle` status reconciler would react to changes in their associated `BundleDeployments`.
Ultimately, all these events in the Fleet resources are also consumed by the Rancher UI, possibly causing unnecessary updates of the views and slowness in general.

### Solution you'd like

There is a compromise between reactiveness and efficiency, so that everything is updated at a reasonable pace without wasting resources.

### Alternatives you've considered

_No response_

### Anything else?

_No response_

Discussion:
@aruiz14: For QA, I added notes from testing I performed in the PR description, I don't know if you need any further testing.

Solution patch:
diff --git a/internal/bundlereader/helm.go b/internal/bundlereader/helm.go
index 5a05975e..e707564f 100644
--- a/internal/bundlereader/helm.go
+++ b/internal/bundlereader/helm.go
@@ -11,7 +11,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-// GetManifestFromHelmChart downloads the given helm chart and creates a manifest with its contents
+// GetManifestFromHelmChart downloads the given helm chart and creates a
+// manifest with its contents. This is used by the agent to deploy HelmApps.
 func GetManifestFromHelmChart(ctx context.Context, c client.Client, bd *fleet.BundleDeployment) (*manifest.Manifest, error) {
 	helm := bd.Spec.Options.Helm
 
@@ -36,7 +37,16 @@ func GetManifestFromHelmChart(ctx context.Context, c client.Client, bd *fleet.Bu
 		return nil, err
 	}
 
-	resources, err := loadDirectory(ctx, false, false, checksum(helm), temp, chartURL, helm.Version, auth)
+	resources, err := loadDirectory(ctx,
+		loadOpts{},
+		directory{
+			prefix:  checksum(helm),
+			base:    temp,
+			source:  chartURL,
+			version: helm.Version,
+			auth:    auth,
+		},
+	)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/bundlereader/loaddirectory.go b/internal/bundlereader/loaddirectory.go
index a5c8138e..a90773e2 100644
--- a/internal/bundlereader/loaddirectory.go
+++ b/internal/bundlereader/loaddirectory.go
@@ -13,6 +13,7 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"strings"
 	"unicode/utf8"
 
@@ -182,17 +183,17 @@ func readFleetIgnore(path string) ([]string, error) {
 	return ignored, nil
 }
 
-func loadDirectory(ctx context.Context, compress bool, disableDepsUpdate bool, prefix, base, source, version string, auth Auth) ([]fleet.BundleResource, error) {
+func loadDirectory(ctx context.Context, opts loadOpts, dir directory) ([]fleet.BundleResource, error) {
 	var resources []fleet.BundleResource
 
-	files, err := GetContent(ctx, base, source, version, auth, disableDepsUpdate)
+	files, err := GetContent(ctx, dir.base, dir.source, dir.version, dir.auth, opts.disableDepsUpdate, opts.ignoreApplyConfigs)
 	if err != nil {
 		return nil, err
 	}
 
 	for name, data := range files {
 		r := fleet.BundleResource{Name: name}
-		if compress || !utf8.Valid(data) {
+		if opts.compress || !utf8.Valid(data) {
 			content, err := content.Base64GZ(data)
 			if err != nil {
 				return nil, err
@@ -202,8 +203,8 @@ func loadDirectory(ctx context.Context, compress bool, disableDepsUpdate bool, p
 		} else {
 			r.Content = string(data)
 		}
-		if prefix != "" {
-			r.Name = filepath.Join(prefix, name)
+		if dir.prefix != "" {
+			r.Name = filepath.Join(dir.prefix, name)
 		}
 		resources = append(resources, r)
 	}
@@ -212,7 +213,7 @@ func loadDirectory(ctx context.Context, compress bool, disableDepsUpdate bool, p
 }
 
 // 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, disableDepsUpdate bool) (map[string][]byte, error) {
+func GetContent(ctx context.Context, base, source, version string, auth Auth, disableDepsUpdate bool, ignoreApplyConfigs []string) (map[string][]byte, error) {
 	temp, err := os.MkdirTemp("", "fleet")
 	if err != nil {
 		return nil, err
@@ -296,6 +297,11 @@ func GetContent(ctx context.Context, base, source, version string, auth Auth, di
 			return err
 		}
 
+		// ignore files containing only fleet apply config
+		if slices.Contains(ignoreApplyConfigs, name) {
+			return nil
+		}
+
 		if info.IsDir() {
 			// If the folder is a helm chart and dependency updates are not disabled,
 			// try to update possible dependencies.
diff --git a/internal/bundlereader/resources.go b/internal/bundlereader/resources.go
index cd56d596..d68f18cb 100644
--- a/internal/bundlereader/resources.go
+++ b/internal/bundlereader/resources.go
@@ -22,7 +22,8 @@ import (
 
 var hasOCIURL = regexp.MustCompile(`^oci:\/\/`)
 
-// readResources reads and downloads all resources from the bundle
+// readResources reads and downloads all resources from the bundle. Resources
+// can be downloaded and are spread across multiple directories.
 func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, base string, auth Auth, helmRepoURLRegex string) ([]fleet.BundleResource, error) {
 	directories, err := addDirectory(base, ".", ".")
 	if err != nil {
@@ -32,7 +33,7 @@ func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, b
 	var chartDirs []*fleet.HelmOptions
 
 	if spec.Helm != nil && spec.Helm.Chart != "" {
-		if err := parseValueFiles(base, spec.Helm); err != nil {
+		if err := parseValuesFiles(base, spec.Helm); err != nil {
 			return nil, err
 		}
 		chartDirs = append(chartDirs, spec.Helm)
@@ -40,7 +41,7 @@ func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, b
 
 	for _, target := range spec.Targets {
 		if target.Helm != nil {
-			err := parseValueFiles(base, target.Helm)
+			err := parseValuesFiles(base, target.Helm)
 			if err != nil {
 				return nil, err
 			}
@@ -60,14 +61,21 @@ func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, b
 	if spec.Helm != nil {
 		disableDepsUpdate = spec.Helm.DisableDependencyUpdate
 	}
-	resources, err := loadDirectories(ctx, compress, disableDepsUpdate, directories...)
+
+	loadOpts := loadOpts{
+		compress:           compress,
+		disableDepsUpdate:  disableDepsUpdate,
+		ignoreApplyConfigs: ignoreApplyConfigs(spec.Helm, spec.Targets...),
+	}
+	resources, err := loadDirectories(ctx, loadOpts, directories...)
 	if err != nil {
 		return nil, err
 	}
 
+	// flatten map to slice
 	var result []fleet.BundleResource
-	for _, resources := range resources {
-		result = append(result, resources...)
+	for _, r := range resources {
+		result = append(result, r...)
 	}
 
 	sort.Slice(result, func(i, j int) bool {
@@ -77,13 +85,48 @@ func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, b
 	return result, nil
 }
 
+type loadOpts struct {
+	compress           bool
+	disableDepsUpdate  bool
+	ignoreApplyConfigs []string
+}
+
+// ignoreApplyConfigs returns a list of config files that should not be added to the
+// bundle's resources. Their contents are converted into deployment options.
+// This includes:
+// * fleet.yaml
+// * spec.Helm.ValuesFiles
+// * spec.Targets[].Helm.ValuesFiles
+func ignoreApplyConfigs(spec *fleet.HelmOptions, targets ...fleet.BundleTarget) []string {
+	ignore := []string{"fleet.yaml"}
+	if spec != nil {
+		ignore = append(ignore, spec.ValuesFiles...)
+	}
+
+	for _, target := range targets {
+		if target.Helm == nil {
+			continue
+		}
+		ignore = append(ignore, target.Helm.ValuesFiles...)
+	}
+
+	return ignore
+}
+
+// directory represents a directory to load resources from. The directory can
+// be created from an external Helm chart, or a local path.
+// One bundle can consist of multiple directories.
 type directory struct {
-	prefix  string
-	base    string
-	source  string
-	key     string
+	// prefix is the generated top level dir of the chart, e.g. '.chart/1234'
+	prefix string
+	// base is the directory on disk to load the files from
+	base string
+	// source is the chart URL to download the chart from
+	source string
+	// version is the version of the chart
 	version string
-	auth    Auth
+	// auth is the auth to use for the chart URL
+	auth Auth
 }
 
 func addDirectory(base, customDir, defaultDir string) ([]directory, error) {
@@ -101,11 +144,10 @@ func addDirectory(base, customDir, defaultDir string) ([]directory, error) {
 		prefix: defaultDir,
 		base:   base,
 		source: customDir,
-		key:    defaultDir,
 	}}, nil
 }
 
-func parseValueFiles(base string, chart *fleet.HelmOptions) (err error) {
+func parseValuesFiles(base string, chart *fleet.HelmOptions) (err error) {
 	if len(chart.ValuesFiles) != 0 {
 		valuesMap, err := generateValues(base, chart)
 		if err != nil {
@@ -169,7 +211,6 @@ func addRemoteCharts(directories []directory, base string, charts []*fleet.HelmO
 				prefix:  checksum(chart),
 				base:    base,
 				source:  chartURL,
-				key:     checksum(chart),
 				auth:    auth,
 				version: chart.Version,
 			})
@@ -205,7 +246,8 @@ func checksum(helm *fleet.HelmOptions) string {
 	return fmt.Sprintf(".chart/%x", sha256.Sum256([]byte(helm.Chart + ":" + helm.Repo + ":" + helm.Version)[:]))
 }
 
-func loadDirectories(ctx context.Context, compress bool, disableDepsUpdate bool, directories ...directory) (map[string][]fleet.BundleResource, error) {
+// loadDirectories loads all resources from a bundle's directories
+func loadDirectories(ctx context.Context, opts loadOpts, directories ...directory) (map[string][]fleet.BundleResource, error) {
 	var (
 		sem    = semaphore.NewWeighted(4)
 		result = map[string][]fleet.BundleResource{}
@@ -223,12 +265,12 @@ func loadDirectories(ctx context.Context, compress bool, disableDepsUpdate bool,
 		dir := dir
 		eg.Go(func() error {
 			defer sem.Release(1)
-			resources, err := loadDirectory(ctx, compress, disableDepsUpdate, dir.prefix, dir.base, dir.source, dir.version, dir.auth)
+			resources, err := loadDirectory(ctx, opts, dir)
 			if err != nil {
 				return fmt.Errorf("loading directory %s, %s: %w", dir.prefix, dir.base, err)
 			}
 
-			key := dir.key
+			key := dir.prefix
 			if key == "" {
 				key = dir.source
 			}
diff --git a/internal/cmd/agent/controller/drift_controller.go b/internal/cmd/agent/controller/drift_controller.go
index 3b40a90a..6130775a 100644
--- a/internal/cmd/agent/controller/drift_controller.go
+++ b/internal/cmd/agent/controller/drift_controller.go
@@ -3,6 +3,7 @@ package controller
 import (
 	"context"
 	"fmt"
+	"time"
 
 	"github.com/rancher/fleet/internal/cmd/agent/deployer"
 	"github.com/rancher/fleet/internal/cmd/agent/deployer/driftdetect"
@@ -10,16 +11,18 @@ import (
 	fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 
 	"github.com/rancher/wrangler/v3/pkg/condition"
-
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
 	errutil "k8s.io/apimachinery/pkg/util/errors"
+	"k8s.io/client-go/util/workqueue"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 	"sigs.k8s.io/controller-runtime/pkg/source"
 )
 
@@ -31,14 +34,18 @@ type DriftReconciler struct {
 	Monitor     *monitor.Monitor
 	DriftDetect *driftdetect.DriftDetect
 
-	DriftChan chan event.GenericEvent
+	DriftChan chan event.TypedGenericEvent[*fleetv1.BundleDeployment]
 
 	Workers int
 }
 
+// enqueueDelay is used as an artificial delay for enqueuing BundleDeployment reconciliation requests
+// This allows aggregating multiple consecutive events on deployed resources, reducing the number of BundleDeployment (and Bundle) reconciliations at the cost of introducing a delay in the notification
+const enqueueDelay = 5 * time.Second
+
 // SetupWithManager sets up the controller with the Manager.
 func (r *DriftReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	src := source.Channel(r.DriftChan, &handler.EnqueueRequestForObject{})
+	src := source.Channel(r.DriftChan, enqueueRequestHandlerWithDelay(enqueueDelay))
 	return ctrl.NewControllerManagedBy(mgr).
 		Named("drift-reconciler").
 		WatchesRawSource(src).
@@ -126,3 +133,22 @@ func (r *DriftReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
 
 	return ctrl.Result{}, errutil.NewAggregate(merr)
 }
+
+// enqueueRequestHandlerWithDelay implements a TypedEventHandler that introduces a constant delay in the resources being enqueued
+// Due to how workqueue.TypedDelayingInterface's AddAfter is implemented, successive calls with the same key are aggregated.
+// Only implemented for Generic events, as this is only meant to be used from with source.Channel to receive internal events, not from an informer
+func enqueueRequestHandlerWithDelay(delay time.Duration) handler.TypedEventHandler[*fleetv1.BundleDeployment, reconcile.Request] {
+	return &handler.TypedFuncs[*fleetv1.BundleDeployment, reconcile.Request]{
+		GenericFunc: func(ctx context.Context, e event.TypedGenericEvent[*fleetv1.BundleDeployment], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
+			if e.Object == nil {
+				log.FromContext(ctx).Error(nil, "GenericEvent received with no metadata", "event", e)
+				return
+			}
+			w.AddAfter(reconcile.Request{NamespacedName: types.NamespacedName{
+				Name:      e.Object.GetName(),
+				Namespace: e.Object.GetNamespace(),
+			}}, delay)
+		},
+	}
+
+}
diff --git a/internal/cmd/agent/deployer/driftdetect/driftdetect.go b/internal/cmd/agent/deployer/driftdetect/driftdetect.go
index 7b336b75..c86f5486 100644
--- a/internal/cmd/agent/deployer/driftdetect/driftdetect.go
+++ b/internal/cmd/agent/deployer/driftdetect/driftdetect.go
@@ -22,7 +22,7 @@ type DriftDetect struct {
 	labelPrefix      string
 	labelSuffix      string
 
-	driftChan chan event.GenericEvent
+	driftChan chan event.TypedGenericEvent[*fleet.BundleDeployment]
 }
 
 func New(
@@ -31,7 +31,7 @@ func New(
 	defaultNamespace string,
 	labelPrefix string,
 	labelSuffix string,
-	driftChan chan event.GenericEvent,
+	driftChan chan event.TypedGenericEvent[*fleet.BundleDeployment],
 ) *DriftDetect {
 	return &DriftDetect{
 		trigger:          trigger,
@@ -63,7 +63,7 @@ func (d *DriftDetect) Refresh(ctx context.Context, bdKey string, bd *fleet.Bundl
 
 	handler := func(key string) {
 		logger.V(1).Info("Notifying driftdetect reconciler of a resource change", "triggeredBy", key)
-		d.driftChan <- event.GenericEvent{Object: bd}
+		d.driftChan <- event.TypedGenericEvent[*fleet.BundleDeployment]{Object: bd}
 
 	}
 
diff --git a/internal/cmd/agent/deployer/summary/summarizers.go b/internal/cmd/agent/deployer/summary/summarizers.go
index de5c0a15..f27af67b 100644
--- a/internal/cmd/agent/deployer/summary/summarizers.go
+++ b/internal/cmd/agent/deployer/summary/summarizers.go
@@ -103,20 +103,12 @@ var (
 		"Reconciling": "reconciling",
 	}
 
-	Summarizers          []Summarizer
-	ConditionSummarizers []Summarizer
+	Summarizers []Summarizer
 )
 
 type Summarizer func(obj data.Object, conditions []Condition, summary fleetv1.Summary) fleetv1.Summary
 
 func init() {
-	ConditionSummarizers = []Summarizer{
-		checkErrors,
-		checkTransitioning,
-		checkRemoving,
-		checkCattleReady,
-	}
-
 	Summarizers = []Summarizer{
 		checkStatusSummary,
 		checkErrors,
diff --git a/internal/cmd/agent/operator.go b/internal/cmd/agent/operator.go
index 9e6fc4d9..e6e63a9d 100644
--- a/internal/cmd/agent/operator.go
+++ b/internal/cmd/agent/operator.go
@@ -92,7 +92,7 @@ func start(
 	localCtx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
-	driftChan := make(chan event.GenericEvent)
+	driftChan := make(chan event.TypedGenericEvent[*v1alpha1.BundleDeployment])
 
 	reconciler, err := newReconciler(
 		ctx,
@@ -196,7 +196,7 @@ func newReconciler(
 	fleetNamespace string,
 	agentScope string,
 	agentConfig config.Config,
-	driftChan chan event.GenericEvent,
+	driftChan chan event.TypedGenericEvent[*v1alpha1.BundleDeployment],
 	workers int,
 ) (*controller.BundleDeploymentReconciler, error) {
 	upstreamClient := mgr.GetClient()
diff --git a/internal/cmd/controller/agentmanagement/agent/agent.go b/internal/cmd/controller/agentmanagement/agent/agent.go
index 5700909c..5f16a242 100644
--- a/internal/cmd/controller/agentmanagement/agent/agent.go
+++ b/internal/cmd/controller/agentmanagement/agent/agent.go
@@ -68,11 +68,12 @@ func AgentWithConfig(ctx context.Context, agentNamespace, controllerNamespace, a
 		return objs, err
 	}
 
+	// keep in sync with manageagent.go
 	mo := opts.ManifestOptions
 	mo.AgentImage = cfg.AgentImage
-	mo.SystemDefaultRegistry = cfg.SystemDefaultRegistry
 	mo.AgentImagePullPolicy = cfg.AgentImagePullPolicy
 	mo.CheckinInterval = cfg.AgentCheckinInterval.Duration.String()
+	mo.SystemDefaultRegistry = cfg.SystemDefaultRegistry
 	mo.BundleDeploymentWorkers = cfg.AgentWorkers.BundleDeployment
 	mo.DriftWorkers = cfg.AgentWorkers.Drift
 
diff --git a/internal/cmd/controller/agentmanagement/controllers/cluster/import.go b/internal/cmd/controller/agentmanagement/controllers/cluster/import.go
index 9541683e..0c1eb7a4 100644
--- a/internal/cmd/controller/agentmanagement/controllers/cluster/import.go
+++ b/internal/cmd/controller/agentmanagement/controllers/cluster/import.go
@@ -320,16 +320,14 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust
 				AgentTLSMode:              cfg.AgentTLSMode,
 				GarbageCollectionInterval: cfg.GarbageCollectionInterval,
 			},
+			// keep in sync with manageagent.go
 			ManifestOptions: agent.ManifestOptions{
-				AgentEnvVars:            cluster.Spec.AgentEnvVars,
-				AgentTolerations:        cluster.Spec.AgentTolerations,
-				CheckinInterval:         cfg.AgentCheckinInterval.Duration.String(),
-				PrivateRepoURL:          cluster.Spec.PrivateRepoURL,
-				AgentAffinity:           cluster.Spec.AgentAffinity,
-				AgentResources:          cluster.Spec.AgentResources,
-				HostNetwork:             *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)),
-				BundleDeploymentWorkers: cfg.AgentWorkers.BundleDeployment,
-				DriftWorkers:            cfg.AgentWorkers.Drift,
+				AgentEnvVars:     cluster.Spec.AgentEnvVars,
+				AgentTolerations: cluster.Spec.AgentTolerations,
+				PrivateRepoURL:   cluster.Spec.PrivateRepoURL,
+				AgentAffinity:    cluster.Spec.AgentAffinity,
+				AgentResources:   cluster.Spec.AgentResources,
+				HostNetwork:      *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)),
 			},
 		})
 	if err != nil {
diff --git a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go
index f1807618..ef3e4070 100644
--- a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go
+++ b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go
@@ -261,16 +261,19 @@ func (h *handler) newAgentBundle(ns string, cluster *fleet.Cluster) (runtime.Obj
 	objs := agent.Manifest(
 		agentNamespace, cluster.Spec.AgentNamespace,
 		agent.ManifestOptions{
-			AgentEnvVars:            cluster.Spec.AgentEnvVars,
+			// keep in sync with cluster/import.go
+			AgentEnvVars:     cluster.Spec.AgentEnvVars,
+			AgentTolerations: cluster.Spec.AgentTolerations,
+			PrivateRepoURL:   cluster.Spec.PrivateRepoURL,
+			AgentAffinity:    cluster.Spec.AgentAffinity,
+			AgentResources:   cluster.Spec.AgentResources,
+			HostNetwork:      *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)),
+
+			// keep in sync with agent/agent.go
 			AgentImage:              cfg.AgentImage,
 			AgentImagePullPolicy:    cfg.AgentImagePullPolicy,
-			AgentTolerations:        cluster.Spec.AgentTolerations,
 			CheckinInterval:         cfg.AgentCheckinInterval.Duration.String(),
-			PrivateRepoURL:          cluster.Spec.PrivateRepoURL,
 			SystemDefaultRegistry:   cfg.SystemDefaultRegistry,
-			AgentAffinity:           cluster.Spec.AgentAffinity,
-			AgentResources:          cluster.Spec.AgentResources,
-			HostNetwork:             *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)),
 			BundleDeploymentWorkers: cfg.AgentWorkers.BundleDeployment,
 			DriftWorkers:            cfg.AgentWorkers.Drift,
 		},
diff --git a/internal/cmd/controller/reconciler/bundle_controller.go b/internal/cmd/controller/reconciler/bundle_controller.go
index ead9fa20..8689bcba 100644
--- a/internal/cmd/controller/reconciler/bundle_controller.go
+++ b/internal/cmd/controller/reconciler/bundle_controller.go
@@ -245,12 +245,34 @@ func (r *BundleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
 	// build BundleDeployments out of targets discarding Status, replacing DependsOn with the
 	// bundle's DependsOn (pure function) and replacing the labels with the bundle's labels
 	for _, target := range matchedTargets {
-		bd, err := r.createBundleDeployment(
+		if target.Deployment == nil {
+			continue
+		}
+		if target.Deployment.Namespace == "" {
+			logger.V(1).Info(
+				"Skipping bundledeployment with empty namespace, waiting for agentmanagement to set cluster.status.namespace",
+				"bundledeployment", target.Deployment,
+			)
+			continue
+		}
+
+		// NOTE we don't use the existing BundleDeployment, we discard annotations, status, etc
+		// copy labels from Bundle as they might have changed
+		bd := target.BundleDeployment()
+
+		// No need to check the deletion timestamp here before adding a finalizer, since the bundle has just
+		// been created.
+		controllerutil.AddFinalizer(bd, bundleDeploymentFinalizer)
+
+		bd.Spec.OCIContents = contentsInOCI
+		bd.Spec.HelmChartOptions = bundle.Spec.HelmAppOptions
+
+		bd, err = r.createBundleDeployment(
 			ctx,
 			logger,
-			target,
+			bd,
 			contentsInOCI,
-			bundle.Spec.HelmAppOptions,
+			bundle.Spec.HelmAppOptions != nil,
 			manifestID)
 		if err != nil {
 			return ctrl.Result{}, err
@@ -323,34 +345,12 @@ func (r *BundleReconciler) addOrRemoveFinalizer(ctx context.Context, logger logr
 func (r *BundleReconciler) createBundleDeployment(
 	ctx context.Context,
 	logger logr.Logger,
-	target *target.Target,
+	bd *fleet.BundleDeployment,
 	contentsInOCI bool,
-	helmAppOptions *fleet.BundleHelmOptions,
+	contentsInHelmChart bool,
 	manifestID string,
 ) (*fleet.BundleDeployment, error) {
-	if target.Deployment == nil {
-		return nil, nil
-	}
-	if target.Deployment.Namespace == "" {
-		logger.V(1).Info(
-			"Skipping bundledeployment with empty namespace, waiting for agentmanagement to set cluster.status.namespace",
-			"bundledeployment", target.Deployment,
-		)
-		return nil, nil
-	}
-
-	// NOTE we don't use the existing BundleDeployment, we discard annotations, status, etc
-	// copy labels from Bundle as they might have changed
-	bd := target.BundleDeployment()
-
-	// No need to check the deletion timestamp here before adding a finalizer, since the bundle has just
-	// been created.
-	controllerutil.AddFinalizer(bd, bundleDeploymentFinalizer)
-
-	bd.Spec.OCIContents = contentsInOCI
-	bd.Spec.HelmChartOptions = helmAppOptions
 	logger = logger.WithValues("bundledeployment", bd, "deploymentID", bd.Spec.DeploymentID)
-	contentsInHelmChart := helmAppOptions != nil
 
 	// When content resources are stored in etcd, we need to add finalizers.
 	if !contentsInOCI && !contentsInHelmChart {



Test patch:
diff --git a/e2e/single-cluster/helm_verify_test.go b/e2e/single-cluster/helm_verify_test.go
index e8aa1704..0dc973e5 100644
--- a/e2e/single-cluster/helm_verify_test.go
+++ b/e2e/single-cluster/helm_verify_test.go
@@ -1,8 +1,11 @@
 package singlecluster_test
 
 import (
+	"slices"
+
 	"github.com/rancher/fleet/e2e/testenv"
 	"github.com/rancher/fleet/e2e/testenv/kubectl"
+	fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -48,6 +51,25 @@ var _ = Describe("Helm Chart Values", func() {
 				ContainSubstring(`"name":"local"`),
 				ContainSubstring(`"url":"local"`),
 			))
+
+			By("checking apply configs are not in the resources", func() {
+				out, err := k.Get("bundles", "helm-verify-test-helm-verify", "-o", "jsonpath={.spec}")
+				Expect(err).ToNot(HaveOccurred(), out)
+
+				var b fleetv1.BundleSpec
+				err = yaml.Unmarshal([]byte(out), &b)
+				Expect(err).ToNot(HaveOccurred())
+
+				names := slices.Collect(func(yield func(string) bool) {
+					for _, r := range b.Resources {
+						if !yield(r.Name) {
+							return
+						}
+					}
+				})
+				Expect(names).NotTo(ContainElement("fleet.yaml"))
+				Expect(names).NotTo(ContainElement("values.yaml"))
+			})
 		})
 	})
 
diff --git a/e2e/single-cluster/single_cluster_test.go b/e2e/single-cluster/single_cluster_test.go
index 8308d265..f1d0d801 100644
--- a/e2e/single-cluster/single_cluster_test.go
+++ b/e2e/single-cluster/single_cluster_test.go
@@ -73,14 +73,6 @@ var _ = Describe("Single Cluster Deployments", func() {
 				_, _ = k.Delete("ns", "test-fleet-mp-service")
 			})
 
-			It("sets status fields for gitrepo on deployment", func() {
-				Eventually(func() bool {
-					out, err := k.Get("gitrepo", "multiple-paths", "-n", "fleet-local", "-o", "jsonpath='{.status.summary}'")
-					Expect(err).ToNot(HaveOccurred(), out)
-					return strings.Contains(out, "\"ready\":2")
-				}).Should(BeTrue())
-			})
-
 			It("deploys bundles from all the paths", func() {
 				Eventually(func() string {
 					out, _ := k.Namespace("fleet-local").Get("bundles")
@@ -90,6 +82,12 @@ var _ = Describe("Single Cluster Deployments", func() {
 					ContainSubstring("multiple-paths-multiple-paths-service"),
 				))
 
+				Eventually(func() bool {
+					out, err := k.Get("gitrepo", "multiple-paths", "-n", "fleet-local", "-o", "jsonpath='{.status.summary}'")
+					Expect(err).ToNot(HaveOccurred(), out)
+					return strings.Contains(out, "\"ready\":2")
+				}).Should(BeTrue())
+
 				Eventually(func(g Gomega) {
 					out, err := k.Get(
 						"bundle",
diff --git a/integrationtests/agent/suite_test.go b/integrationtests/agent/suite_test.go
index bf2670e0..b02b8457 100644
--- a/integrationtests/agent/suite_test.go
+++ b/integrationtests/agent/suite_test.go
@@ -95,7 +95,7 @@ var _ = BeforeSuite(func() {
 
 	setupFakeContents()
 
-	driftChan := make(chan event.GenericEvent)
+	driftChan := make(chan event.TypedGenericEvent[*v1alpha1.BundleDeployment])
 
 	// Set up the bundledeployment reconciler
 	Expect(k8sClient.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: clusterNS}})).ToNot(HaveOccurred())
@@ -133,7 +133,7 @@ var _ = AfterSuite(func() {
 // newReconciler creates a new BundleDeploymentReconciler that will watch for changes
 // in the test Fleet namespace, using configuration from the provided manager.
 // Resources are provided by the lookup parameter.
-func newReconciler(ctx context.Context, mgr manager.Manager, lookup *lookup, driftChan chan event.GenericEvent) *controller.BundleDeploymentReconciler {
+func newReconciler(ctx context.Context, mgr manager.Manager, lookup *lookup, driftChan chan event.TypedGenericEvent[*v1alpha1.BundleDeployment]) *controller.BundleDeploymentReconciler {
 	upstreamClient := mgr.GetClient()
 	// re-use client, since this is a single cluster test
 	localClient := upstreamClient
diff --git a/integrationtests/cli/apply/apply_online_test.go b/integrationtests/cli/apply/apply_online_test.go
index 9f1d52fa..d867380f 100644
--- a/integrationtests/cli/apply/apply_online_test.go
+++ b/integrationtests/cli/apply/apply_online_test.go
@@ -67,7 +67,7 @@ var _ = Describe("Fleet apply online", Label("online"), func() {
 					Name:      "test_labels",
 				},
 			}
-			//bundle in the fleet.yaml file; some values are autofilled in the implementation (this is why there are not only the labels)
+			// bundle in the cm.yaml file; some values are autofilled in the implementation (this is why there are not only the labels)
 			newBundle = &fleet.Bundle{
 				ObjectMeta: metav1.ObjectMeta{
 					Labels: map[string]string{
@@ -79,8 +79,14 @@ var _ = Describe("Fleet apply online", Label("online"), func() {
 				Spec: fleet.BundleSpec{
 					Resources: []fleet.BundleResource{
 						{
-							Name:    "fleet.yaml",
-							Content: "labels:\n  new: fleet-label2",
+							Name: "cm.yaml",
+							Content: `apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cm3
+data:
+  test: "value23"
+`,
 						},
 					},
 					Targets: []fleet.BundleTarget{
diff --git a/integrationtests/cli/apply/apply_test.go b/integrationtests/cli/apply/apply_test.go
index f8a2f610..680fe16b 100644
--- a/integrationtests/cli/apply/apply_test.go
+++ b/integrationtests/cli/apply/apply_test.go
@@ -92,15 +92,12 @@ var _ = Describe("Fleet apply", Ordered, func() {
 			deploymentB := bundle[1]
 			deploymentC := bundle[2]
 
-			Expect(deploymentA.Spec.Resources).To(HaveLen(2))
-			Expect(deploymentB.Spec.Resources).To(HaveLen(2))
-			Expect(deploymentC.Spec.Resources).To(HaveLen(2))
+			Expect(deploymentA.Spec.Resources).To(HaveLen(1))
+			Expect(deploymentB.Spec.Resources).To(HaveLen(1))
+			Expect(deploymentC.Spec.Resources).To(HaveLen(1))
 
-			Expect(cli.AssetsPath + "nested_multiple/deploymentA/fleet.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_multiple/deploymentA/svc/svc.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources))
-			Expect(cli.AssetsPath + "nested_multiple/deploymentB/fleet.yaml").To(bePresentInBundleResources(deploymentB.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_multiple/deploymentB/svc/nested/svc.yaml").To(bePresentInBundleResources(deploymentB.Spec.Resources))
-			Expect(cli.AssetsPath + "nested_multiple/deploymentC/fleet.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_multiple/deploymentC/deployment.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources))
 		})
 	})
@@ -119,17 +116,14 @@ var _ = Describe("Fleet apply", Ordered, func() {
 			deploymentA := bundle[1]
 			deploymentC := bundle[2]
 
-			Expect(deploymentA.Spec.Resources).To(HaveLen(2))
+			Expect(deploymentA.Spec.Resources).To(HaveLen(1))
 			Expect(deploymentC.Spec.Resources).To(HaveLen(1))
-			Expect(root.Spec.Resources).To(HaveLen(5))
+			Expect(root.Spec.Resources).To(HaveLen(6))
 
-			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/fleet.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/deployment.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources))
-			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentC/fleet.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources))
-			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/fleet.yaml").To(bePresentInBundleResources(root.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/deployment.yaml").To(bePresentInBundleResources(root.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentB/svc.yaml").To(bePresentInBundleResources(root.Spec.Resources))
-			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentC/fleet.yaml").To(bePresentInBundleResources(root.Spec.Resources))
+			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentC/cm.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources))
 			Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentD/deployment.yaml").To(bePresentInBundleResources(root.Spec.Resources))
 		})
 	})
@@ -262,13 +256,16 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 			bundle, err := cli.GetBundleFromOutput(buf)
 			Expect(err).NotTo(HaveOccurred())
 			// files expected are:
-			// Chart.yaml + values.yaml + templates/configmap.yaml + fleet.yaml +
+			// Chart.yaml + values.yaml + templates/configmap.yaml +
 			// Chart.lock + charts/config-chart-0.1.0.tgz
-			Expect(bundle.Spec.Resources).To(HaveLen(6))
+			Expect(bundle.Spec.Resources).To(HaveLen(5))
 			files, err := getAllFilesInDir(tmpDirRel)
 			Expect(err).NotTo(HaveOccurred())
-			Expect(files).To(HaveLen(len(bundle.Spec.Resources)))
+			Expect(files).To(HaveLen(len(bundle.Spec.Resources) + 1))
 			for _, file := range files {
+				if strings.HasSuffix(file, "fleet.yaml") {
+					continue
+				}
 				Expect(file).To(bePresentInBundleResources(bundle.Spec.Resources))
 			}
 			// explicitly check for dependency files
@@ -286,12 +283,15 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 			bundle, err := cli.GetBundleFromOutput(buf)
 			Expect(err).NotTo(HaveOccurred())
 			// files expected are:
-			// Chart.yaml + values.yaml + templates/configmap.yaml + fleet.yaml
-			Expect(bundle.Spec.Resources).To(HaveLen(4))
+			// Chart.yaml + values.yaml + templates/configmap.yaml
+			Expect(bundle.Spec.Resources).To(HaveLen(3))
 			files, err := getAllFilesInDir(tmpDirRel)
 			Expect(err).NotTo(HaveOccurred())
-			Expect(files).To(HaveLen(len(bundle.Spec.Resources)))
+			Expect(files).To(HaveLen(len(bundle.Spec.Resources) + 1))
 			for _, file := range files {
+				if strings.HasSuffix(file, "fleet.yaml") {
+					continue
+				}
 				Expect(file).To(bePresentInBundleResources(bundle.Spec.Resources))
 			}
 			// explicitly check for dependency files (they should not exist in the file system nor in bundle resources)
@@ -311,10 +311,10 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 			bundle, err := cli.GetBundleFromOutput(buf)
 			Expect(err).NotTo(HaveOccurred())
 			// expected files are:
-			// fleet.yaml + Chart.yaml + values.yaml + templates/configmap.yaml +
+			// Chart.yaml + values.yaml + templates/configmap.yaml +
 			// Chart.lock + charts/config-chart-0.1.0.tgz
-			Expect(bundle.Spec.Resources).To(HaveLen(6))
-			Expect(path.Join(tmpDirRel, "fleet.yaml")).To(bePresentInBundleResources(bundle.Spec.Resources))
+			Expect(bundle.Spec.Resources).To(HaveLen(5))
+			Expect(path.Join(tmpDirRel, "fleet.yaml")).NotTo(bePresentInBundleResources(bundle.Spec.Resources))
 			// as files were unpacked from the downloaded chart we can't just
 			// list the files in the original folder and compare.
 			// Files are only located in the bundle resources
@@ -335,10 +335,9 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 			bundle, err := cli.GetBundleFromOutput(buf)
 			Expect(err).NotTo(HaveOccurred())
 			// expected files are:
-			// fleet.yaml +
 			// Chart.yaml + values.yaml + templates/configmap.yaml
-			Expect(bundle.Spec.Resources).To(HaveLen(4))
-			Expect(path.Join(tmpDirRel, "fleet.yaml")).To(bePresentInBundleResources(bundle.Spec.Resources))
+			Expect(bundle.Spec.Resources).To(HaveLen(3))
+			Expect(path.Join(tmpDirRel, "fleet.yaml")).NotTo(bePresentInBundleResources(bundle.Spec.Resources))
 			// as files were unpacked from the downloaded chart we can't just
 			// list the files in the original folder and compare.
 			// Files are only located in the bundle resources
@@ -369,10 +368,9 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 
 			// remoteDepl corresponds to multi-chart/remote-chart-with-deps
 			// expected files are:
-			// fleet.yaml +
 			// Chart.yaml + values.yaml + templates/configmap.yaml + Chart.lock + charts/config-chart-0.1.0.tgz
-			Expect(remoteDepl.Spec.Resources).To(HaveLen(6))
-			Expect(path.Join(tmpDirRel, "remote-chart-with-deps", "fleet.yaml")).To(bePresentInBundleResources(remoteDepl.Spec.Resources))
+			Expect(remoteDepl.Spec.Resources).To(HaveLen(5))
+			Expect(path.Join(tmpDirRel, "remote-chart-with-deps", "fleet.yaml")).NotTo(bePresentInBundleResources(remoteDepl.Spec.Resources))
 			// as files were unpacked from the downloaded chart we can't just
 			// list the files in the original folder and compare.
 			// Files are only located in the bundle resources
@@ -384,13 +382,16 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 
 			// simpleDepl corresponds to multi-chart/simple-with-fleet-yaml
 			// expected files are:
-			// fleet.yaml + Chart.yaml + values.yaml + templates/configmap.yaml +
+			// Chart.yaml + values.yaml + templates/configmap.yaml +
 			// Chart.lock + charts/config-chart-0.1.0.tgz
-			Expect(simpleDepl.Spec.Resources).To(HaveLen(6))
+			Expect(simpleDepl.Spec.Resources).To(HaveLen(5))
 			files, err := getAllFilesInDir(path.Join(tmpDirRel, "simple-with-fleet-yaml"))
 			Expect(err).NotTo(HaveOccurred())
-			Expect(files).To(HaveLen(len(simpleDepl.Spec.Resources)))
+			Expect(files).To(HaveLen(len(simpleDepl.Spec.Resources) + 1))
 			for _, file := range files {
+				if strings.HasSuffix(file, "fleet.yaml") {
+					continue
+				}
 				Expect(file).To(bePresentInBundleResources(simpleDepl.Spec.Resources))
 			}
 			// explicitly check for dependency files
@@ -399,12 +400,15 @@ var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func
 
 			// noDepsDepl corresponds to multi-char/simple-with-fleet-yaml-no-deps
 			// expected files are:
-			// Chart.yaml + fleet.yaml + values.yaml + templates/configmap.yaml
-			Expect(noDepsDepl.Spec.Resources).To(HaveLen(4))
+			// Chart.yaml +  values.yaml + templates/configmap.yaml
+			Expect(noDepsDepl.Spec.Resources).To(HaveLen(3))
 			files, err = getAllFilesInDir(path.Join(tmpDirRel, "simple-with-fleet-yaml-no-deps"))
 			Expect(err).NotTo(HaveOccurred())
-			Expect(files).To(HaveLen(len(noDepsDepl.Spec.Resources)))
+			Expect(files).To(HaveLen(len(noDepsDepl.Spec.Resources) + 1))
 			for _, file := range files {
+				if strings.HasSuffix(file, "fleet.yaml") {
+					continue
+				}
 				Expect(file).To(bePresentInBundleResources(noDepsDepl.Spec.Resources))
 			}
 			// explicitly check for dependency files (they should not exist in the file system nor in bundle resources)
diff --git a/integrationtests/cli/apply/helm_test.go b/integrationtests/cli/apply/helm_test.go
index 30ad347c..53a2df50 100644
--- a/integrationtests/cli/apply/helm_test.go
+++ b/integrationtests/cli/apply/helm_test.go
@@ -235,8 +235,7 @@ func verifyResourcesArePresent() bool {
 	paths, err := getAllResourcesPathFromTheHelmRelease()
 	Expect(err).NotTo(HaveOccurred())
 	Expect(paths).Should(HaveLen(numberOfFilesInHelmConfigChart))
-	// should contain all resources plus the fleet.yaml
-	Expect(bundle.Spec.Resources).Should(HaveLen(numberOfFilesInHelmConfigChart + 1))
+	Expect(bundle.Spec.Resources).Should(HaveLen(numberOfFilesInHelmConfigChart))
 	for _, path := range paths {
 		present, err := cli.IsResourcePresentInBundle(path, bundle.Spec.Resources)
 		Expect(err).NotTo(HaveOccurred(), "validating resource: "+path)
diff --git a/integrationtests/cli/assets/labels_update/cm.yaml b/integrationtests/cli/assets/labels_update/cm.yaml
new file mode 100644
index 00000000..bd8f0350
--- /dev/null
+++ b/integrationtests/cli/assets/labels_update/cm.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cm3
+data:
+  test: "value23"
diff --git a/integrationtests/cli/assets/nested_mixed_two_levels/nested/deploymentC/cm.yaml b/integrationtests/cli/assets/nested_mixed_two_levels/nested/deploymentC/cm.yaml
new file mode 100644
index 00000000..5599d179
--- /dev/null
+++ b/integrationtests/cli/assets/nested_mixed_two_levels/nested/deploymentC/cm.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cm1
+data:
+  test: "value123"
diff --git a/integrationtests/cli/assets/targets/override/cm.yaml b/integrationtests/cli/assets/targets/override/cm.yaml
new file mode 100644
index 00000000..5599d179
--- /dev/null
+++ b/integrationtests/cli/assets/targets/override/cm.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cm1
+data:
+  test: "value123"
diff --git a/integrationtests/cli/assets/targets/simple/cm.yaml b/integrationtests/cli/assets/targets/simple/cm.yaml
new file mode 100644
index 00000000..585a494f
--- /dev/null
+++ b/integrationtests/cli/assets/targets/simple/cm.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cm2
+data:
+  test: "value1234"
diff --git a/internal/bundlereader/loaddirectory_test.go b/internal/bundlereader/loaddirectory_test.go
index 4d85b5e1..4c83a2ba 100644
--- a/internal/bundlereader/loaddirectory_test.go
+++ b/internal/bundlereader/loaddirectory_test.go
@@ -36,6 +36,20 @@ func TestGetContent(t *testing.T) {
 				isDir: true,
 				name:  "no-fleetignore",
 				children: []fsNode{
+					{
+						name:     "fleet.yaml",
+						contents: "foo",
+					},
+					{
+						name:  "chart",
+						isDir: true,
+						children: []fsNode{
+							{
+								name:     "myvalues.yaml",
+								contents: "bar",
+							},
+						},
+					},
 					{
 						name:     "something.yaml",
 						contents: "foo",
@@ -52,6 +66,10 @@ func TestGetContent(t *testing.T) {
 				isDir: true,
 				name:  "empty-fleetignore",
 				children: []fsNode{
+					{
+						name:     "fleet.yaml",
+						contents: "foo",
+					},
 					{
 						name:     "something.yaml",
 						contents: "foo",
@@ -529,12 +547,14 @@ func TestGetContent(t *testing.T) {
 
 	defer os.RemoveAll(base)
 
+	ignoreApplyConfigs := []string{"fleet.yaml", "chart/myvalues.yaml"}
+
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
 
 			root := createDirStruct(t, base, c.directoryStructure)
 
-			files, err := bundlereader.GetContent(context.Background(), root, root, "", bundlereader.Auth{}, false)
+			files, err := bundlereader.GetContent(context.Background(), root, root, "", bundlereader.Auth{}, false, ignoreApplyConfigs)
 			assert.NoError(t, err)
 
 			assert.Equal(t, len(c.expectedFiles), len(files))
@@ -617,7 +637,7 @@ func TestGetContentOCI(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
-			result, err := bundlereader.GetContent(context.Background(), base, c.source, c.version, bundlereader.Auth{}, false)
+			result, err := bundlereader.GetContent(context.Background(), base, c.source, c.version, bundlereader.Auth{}, false, []string{})
 			if c.expectedErr == "" {
 				assert.NoError(err)
 				for k := range result {
diff --git a/internal/cmd/agent/deployer/monitor/updatestatus.go b/internal/cmd/agent/deployer/monitor/updatestatus.go
index f950e48c..018f83f6 100644
--- a/internal/cmd/agent/deployer/monitor/updatestatus.go
+++ b/internal/cmd/agent/deployer/monitor/updatestatus.go
@@ -253,7 +253,7 @@ func calculateResourceCounts(all []fleet.BundleDeploymentResource, nonReady []fl
 	// The agent must have enough visibility to determine the exact state of every resource.
 	// e.g. "WaitApplied" or "Unknown" states do not make sense in this context
 	counts := fleet.ResourceCounts{
-		DesiredReady: len(all),
+		DesiredReady: calculateDesiredReady(resourceKeys, modified),
 	}
 	for _, r := range modified {
 		if r.Create {
@@ -290,6 +290,28 @@ func calculateResourceCounts(all []fleet.BundleDeploymentResource, nonReady []fl
 	return counts
 }
 
+// calculateDesiredReady retrieves the number of total resources to be deployed.
+// A ResourceKey set is obtained from plan.Objects, which only includes living objects in the cluster, so it needs to be extended with  resources in "Missing" state.
+func calculateDesiredReady(liveResourceKeys map[fleet.ResourceKey]struct{}, modified []fleet.ModifiedStatus) int {
+	desired := len(liveResourceKeys)
+	for _, r := range modified {
+		if !r.Create {
+			continue
+		}
+		// Missing resource state
+		// Increase desired count if not already present in the resource keys set
+		if _, ok := liveResourceKeys[fleet.ResourceKey{
+			Kind:       r.Kind,
+			APIVersion: r.APIVersion,
+			Namespace:  r.Namespace,
+			Name:       r.Name,
+		}]; !ok {
+			desired++
+		}
+	}
+	return desired
+}
+
 func nonReady(ctx context.Context, plan desiredset.Plan, ignoreOptions fleet.IgnoreOptions) (result []fleet.NonReadyStatus) {
 	logger := log.FromContext(ctx)
 	defer func() {
diff --git a/internal/cmd/agent/deployer/monitor/updatestatus_test.go b/internal/cmd/agent/deployer/monitor/updatestatus_test.go
index 819e03f4..ceadf586 100644
--- a/internal/cmd/agent/deployer/monitor/updatestatus_test.go
+++ b/internal/cmd/agent/deployer/monitor/updatestatus_test.go
@@ -82,14 +82,7 @@ func Test_updateFromResources(t *testing.T) {
 		{
 			name: "missing",
 			args: args{
-				resources: []fleet.BundleDeploymentResource{
-					{
-						Kind:       "ConfigMap",
-						APIVersion: "v1",
-						Namespace:  "testns",
-						Name:       "testcm",
-					},
-				},
+				resources: []fleet.BundleDeploymentResource{},
 				modified: []fleet.ModifiedStatus{
 					{
 						Kind:       "ConfigMap",
@@ -104,7 +97,7 @@ func Test_updateFromResources(t *testing.T) {
 				assert.Equal(t, status.ResourceCounts, fleet.ResourceCounts{DesiredReady: 1, Missing: 1})
 				assert.Truef(t, status.Ready, "unexpected ready status")
 				assert.Falsef(t, status.NonModified, "unexpected non-modified status")
-				assert.Lenf(t, status.Resources, 1, "unexpected resources length")
+				assert.Emptyf(t, status.Resources, "expected resources to be empty")
 				assert.Len(t, status.ModifiedStatus, 1, "incorrect modified status length")
 				assert.True(t, status.ModifiedStatus[0].Create)
 				assert.Emptyf(t, status.NonReadyStatus, "expected non-ready status to be empty")
@@ -145,12 +138,6 @@ func Test_updateFromResources(t *testing.T) {
 			name: "missing and non-ready",
 			args: args{
 				resources: []fleet.BundleDeploymentResource{
-					{
-						Kind:       "ConfigMap",
-						APIVersion: "v1",
-						Namespace:  "testns",
-						Name:       "testcm",
-					},
 					{
 						Kind:       "Pod",
 						APIVersion: "v1",
@@ -164,7 +151,7 @@ func Test_updateFromResources(t *testing.T) {
 						APIVersion: "v1",
 						Namespace:  "testns",
 						Name:       "testcm",
-						Patch:      `{"data": {"foo": "bar"}`,
+						Create:     true,
 					},
 				},
 				nonReady: []fleet.NonReadyStatus{
@@ -182,29 +169,20 @@ func Test_updateFromResources(t *testing.T) {
 				},
 			},
 			assert: func(t *testing.T, status fleet.BundleDeploymentStatus) {
-				assert.Equal(t, status.ResourceCounts, fleet.ResourceCounts{DesiredReady: 2, Modified: 1, NotReady: 1})
+				assert.Equal(t, status.ResourceCounts, fleet.ResourceCounts{DesiredReady: 2, Missing: 1, NotReady: 1})
 				assert.Falsef(t, status.Ready, "unexpected ready status")
 				assert.Falsef(t, status.NonModified, "unexpected non-modified status")
-				assert.Lenf(t, status.Resources, 2, "unexpected resources length")
+				assert.Lenf(t, status.Resources, 1, "unexpected resources length")
 				assert.Len(t, status.NonReadyStatus, 1, "incorrect non-ready status length")
 				assert.NotEmptyf(t, status.NonReadyStatus[0].Summary, "unexpected empty summary for non-ready resource")
 				assert.Len(t, status.ModifiedStatus, 1, "incorrect modified status length")
-				assert.NotEmpty(t, status.ModifiedStatus[0].Patch)
+				assert.True(t, status.ModifiedStatus[0].Create)
 			},
 		},
 		{
 			name: "non-ready and modified status lists have a max length",
 			args: args{
 				resources: func(n int) []fleet.BundleDeploymentResource {
-					cms := make([]fleet.BundleDeploymentResource, n)
-					for x := range n {
-						cms[x] = fleet.BundleDeploymentResource{
-							Kind:       "ConfigMap",
-							APIVersion: "v1",
-							Namespace:  "testns",
-							Name:       fmt.Sprintf("testcm-%d", x),
-						}
-					}
 					pods := make([]fleet.BundleDeploymentResource, n)
 					for x := range n {
 						pods[x] = fleet.BundleDeploymentResource{
@@ -214,7 +192,7 @@ func Test_updateFromResources(t *testing.T) {
 							Name:       fmt.Sprintf("pod-%d", x),
 						}
 					}
-					return append(cms, pods...)
+					return pods
 				}(12),
 				nonReady: func(n int) []fleet.NonReadyStatus {
 					pods := make([]fleet.NonReadyStatus, n)
@@ -249,7 +227,7 @@ func Test_updateFromResources(t *testing.T) {
 				assert.Equal(t, status.ResourceCounts, fleet.ResourceCounts{DesiredReady: 24, Missing: 12, NotReady: 12})
 				assert.Falsef(t, status.Ready, "unexpected ready status")
 				assert.Falsef(t, status.NonModified, "unexpected non-modified status")
-				assert.Lenf(t, status.Resources, 24, "unexpected resources length")
+				assert.Lenf(t, status.Resources, 12, "unexpected resources length")
 
 				assert.Len(t, status.NonReadyStatus, 10, "non-ready status length exceeds maximum")
 				assert.Len(t, status.ModifiedStatus, 10, "incorrect modified exceeds maximum")
diff --git a/internal/ociwrapper/ociwrapper_test.go b/internal/ociwrapper/ociwrapper_test.go
index b7077ff4..aa7c08cb 100644
--- a/internal/ociwrapper/ociwrapper_test.go
+++ b/internal/ociwrapper/ociwrapper_test.go
@@ -14,16 +14,16 @@ import (
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"oras.land/oras-go/v2"
 	"oras.land/oras-go/v2/content"
+	orasmemory "oras.land/oras-go/v2/content/memory"
 	"oras.land/oras-go/v2/registry/remote/auth"
 	"oras.land/oras-go/v2/registry/remote/retry"
 
+	"github.com/rancher/fleet/internal/manifest"
+	"github.com/rancher/fleet/internal/mocks"
 	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"github.com/rancher/fleet/internal/manifest"
-	"github.com/rancher/fleet/internal/mocks"
-	orasmemory "oras.land/oras-go/v2/content/memory"
 )
 
 type MockOrasOperator struct {
@@ -121,24 +121,6 @@ var _ = Describe("OCIUtils tests", func() {
 		err := oci.PushManifest(context.Background(), opts, "123", manifest)
 		Expect(err.Error()).To(Equal("ERROR PACKING"))
 	})
-	It("returns an error when is unable to connect to the registry", func() {
-		oci := NewOCIWrapper()
-		opts := OCIOpts{
-			Reference: "127.0.0.0:2334",
-		}
-		manifest := &manifest.Manifest{
-			Commit: "123456",
-			Resources: []fleet.BundleResource{
-				{
-					Name:     "resource1",
-					Content:  "Content1",
-					Encoding: "encoding1",
-				},
-			},
-		}
-		err := oci.PushManifest(context.Background(), opts, "123", manifest)
-		Expect(err.Error()).To(Or(ContainSubstring("connection refused"), ContainSubstring("i/o timeout")))
-	})
 	It("returns an OCI repository with the expected values when using basic HTTP", func() {
 		opts := OCIOpts{
 			Reference: "test.com",