Skip to content

Commit 4243951

Browse files
vinayaka krishnamurthyvinayaka krishnamurthy
authored andcommitted
fix(substrate): use typed config namespace for discovery
1 parent 649a245 commit 4243951

10 files changed

Lines changed: 262 additions & 27 deletions

cmd/kapro/adopt.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ func importSubstrateObjects(opts importSubstrateOptions, substrateKind, manageme
210210
ManagementPolicy: managementPolicy,
211211
Selector: &metav1.LabelSelector{MatchLabels: matchLabels},
212212
},
213-
Parameters: map[string]string{"namespace": opts.Namespace},
214213
},
215214
},
216215
&kaprov1alpha1.SubstrateDiscoveryPolicy{

cmd/kapro/adopt_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ func TestImportSubstrateObjectsUseClassRefAndTypedConfig(t *testing.T) {
9797
substrate.Spec.ConfigRef.Name != "checkout" {
9898
t.Fatalf("configRef=%#v, want FluxSubstrateConfig checkout", substrate.Spec.ConfigRef)
9999
}
100+
if _, ok := substrate.Spec.Parameters["namespace"]; ok {
101+
t.Fatalf("live import should keep namespace only on typed config, got parameters=%#v", substrate.Spec.Parameters)
102+
}
100103
if substrate.Spec.Discovery == nil ||
101104
substrate.Spec.Discovery.ManagementPolicy != "Adopt" ||
102105
substrate.Spec.Discovery.Selector == nil ||

cmd/kapro/doctor.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1616
apierrors "k8s.io/apimachinery/pkg/api/errors"
1717
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
19+
"k8s.io/apimachinery/pkg/runtime/schema"
1820
"k8s.io/apimachinery/pkg/types"
1921
"sigs.k8s.io/controller-runtime/pkg/client"
2022

@@ -473,10 +475,7 @@ func checkGitOpsSubstrates(ctx context.Context, c client.Client) doctorFinding {
473475
mode = strings.ToLower(substrate.Spec.Discovery.ManagementPolicy)
474476
}
475477
}
476-
namespace := strings.TrimSpace(substrate.Spec.Parameters["namespace"])
477-
if namespace == "" {
478-
namespace = defaultSubstrateNamespace(kind)
479-
}
478+
namespace := doctorSubstrateNamespace(ctx, c, &substrate)
480479
details = append(details, fmt.Sprintf("%s substrate=%s execution=%s mode=%s namespace=%s", substrate.Name, kind, executionMode, mode, namespace))
481480
if kind == string(kaprov1alpha1.SubstrateKindArgo) || kind == string(kaprov1alpha1.SubstrateKindFlux) {
482481
var ns corev1.Namespace
@@ -507,6 +506,37 @@ func checkGitOpsSubstrates(ctx context.Context, c client.Client) doctorFinding {
507506
}
508507
}
509508

509+
func doctorSubstrateNamespace(ctx context.Context, c client.Client, substrate *kaprov1alpha1.Substrate) string {
510+
if configNamespace := doctorSubstrateConfigNamespace(ctx, c, substrate); configNamespace != "" {
511+
return configNamespace
512+
}
513+
if parameterNamespace := strings.TrimSpace(substrate.Spec.Parameters["namespace"]); parameterNamespace != "" {
514+
return parameterNamespace
515+
}
516+
return defaultSubstrateNamespace(substrate.Spec.SubstrateKind())
517+
}
518+
519+
func doctorSubstrateConfigNamespace(ctx context.Context, c client.Client, substrate *kaprov1alpha1.Substrate) string {
520+
if c == nil || substrate.Spec.ConfigRef == nil {
521+
return ""
522+
}
523+
ref := substrate.Spec.ConfigRef
524+
gv, err := schema.ParseGroupVersion(ref.APIVersion)
525+
if err != nil {
526+
return ""
527+
}
528+
config := &unstructured.Unstructured{}
529+
config.SetGroupVersionKind(gv.WithKind(ref.Kind))
530+
if err := c.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, config); err != nil {
531+
return ""
532+
}
533+
namespace, _, err := unstructured.NestedString(config.Object, "spec", "namespace")
534+
if err != nil {
535+
return ""
536+
}
537+
return strings.TrimSpace(namespace)
538+
}
539+
510540
type doctorSecretRef struct {
511541
types.NamespacedName
512542
Source string

cmd/kapro/doctor_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1414
apierrors "k8s.io/apimachinery/pkg/api/errors"
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1617
"k8s.io/apimachinery/pkg/runtime"
1718
"k8s.io/apimachinery/pkg/runtime/schema"
1819
"k8s.io/apimachinery/pkg/util/intstr"
@@ -141,6 +142,44 @@ func TestDoctorReportSummarizesGitOpsSubstrates(t *testing.T) {
141142
}
142143
}
143144

