Skip to content

Commit a84fb3e

Browse files
feat: Add category mappings support to Konnector Agent Helm chart (#1385)
**What problem does this PR solve?**: This PR adds support for extracting and passing `categoryMappings` to the Konnector Agent Helm chart. The category mappings are derived from `additionalCategories` specified in the worker node configuration and controlplane configuration Changes: 1. **Category Extraction Logic** (`pkg/handlers/lifecycle/konnectoragent/handler.go`): - Added `extractCategoryMappings()` function that: - Extracts `additionalCategories` from cluster-level worker config variables and controlplane config variables 2. **Helm Values Template** (`charts/cluster-api-runtime-extensions-nutanix/addons/konnector-agent/values-template.yaml`): - Added conditional rendering of `categoryMappings` field **Which issue(s) this PR fixes**: [Fixes #](https://jira.nutanix.com/browse/NCN-110842) **How Has This Been Tested?**: Created clusters using dev caren and verified the categories and configmaps related to konnector agent Konnector Agent HCP with category Mapping: <img width="1089" height="878" alt="Screenshot 2025-11-12 at 8 37 19 PM" src="https://github.com/user-attachments/assets/f2560fbb-b6b4-4555-95c8-49683d7e0c2a" /> Konnector Agent HCP without category mappings: <img width="1100" height="887" alt="Screenshot 2025-11-12 at 8 37 39 PM" src="https://github.com/user-attachments/assets/f323bf38-9175-4dd9-ab6c-a285cadf502c" /> **Special notes for your reviewer**: <!-- Use this to provide any additional information to the reviewers. This may include: - Best way to review the PR. - Where the author wants the most review attention on. - etc. -->
1 parent f2f787a commit a84fb3e

File tree

4 files changed

+749
-0
lines changed

4 files changed

+749
-0
lines changed

charts/cluster-api-runtime-extensions-nutanix/addons/konnector-agent/values-template.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ pc:
1010
k8sClusterName: {{ .ClusterName }}
1111
k8sDistribution: NKP
1212
createSecret: false
13+
{{- if .CategoryMappings }}
14+
categoryMappings: {{ .CategoryMappings }}
15+
{{- end }}

hack/tools/fetch-images/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,14 @@ prismEndPoint: endpoint
382382
PrismCentralPort uint16
383383
PrismCentralInsecure bool
384384
ClusterName string
385+
CategoryMappings string
385386
}{
386387
AgentName: "konnector-agent",
387388
PrismCentralHost: "prism-central.example.com",
388389
PrismCentralPort: 9440,
389390
PrismCentralInsecure: true,
390391
ClusterName: "test-cluster",
392+
CategoryMappings: "",
391393
}
392394

393395
err = template.Must(template.New(defaultHelmAddonFilename).ParseFiles(f)).Execute(tempFile, &templateInput)

pkg/handlers/lifecycle/konnectoragent/handler.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import (
1414
"github.com/go-logr/logr"
1515
"github.com/spf13/pflag"
1616
corev1 "k8s.io/api/core/v1"
17+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1718
apierrors "k8s.io/apimachinery/pkg/api/errors"
1819
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1920
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2021
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
2122
ctrl "sigs.k8s.io/controller-runtime"
2223
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
2324

25+
capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
2426
caaphv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1"
2527
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
2628
apivariables "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
@@ -287,6 +289,7 @@ func templateValuesFunc(
287289
PrismCentralPort uint16
288290
PrismCentralInsecure bool
289291
ClusterName string
292+
CategoryMappings string
290293
}
291294

292295
address, port, err := nutanixConfig.PrismCentralEndpoint.ParseURL()
@@ -301,6 +304,9 @@ func templateValuesFunc(
301304
clusterName = clusterName[:maxClusterNameLength]
302305
}
303306

307+
// Extract categoryMappings from worker config additionalCategories
308+
categoryMappings := extractCategoryMappings(cluster)
309+
304310
templateInput := input{
305311
AgentName: defaultK8sAgentName,
306312
PrismCentralHost: address,
@@ -309,6 +315,7 @@ func templateValuesFunc(
309315
// need to add support to accept PC's trust bundle in agent(it's not implemented currently)
310316
PrismCentralInsecure: true,
311317
ClusterName: clusterName,
318+
CategoryMappings: categoryMappings,
312319
}
313320

314321
var b bytes.Buffer
@@ -321,6 +328,103 @@ func templateValuesFunc(
321328
}
322329
}
323330

331+
// extractCategoryMappings extracts additionalCategories from both control plane and worker config variables
332+
// and converts them to comma-separated format.
333+
func extractCategoryMappings(cluster *clusterv1.Cluster) string {
334+
var categories []string
335+
336+
// Extract control plane nodes categories from cluster topology variables
337+
if cluster.Spec.Topology != nil && cluster.Spec.Topology.Variables != nil {
338+
varMap := variables.ClusterVariablesToVariablesMap(cluster.Spec.Topology.Variables)
339+
categories = append(categories, extractCategoriesFromVarMap(varMap)...)
340+
}
341+
342+
// Append machine deployment overrides from all machine deployments
343+
if cluster.Spec.Topology != nil && cluster.Spec.Topology.Workers != nil {
344+
for i := range cluster.Spec.Topology.Workers.MachineDeployments {
345+
md := &cluster.Spec.Topology.Workers.MachineDeployments[i]
346+
if md.Variables != nil && len(md.Variables.Overrides) > 0 {
347+
mdVarMap := variables.ClusterVariablesToVariablesMap(md.Variables.Overrides)
348+
mdCategories := extractCategoriesFromVarMap(mdVarMap)
349+
if len(mdCategories) > 0 {
350+
categories = append(categories, mdCategories...)
351+
}
352+
}
353+
}
354+
}
355+
356+
if len(categories) == 0 {
357+
return ""
358+
}
359+
360+
// Remove duplicate category pairs (same key=value pairs)
361+
categories = removeDuplicateCategories(categories)
362+
363+
return strings.Join(categories, ",")
364+
}
365+
366+
// extractCategoriesFromVarMap extracts additionalCategories from a variable map
367+
// and returns them as a slice of "key=value" strings. It extracts from both control plane and worker config.
368+
func extractCategoriesFromVarMap(varMap map[string]apiextensionsv1.JSON) []string {
369+
var categories []string
370+
371+
clusterConfigVar, err := variables.Get[apivariables.ClusterConfigSpec](
372+
varMap,
373+
v1alpha1.ClusterConfigVariableName,
374+
)
375+
if err == nil && clusterConfigVar.ControlPlane != nil &&
376+
clusterConfigVar.ControlPlane.Nutanix != nil &&
377+
clusterConfigVar.ControlPlane.Nutanix.MachineDetails.AdditionalCategories != nil &&
378+
len(clusterConfigVar.ControlPlane.Nutanix.MachineDetails.AdditionalCategories) > 0 {
379+
categories = append(
380+
categories,
381+
formatCategoriesFromSlice(clusterConfigVar.ControlPlane.Nutanix.MachineDetails.AdditionalCategories)...)
382+
}
383+
384+
// Then, extract worker categories
385+
workerConfigVar, err := variables.Get[apivariables.WorkerNodeConfigSpec](
386+
varMap,
387+
v1alpha1.WorkerConfigVariableName,
388+
)
389+
if err == nil && workerConfigVar.Nutanix != nil &&
390+
workerConfigVar.Nutanix.MachineDetails.AdditionalCategories != nil &&
391+
len(workerConfigVar.Nutanix.MachineDetails.AdditionalCategories) > 0 {
392+
categories = append(
393+
categories,
394+
formatCategoriesFromSlice(workerConfigVar.Nutanix.MachineDetails.AdditionalCategories)...)
395+
}
396+
return categories
397+
}
398+
399+
// formatCategoriesFromSlice formats a slice of NutanixCategoryIdentifier into "key=value" strings.
400+
// It filters out categories with empty keys or values.
401+
func formatCategoriesFromSlice(categories []capxv1.NutanixCategoryIdentifier) []string {
402+
var result []string
403+
for _, cat := range categories {
404+
if cat.Key != "" && cat.Value != "" {
405+
categoryValue := fmt.Sprintf("%s=%s", cat.Key, cat.Value)
406+
result = append(result, categoryValue)
407+
}
408+
}
409+
return result
410+
}
411+
412+
// removeDuplicateCategories removes duplicate category pairs (same key=value pairs)
413+
// while preserving the order of first occurrence.
414+
func removeDuplicateCategories(categories []string) []string {
415+
seen := make(map[string]bool)
416+
result := make([]string, 0, len(categories))
417+
418+
for _, cat := range categories {
419+
if !seen[cat] {
420+
seen[cat] = true
421+
result = append(result, cat)
422+
}
423+
}
424+
425+
return result
426+
}
427+
324428
func (n *DefaultKonnectorAgent) BeforeClusterDelete(
325429
ctx context.Context,
326430
req *runtimehooksv1.BeforeClusterDeleteRequest,

0 commit comments

Comments
 (0)