Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ ARCH ?= $(shell go env GOARCH)
OS ?= $(shell uname -s | tr A-Z a-z)
K8S_LATEST_VER ?= $(shell curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)
export CONTROLLER_IMG ?= $(REGISTRY)/$(IMAGE_NAME)
TAG ?= v1.14.0
TAG ?= main

## Tool Binaries
CONTROLLER_GEN := $(TOOLS_BIN_DIR)/controller-gen
Expand Down
2 changes: 1 addition & 1 deletion config/default/manager_auth_proxy_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ spec:
- "--shard-key="
- --capi-onboard-annotation=
- "--v=5"
- "--version=v1.14.0"
- "--version=main"
- "--registry="
- "--agent-in-mgmt-cluster=false"
env:
Expand Down
4 changes: 2 additions & 2 deletions config/default/manager_image_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ spec:
template:
spec:
initContainers:
- image: docker.io/projectsveltos/classifier:v1.14.0
- image: docker.io/projectsveltos/classifier:main
name: migrate
containers:
# Change the value of image field below to your controller image URL
- image: docker.io/projectsveltos/classifier:v1.14.0
- image: docker.io/projectsveltos/classifier:main
name: manager
101 changes: 90 additions & 11 deletions controllers/classifier_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ const (
// * **Strategic Merge Patch**
// * **JSON Patch (RFC6902)**
sveltosApplierOverrideAnnotation = "sveltosapplier.projectsveltos.io/config-override-ref"

// This optional annotation restricts sveltos-agent's namespace-scoped watch mode (agentless
// mode only, requires a Sveltos Enterprise license granting NamespaceScopedAgents -- see
// sveltos-agent's own VerifyNamespaceScopeLicense) to the comma-separated namespaces it
// names. Same annotation key addon-controller's drift-detection-manager deploy path already
// uses, so one annotation on the Cluster/SveltosCluster controls both agents.
agentWatchNamespacesAnnotation = "agent.projectsveltos.io/watch-namespaces"
)

func getSveltosAgentNamespace(sveltosNamespace string) string {
Expand Down Expand Up @@ -584,10 +591,15 @@ func deploySveltosAgentWithKubeconfigInCluster(ctx context.Context, c client.Cli
return err
}

watchNamespaces, err := getAgentWatchNamespaces(ctx, c, clusterNamespace, clusterName, clusterType, logger)
if err != nil {
return err
}

logger.V(logs.LogDebug).Info("Deploying sveltos agent")
// Deploy SveltosAgent
err = deploySveltosAgentInManagedCluster(ctx, remoteRestConfig, clusterNamespace, clusterName, applicant,
"send-reports", clusterType, patches, false, logger)
"send-reports", clusterType, patches, false, watchNamespaces, logger)
if err != nil {
return err
}
Expand Down Expand Up @@ -989,6 +1001,23 @@ func (r *ClassifierReconciler) getCurrentHash(ctx context.Context, classifierSco
currentHash = h.Sum(nil)
}

watchNamespaces, err := getAgentWatchNamespaces(ctx, r.Client, cluster.Namespace, cluster.Name,
clusterproxy.GetClusterType(cluster), logger)
if err != nil {
return nil, err
}
if len(watchNamespaces) > 0 {
// Without this, a change to agentWatchNamespacesAnnotation on the Cluster/SveltosCluster
// still requeues the Classifier (predicates.ClusterPredicate/SveltosClusterPredicates
// already diff annotations wholesale), but processClassifier's isConfigSame check would
// find this hash unchanged and skip the redeploy that would actually apply the new value
// -- see getAgentWatchNamespaces' doc comment.
h := sha256.New()
h.Write(currentHash)
h.Write([]byte(strings.Join(watchNamespaces, ",")))
currentHash = h.Sum(nil)
}