145+
func TestDoctorReportUsesTypedConfigNamespace(t *testing.T) {
146+
config := &unstructured.Unstructured{}
147+
config.SetGroupVersionKind(schema.GroupVersionKind{Group: "flux.substrate.kapro.io", Version: "v1alpha1", Kind: "FluxSubstrateConfig"})
148+
config.SetName("checkout")
149+
if err := unstructured.SetNestedField(config.Object, "flux-managed", "spec", "namespace"); err != nil {
150+
t.Fatal(err)
151+
}
152+
substrate := &kaprov1alpha1.Substrate{
153+
ObjectMeta: metav1.ObjectMeta{Name: "flux"},
154+
Spec: kaprov1alpha1.SubstrateSpec{
155+
ClassRef: &kaprov1alpha1.SubstrateClassReference{Name: "flux"},
156+
ConfigRef: &kaprov1alpha1.SubstrateObjectReference{
157+
APIVersion: "flux.substrate.kapro.io/v1alpha1",
158+
Kind: "FluxSubstrateConfig",
159+
Name: "checkout",
160+
},
161+
Execution: &kaprov1alpha1.SubstrateExecutionSpec{Mode: kaprov1alpha1.ExecutionModeHubPush},
162+
Parameters: map[string]string{"namespace": "wrong-namespace"},
163+
Discovery: &kaprov1alpha1.SubstrateDiscoverySpec{Enabled: true, ManagementPolicy: "Observe"},
164+
},
165+
}
166+
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "flux-managed"}}
167+
c := fakeDoctorClient(t, append(append(healthyDoctorObjects(), validatingWebhookObjects()...), substrate, config, ns)...)
168+
report := collectDoctorReport(context.Background(), c, doctorOptions{
169+
Namespace: "kapro-system",
170+
Deployment: "kapro-kapro-operator",
171+
}, allowAllSAR)
172+
173+
substrates := findDoctorCheck(report, "gitops-substrates")
174+
details := strings.Join(substrates.Details, ",")
175+
if substrates.Status != doctorStatusPass || !strings.Contains(details, "namespace=flux-managed") {
176+
t.Fatalf("expected typed config namespace in gitops substrate summary, got %#v", substrates)
177+
}
178+
if strings.Contains(details, "wrong-namespace") {
179+
t.Fatalf("doctor should prefer typed config namespace over parameters, got %#v", substrates)
180+
}
181+
}
182+
144183
func TestDoctorReportWarnsOnMissingGitOpsNamespace(t *testing.T) {
145184
substrate := &kaprov1alpha1.Substrate{
146185
ObjectMeta: metav1.ObjectMeta{Name: "argo"},

cmd/kapro/scaffold.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -714,15 +714,13 @@ spec:
714714
name: %s
715715
execution:
716716
mode: hub-push
717-
parameters:
718-
namespace: %s
719717
discovery:
720718
enabled: true
721719
managementPolicy: %s
722720
maxObjects: 1000
723721
selector:
724722
matchLabels:
725-
%s`, substrate, family, ledger, substrate, apiVersion, configKind, name, namespace, name, substrate, apiVersion, configKind, name, namespace, managementPolicy, renderYAMLMap(labels, 8))
723+
%s`, substrate, family, ledger, substrate, apiVersion, configKind, name, namespace, name, substrate, apiVersion, configKind, name, managementPolicy, renderYAMLMap(labels, 8))
726724
}
727725

728726
func substrateConfigKind(substrate string) (string, string) {

cmd/kapro/scaffold_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"reflect"
77
"strings"
88
"testing"
9+
10+
"sigs.k8s.io/yaml"
911
)
1012