var kubeconfig []byte
if r.ClassifierReportMode == AgentSendReportsNoGateway {
h := sha256.New()
Expand Down Expand Up @@ -1566,7 +1595,7 @@ func deployReloaderReportCRD(ctx context.Context, clusterNamespace, clusterName,
}

func prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, mode string,
clusterType libsveltosv1beta1.ClusterType) string {
clusterType libsveltosv1beta1.ClusterType, watchNamespaces []string) string {

if mode != "do-not-send-reports" {
agentYAML = strings.ReplaceAll(agentYAML, "do-not-send-reports", "send-reports")
Expand All @@ -1575,6 +1604,7 @@ func prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, mode stri
agentYAML = strings.ReplaceAll(agentYAML, "cluster-namespace=", fmt.Sprintf("cluster-namespace=%s", clusterNamespace))
agentYAML = strings.ReplaceAll(agentYAML, "cluster-name=", fmt.Sprintf("cluster-name=%s", clusterName))
agentYAML = strings.ReplaceAll(agentYAML, "cluster-type=", fmt.Sprintf("cluster-type=%s", clusterType))
agentYAML = strings.ReplaceAll(agentYAML, "watch-namespaces=", fmt.Sprintf("watch-namespaces=%s", strings.Join(watchNamespaces, ",")))
agentYAML = strings.ReplaceAll(agentYAML, "v=5", "v=0")

if getSveltosAgentEnableNATS() {
Expand Down Expand Up @@ -1645,18 +1675,23 @@ func deploySveltosAgent(ctx context.Context, c client.Client, clusterNamespace,
return err
}

watchNamespaces, err := getAgentWatchNamespaces(ctx, c, clusterNamespace, clusterName, clusterType, logger)
if err != nil {
return err
}

// Deploy SveltosAgent
if isPullMode {
err = deploySveltosAgentInManagedCluster(ctx, nil, clusterNamespace,
clusterName, classifierName, "do-not-send-reports", clusterType, patches, true, logger)
clusterName, classifierName, "do-not-send-reports", clusterType, patches, true, watchNamespaces, logger)
if err != nil {
return err
}
} else if startInMgmtCluster {
// Use management cluster restConfig
restConfig := getManagementClusterConfig()
return deploySveltosAgentInManagementCluster(ctx, restConfig, c, clusterNamespace, clusterName,
classifierName, "do-not-send-reports", clusterType, patches, logger)
classifierName, "do-not-send-reports", clusterType, patches, watchNamespaces, logger)
} else {
// Use managed cluster restConfig
remoteRestConfig, err := clustercache.GetManager().GetKubernetesRestConfig(ctx, c, clusterNamespace, clusterName,
Expand All @@ -1671,7 +1706,7 @@ func deploySveltosAgent(ctx context.Context, c client.Client, clusterNamespace,
return err
}
err = deploySveltosAgentInManagedCluster(ctx, remoteRestConfig, clusterNamespace,
clusterName, classifierName, "do-not-send-reports", clusterType, patches, false, logger)
clusterName, classifierName, "do-not-send-reports", clusterType, patches, false, watchNamespaces, logger)
if err != nil {
return err
}
Expand All @@ -1686,12 +1721,12 @@ func replaceRegistry(agentYAML, registry string) string {

func deploySveltosAgentInManagedCluster(ctx context.Context, remoteRestConfig *rest.Config,
clusterNamespace, clusterName, classifierName, mode string, clusterType libsveltosv1beta1.ClusterType,
patches []libsveltosv1beta1.Patch, isPullMode bool, logger logr.Logger) error {
patches []libsveltosv1beta1.Patch, isPullMode bool, watchNamespaces []string, logger logr.Logger) error {

logger.V(logs.LogDebug).Info("deploy sveltos-agent in the managed cluster")

agentYAML := string(agent.GetSveltosAgentYAML())
agentYAML = prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, mode, clusterType)
agentYAML = prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, mode, clusterType, watchNamespaces)

return deploySveltosAgentResources(ctx, clusterNamespace, clusterName, classifierName,
remoteRestConfig, agentYAML, nil, patches, isPullMode, logger)
Expand All @@ -1717,12 +1752,12 @@ func upgradeSveltosApplierInManagedCluster(ctx context.Context, clusterNamespace

func deploySveltosAgentInManagementCluster(ctx context.Context, restConfig *rest.Config, c client.Client,
clusterNamespace, clusterName, classifierName, mode string, clusterType libsveltosv1beta1.ClusterType,
patches []libsveltosv1beta1.Patch, logger logr.Logger) error {
patches []libsveltosv1beta1.Patch, watchNamespaces []string, logger logr.Logger) error {

logger.V(logs.LogDebug).Info("deploy sveltos-agent in the management cluster")

agentYAML := string(agent.GetSveltosAgentInMgmtClusterYAML())
agentYAML = prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, mode, clusterType)
agentYAML = prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, mode, clusterType, watchNamespaces)

// Following labels are added on the objects representing the drift-detection-manager
// for this cluster.
Expand Down Expand Up @@ -2021,9 +2056,11 @@ func removeSveltosAgentFromManagementCluster(ctx context.Context,
clusterNamespace, clusterName string, clusterType libsveltosv1beta1.ClusterType,
logger logr.Logger) error {

// Get YAML containing sveltos-agent resources
// Get YAML containing sveltos-agent resources. Only used below to determine the resource
// identity to delete, not actually deployed, so the substituted arg values (watchNamespaces
// included) don't matter here -- nil is fine.
agentYAML := string(agent.GetSveltosAgentInMgmtClusterYAML())
agentYAML = prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, "", clusterType)
agentYAML = prepareSveltosAgentYAML(agentYAML, clusterNamespace, clusterName, "", clusterType, nil)

// Classifier deploys sveltos-agent resources for each cluster.
lbls := getSveltosAgentLabels(clusterNamespace, clusterName, clusterType)
Expand Down Expand Up @@ -2272,6 +2309,48 @@ func getSveltosApplierPatches(ctx context.Context, c client.Client,
return getSveltosApplierPatchesOld(ctx, c, logger)
}

// getAgentWatchNamespaces reads agentWatchNamespacesAnnotation off the Cluster/SveltosCluster
// instance and returns the comma-separated namespace list it names, split and trimmed. Returns
// nil, nil (not an error) when the Cluster, the annotation, or its value is missing, same
// contract as getPerClusterPatches. sveltos-agent itself only honors this outside managed-cluster
// mode (see resolveScopedNamespaces in its own main.go), so callers here don't need to
// special-case agentless vs. managed-cluster mode: passing the value through unconditionally is
// harmless when sveltos-agent is deployed in the managed cluster.
func getAgentWatchNamespaces(ctx context.Context, c client.Client,
clusterNamespace, clusterName string, clusterType libsveltosv1beta1.ClusterType,
logger logr.Logger) ([]string, error) {

cluster, err := clusterproxy.GetCluster(ctx, c, clusterNamespace, clusterName, clusterType)
if err != nil {
if apierrors.IsNotFound(err) {
return nil, nil
}
return nil, err
}

annos := cluster.GetAnnotations()
if annos == nil {
return nil, nil
}

value, ok := annos[agentWatchNamespacesAnnotation]
if !ok || value == "" {
return nil, nil
}

var namespaces []string
for _, ns := range strings.Split(value, ",") {
ns = strings.TrimSpace(ns)
if ns != "" {
namespaces = append(namespaces, ns)
}
}

logger.V(logs.LogDebug).Info(fmt.Sprintf("got watch-namespaces %v from annotation %s",
namespaces, agentWatchNamespacesAnnotation))
return namespaces, nil
}

func addTemplateSpecLabels(u *unstructured.Unstructured, lbls map[string]string) (*unstructured.Unstructured, error) {
var deployment appsv1.Deployment
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &deployment)
Expand Down
88 changes: 84 additions & 4 deletions controllers/classifier_deployer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ import (
)

const (
classifierCRDName = "classifiers.lib.projectsveltos.io"
classifierCRDName = "classifiers.lib.projectsveltos.io"
agentWatchNamespacesAnnotKey = "agent.projectsveltos.io/watch-namespaces"
watchNamespaceNs1 = "ns1"
watchNamespaceNs2 = "ns2"
)

var _ = Describe("Classifier Deployer", func() {
Expand Down Expand Up @@ -476,16 +479,22 @@ var _ = Describe("Classifier Deployer", func() {
})

It("deploySveltosAgent deploys sveltos agent", func() {
watchNamespaces := []string{watchNamespaceNs1, watchNamespaceNs2}
Expect(controllers.DeploySveltosAgentInManagedCluster(ctx, testEnv.Config, randomString(), randomString(),
randomString(), "do-not-send-reports", libsveltosv1beta1.ClusterTypeCapi, nil, false, logger)).To(Succeed())
randomString(), "do-not-send-reports", libsveltosv1beta1.ClusterTypeCapi, nil, false,
watchNamespaces, logger)).To(Succeed())

// Eventual loop so testEnv Cache is synced
currentSveltosAgent := &appsv1.Deployment{}
Eventually(func() error {
currentSveltosAgent := &appsv1.Deployment{}
return testEnv.Get(context.TODO(),
types.NamespacedName{Namespace: sveltosNamespace, Name: "sveltos-agent-manager"},
currentSveltosAgent)
}, timeout, pollingInterval).Should(BeNil())

Expect(currentSveltosAgent.Spec.Template.Spec.Containers).ToNot(BeEmpty())
Expect(currentSveltosAgent.Spec.Template.Spec.Containers[0].Args).To(
ContainElement("--watch-namespaces=ns1,ns2"))
})

It("createAccessRequest creates AccessRequest instance", func() {
Expand Down Expand Up @@ -607,14 +616,17 @@ var _ = Describe("Classifier Deployer", func() {
_, err := keymanager.GetKeyManagerInstance(context.TODO(), testEnv.Client)
Expect(err).To(BeNil())

watchNamespaces := []string{watchNamespaceNs1, watchNamespaceNs2}
Expect(controllers.DeploySveltosAgentInManagementCluster(context.TODO(), testEnv.Config,
testEnv.Client, clusterNamespace, clusterName, randomString(), "", clusterType, nil, logger)).To(Succeed())
testEnv.Client, clusterNamespace, clusterName, randomString(), "", clusterType, nil,
watchNamespaces, logger)).To(Succeed())

expectedLabels := controllers.GetSveltosAgentLabels(clusterNamespace, clusterName, clusterType)

listOptions := []client.ListOption{
client.InNamespace(controllers.GetSveltosAgentNamespace(sveltosNamespace)),
}
var matchingDeployment *appsv1.Deployment
Eventually(func() bool {
deployments := &appsv1.DeploymentList{}
err := testEnv.List(context.TODO(), deployments, listOptions...)
Expand All @@ -629,12 +641,17 @@ var _ = Describe("Classifier Deployer", func() {
for i := range deployments.Items {
d := &deployments.Items[i]
if verifyLabels(d.Labels, expectedLabels) {
matchingDeployment = d
return true
}
}
return false
}, timeout, pollingInterval).Should(BeTrue())

Expect(matchingDeployment.Spec.Template.Spec.Containers).ToNot(BeEmpty())
Expect(matchingDeployment.Spec.Template.Spec.Containers[0].Args).To(
ContainElement("--watch-namespaces=ns1,ns2"))

Expect(sveltos_upgrade.StoreSveltosAgentVersion(context.TODO(), testEnv.Client, sveltosNamespace, "v1.0.0",
clusterNamespace, clusterName, clusterType, true, logger)).To(Succeed())

Expand Down Expand Up @@ -930,6 +947,69 @@ metadata:
verifyPatches(patches)
})

It("getAgentWatchNamespaces reads and trims the comma-separated namespace list from the annotation", func() {
sveltosCluster := &libsveltosv1beta1.SveltosCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: randomString(),
Name: upstreamClusterNamePrefix + randomString(),
Annotations: map[string]string{
agentWatchNamespacesAnnotKey: " foo ,bar,, baz",
},
},
}

c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(sveltosCluster).Build()

namespaces, err := controllers.GetAgentWatchNamespaces(context.TODO(), c, sveltosCluster.Namespace,
sveltosCluster.Name, libsveltosv1beta1.ClusterTypeSveltos, logger)
Expect(err).To(BeNil())
Expect(namespaces).To(Equal([]string{"foo", "bar", "baz"}))
})

It("getAgentWatchNamespaces returns nil, not an error, when the annotation is absent", func() {
sveltosCluster := &libsveltosv1beta1.SveltosCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: randomString(),
Name: upstreamClusterNamePrefix + randomString(),
},
}

c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(sveltosCluster).Build()

namespaces, err := controllers.GetAgentWatchNamespaces(context.TODO(), c, sveltosCluster.Namespace,
sveltosCluster.Name, libsveltosv1beta1.ClusterTypeSveltos, logger)
Expect(err).To(BeNil())
Expect(namespaces).To(BeNil())
})