1113
func TestRunInitScaffoldArgo(t *testing.T) {
@@ -480,6 +482,15 @@ func TestRunConnectScaffoldFlux(t *testing.T) {
480482
t.Fatalf("missing %q in:\n%s", want, content)
481483
}
482484
}
485+
if strings.Contains(content, "parameters:\n namespace:") {
486+
t.Fatalf("connect scaffold should keep namespace only on typed config, got:\n%s", content)
487+
}
488+
for _, doc := range strings.Split(content, "\n---\n") {
489+
var object map[string]any
490+
if err := yaml.Unmarshal([]byte(doc), &object); err != nil {
491+
t.Fatalf("connect scaffold emitted invalid YAML document:\n%s\nerror: %v", doc, err)
492+
}
493+
}
483494
for _, forbidden := range []string{
484495
"actuator:",
485496
"\n substrate:\n kind:",

internal/controller/substrate_controller.go

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,7 @@ func (r *SubstrateReconciler) observeDiscovery(ctx context.Context, profile *kap
173173
if profile.Spec.Discovery == nil || !profile.Spec.Discovery.Enabled {
174174
return counts, "DiscoveryDisabled", "substrate discovery is disabled"
175175
}
176-
namespace := "argocd"
177-
if profile.Spec.SubstrateKind() == string(kaprov1alpha1.SubstrateKindFlux) {
178-
namespace = "flux-system"
179-
}
180-
if profile.Spec.Parameters["namespace"] != "" {
181-
namespace = profile.Spec.Parameters["namespace"]
182-
}
176+
namespace := substrateDiscoveryNamespace(ctx, r.Client, profile)
183177

184178
switch profile.Spec.SubstrateKind() {
185179
case string(kaprov1alpha1.SubstrateKindArgo):
@@ -659,6 +653,49 @@ func (r *SubstrateReconciler) SetupWithManager(mgr ctrl.Manager) error {
659653
return b.Complete(r)
660654
}
661655

656+
func substrateDiscoveryNamespace(ctx context.Context, c client.Client, profile *kaprov1alpha1.Substrate) string {
657+
namespace := defaultSubstrateDiscoveryNamespace(profile)
658+
if configNamespace := substrateConfigNamespace(ctx, c, profile); configNamespace != "" {
659+
return configNamespace
660+
}
661+
if parameterNamespace := strings.TrimSpace(profile.Spec.Parameters["namespace"]); parameterNamespace != "" {
662+
return parameterNamespace
663+
}
664+
return namespace
665+
}
666+
667+
func defaultSubstrateDiscoveryNamespace(profile *kaprov1alpha1.Substrate) string {
668+
switch profile.Spec.SubstrateKind() {
669+
case string(kaprov1alpha1.SubstrateKindArgo):
670+
return "argocd"
671+
case string(kaprov1alpha1.SubstrateKindFlux):
672+
return "flux-system"
673+
default:
674+
return ""
675+
}
676+
}
677+
678+
func substrateConfigNamespace(ctx context.Context, c client.Client, profile *kaprov1alpha1.Substrate) string {
679+
if c == nil || profile.Spec.ConfigRef == nil {
680+
return ""
681+
}
682+
ref := profile.Spec.ConfigRef
683+
gv, err := schema.ParseGroupVersion(ref.APIVersion)
684+
if err != nil {
685+
return ""
686+
}
687+
config := &unstructured.Unstructured{}
688+
config.SetGroupVersionKind(gv.WithKind(ref.Kind))
689+
if err := c.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, config); err != nil {
690+
return ""
691+
}
692+
namespace, _, err := unstructured.NestedString(config.Object, "spec", "namespace")
693+
if err != nil {
694+
return ""
695+
}
696+
return strings.TrimSpace(namespace)
697+
}
698+
662699
func typedSubstrateConfigWatchKinds() []schema.GroupVersionKind {
663700
return []schema.GroupVersionKind{
664701
{Group: "argocd.substrate.kapro.io", Version: "v1alpha1", Kind: "ArgoCDSubstrateConfig"},
@@ -694,7 +731,7 @@ func (r *SubstrateReconciler) substrateProfilesForSubstrateObject(ctx context.Co
694731
requests := make([]reconcile.Request, 0, len(profiles.Items))
695732
for i := range profiles.Items {
696733
profile := &profiles.Items[i]
697-
if substrateProfileMatchesObject(profile, obj) {
734+
if r.substrateProfileMatchesObject(ctx, profile, obj) {
698735
requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(profile)})
699736
}
700737
}
@@ -737,7 +774,7 @@ func (r *SubstrateReconciler) substrateProfilesForTypedConfig(ctx context.Contex
737774
return requests
738775
}
739776

740-
func substrateProfileMatchesObject(profile *kaprov1alpha1.Substrate, obj client.Object) bool {
777+
func (r *SubstrateReconciler) substrateProfileMatchesObject(ctx context.Context, profile *kaprov1alpha1.Substrate, obj client.Object) bool {
741778
if profile.Spec.Discovery == nil || !profile.Spec.Discovery.Enabled {
742779
return false
743780
}
@@ -759,13 +796,7 @@ func substrateProfileMatchesObject(profile *kaprov1alpha1.Substrate, obj client.
759796
if profile.Spec.SubstrateKind() != string(objectDriver) {
760797
return false
761798
}
762-
namespace := "argocd"
763-
if profile.Spec.SubstrateKind() == string(kaprov1alpha1.SubstrateKindFlux) {
764-
namespace = "flux-system"
765-
}
766-
if profile.Spec.Parameters["namespace"] != "" {
767-
namespace = profile.Spec.Parameters["namespace"]
768-
}
799+
namespace := substrateDiscoveryNamespace(ctx, r.Client, profile)
769800
if obj.GetNamespace() != namespace {
770801
return false
771802
}

internal/controller/substrate_controller_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,77 @@ func TestSubstrateProfileArgoDiscoveryCountsExistingResources(t *testing.T) {
326326
}
327327
}
328328

329+
func TestSubstrateProfileDiscoveryUsesTypedConfigNamespace(t *testing.T) {
330+
scheme := runtime.NewScheme()
331+
if err := kaprov1alpha1.AddToScheme(scheme); err != nil {
332+
t.Fatal(err)
333+
}
334+
config := typedSubstrateConfigWithSpecNamespace("flux.substrate.kapro.io/v1alpha1", "FluxSubstrateConfig", "checkout", "flux-managed")
335+
profile := &kaprov1alpha1.Substrate{
336+
ObjectMeta: metav1.ObjectMeta{Name: "flux"},
337+
Spec: kaprov1alpha1.SubstrateSpec{
338+
ClassRef: &kaprov1alpha1.SubstrateClassReference{Name: "flux"},
339+
ConfigRef: &kaprov1alpha1.SubstrateObjectReference{
340+
APIVersion: "flux.substrate.kapro.io/v1alpha1",
341+
Kind: "FluxSubstrateConfig",
342+
Name: "checkout",
343+
},
344+
Discovery: &kaprov1alpha1.SubstrateDiscoverySpec{Enabled: true},
345+
Parameters: map[string]string{"namespace": "wrong-namespace"},
346+
},
347+
}
348+
gitRepository := &unstructured.Unstructured{}
349+
gitRepository.SetGroupVersionKind(schema.GroupVersionKind{Group: "source.toolkit.fluxcd.io", Version: "v1", Kind: "GitRepository"})
350+
gitRepository.SetNamespace("flux-managed")
351+
gitRepository.SetName("checkout-git")
352+
353+
r := &SubstrateReconciler{
354+
Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(profile, config, gitRepository).Build(),
355+
}
356+
357+
counts, reason, _ := r.observeDiscovery(context.Background(), profile)
358+
if reason != "DiscoverySucceeded" {
359+
t.Fatalf("reason=%s", reason)
360+
}
361+
if counts.applications != 1 {
362+
t.Fatalf("applications=%d, want typed config namespace object to be discovered", counts.applications)
363+
}
364+
}
365+
366+
func TestSubstrateProfileObjectWatchUsesTypedConfigNamespace(t *testing.T) {
367+
scheme := runtime.NewScheme()
368+
if err := kaprov1alpha1.AddToScheme(scheme); err != nil {
369+
t.Fatal(err)
370+
}
371+
config := typedSubstrateConfigWithSpecNamespace("flux.substrate.kapro.io/v1alpha1", "FluxSubstrateConfig", "checkout", "flux-managed")
372+
profile := &kaprov1alpha1.Substrate{
373+
ObjectMeta: metav1.ObjectMeta{Name: "flux"},
374+
Spec: kaprov1alpha1.SubstrateSpec{
375+
ClassRef: &kaprov1alpha1.SubstrateClassReference{Name: "flux"},
376+
ConfigRef: &kaprov1alpha1.SubstrateObjectReference{
377+
APIVersion: "flux.substrate.kapro.io/v1alpha1",
378+
Kind: "FluxSubstrateConfig",
379+
Name: "checkout",
380+
},
381+
Discovery: &kaprov1alpha1.SubstrateDiscoverySpec{Enabled: true},
382+
Parameters: map[string]string{"namespace": "wrong-namespace"},
383+
},
384+
}
385+
gitRepository := &unstructured.Unstructured{}
386+
gitRepository.SetGroupVersionKind(schema.GroupVersionKind{Group: "source.toolkit.fluxcd.io", Version: "v1", Kind: "GitRepository"})
387+
gitRepository.SetNamespace("flux-managed")
388+
gitRepository.SetName("checkout-git")
389+
390+
r := &SubstrateReconciler{
391+
Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(profile, config).Build(),
392+
}
393+
394+
requests := r.substrateProfilesForSubstrateObject(context.Background(), gitRepository)
395+
if len(requests) != 1 || requests[0].Name != "flux" {
396+
t.Fatalf("requests=%#v, want flux profile from typed config namespace", requests)
397+
}
398+
}
399+
329400
func TestSubstrateProfileArgoDiscoveryClassifiesExistingGitOpsPatterns(t *testing.T) {
330401
scheme := runtime.NewScheme()
331402
if err := clientgoscheme.AddToScheme(scheme); err != nil {
@@ -587,6 +658,14 @@ func typedSubstrateConfig(apiVersion, kind, namespace, name string) *unstructure
587658
return config
588659
}
589660

661+
func typedSubstrateConfigWithSpecNamespace(apiVersion, kind, name, namespace string) *unstructured.Unstructured {
662+
config := typedSubstrateConfig(apiVersion, kind, "", name)
663+
if err := unstructured.SetNestedField(config.Object, namespace, "spec", "namespace"); err != nil {
664+
panic(err)
665+
}
666+
return config
667+
}
668+
590669
func hasDiscoveryPattern(objects []kaprov1alpha1.DiscoveredSubstrateObject, pattern string) bool {
591670
for _, obj := range objects {
592671
if obj.Pattern == pattern {

internal/controller/substratediscoverypolicy_controller.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func (r *SubstrateDiscoveryPolicyReconciler) discover(ctx context.Context, polic
136136
if !a.Capabilities().SupportsDiscover {
137137
return adapterPolicyDiscoveryOutcome{reason: "DiscoveryUnsupported", message: fmt.Sprintf("substrate kind %s does not support discovery", substrate.Spec.SubstrateKind())}, nil
138138
}
139-
req, err := adapterPolicyDiscoveryRequest(&substrate, policy)
139+
req, err := r.adapterPolicyDiscoveryRequest(ctx, &substrate, policy)
140140
if err != nil {
141141
return adapterPolicyDiscoveryOutcome{reason: "InvalidSelector", message: err.Error()}, nil
142142
}
@@ -247,12 +247,12 @@ func adapterPolicySubstrateStatusObjects(substrate *kaprov1alpha1.Substrate) int
247247
return substrate.Status.DiscoveredClusters + substrate.Status.DiscoveredApplications + substrate.Status.DiscoveredApplicationSets
248248
}
249249

250-
func adapterPolicyDiscoveryRequest(substrate *kaprov1alpha1.Substrate, policy *kaprov1alpha1.SubstrateDiscoveryPolicy) (kaproadapter.DiscoveryRequest, error) {
250+
func (r *SubstrateDiscoveryPolicyReconciler) adapterPolicyDiscoveryRequest(ctx context.Context, substrate *kaprov1alpha1.Substrate, policy *kaprov1alpha1.SubstrateDiscoveryPolicy) (kaproadapter.DiscoveryRequest, error) {
251251
req := kaproadapter.DiscoveryRequest{
252252
Substrate: substrate,
253253
SubstrateKind: kaprov1alpha1.SubstrateKind(substrate.Spec.SubstrateKind()),
254254
ExecutionScope: substrateRuntimeForDiscovery(substrate.Spec.ExecutionMode()),
255-
Namespace: substrate.Spec.Parameters["namespace"],
255+
Namespace: substrateDiscoveryNamespace(ctx, r.Client, substrate),
256256
Parameters: substrate.Spec.Parameters,
257257
}
258258
if substrate.Spec.Discovery != nil {

0 commit comments

Comments
 (0)