It("getAgentWatchNamespaces returns nil, not an error, when the annotation value is empty", func() {
sveltosCluster := &libsveltosv1beta1.SveltosCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: randomString(),
Name: upstreamClusterNamePrefix + randomString(),
Annotations: map[string]string{
agentWatchNamespacesAnnotKey: "",
},
},
}

c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(sveltosCluster).Build()

namespaces, err := controllers.GetAgentWatchNamespaces(context.TODO(), c, sveltosCluster.Namespace,
sveltosCluster.Name, libsveltosv1beta1.ClusterTypeSveltos, logger)
Expect(err).To(BeNil())
Expect(namespaces).To(BeNil())
})

It("getAgentWatchNamespaces returns nil, not an error, when the Cluster does not exist", func() {
c := fake.NewClientBuilder().WithScheme(scheme).Build()

namespaces, err := controllers.GetAgentWatchNamespaces(context.TODO(), c, randomString(),
upstreamClusterNamePrefix+randomString(), libsveltosv1beta1.ClusterTypeSveltos, logger)
Expect(err).To(BeNil())
Expect(namespaces).To(BeNil())
})

It("getSveltosApplierPatches reads post render patches from per cluster ConfigMap", func() {
configMapNamespace := randomString()
configMapName := randomString()
Expand Down
1 change: 1 addition & 0 deletions controllers/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ var (
GetSveltosAgentLabels = getSveltosAgentLabels
GetSveltosAgentNamespace = getSveltosAgentNamespace
GetSveltosAgentPatches = getSveltosAgentPatches
GetAgentWatchNamespaces = getAgentWatchNamespaces
GetSveltosApplierPatches = getSveltosApplierPatches

CreateAccessRequest = createAccessRequest
Expand Down
Loading