diff --git a/integration-test/branch-12/target-1/output.html b/integration-test/branch-12/target-1/output.html index 03829726..74d6ff8e 100644 --- a/integration-test/branch-12/target-1/output.html +++ b/integration-test/branch-12/target-1/output.html @@ -60,7 +60,7 @@

Argo CD Diff Preview

Summary:

Modified (1):
-± argocd-helm-chart (+2434|-62)
+± argocd-helm-chart (+2437|-65)
@@ -1016,6 +1016,35 @@

StatefulSet: argocd/argocd-helm-chart-application-co

+

ServiceMonitor: argocd/argocd-helm-chart-application-controller

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     app.kubernetes.io/part-of: argocd
-    app.kubernetes.io/version: v2.13.1
-    helm.sh/chart: argo-cd-7.7.7
+    app.kubernetes.io/version: v3.2.0
+    helm.sh/chart: argo-cd-9.1.4
   name: argocd-helm-chart-application-controller
   namespace: argocd
 spec:
   endpoints:
   - honorLabels: false
-    interval: 30s
+    interval: 15s
     path: /metrics
     port: http-metrics
   namespaceSelector:
     matchNames:
     - argocd
   selector:
     matchLabels:
       app.kubernetes.io/component: application-controller
       app.kubernetes.io/instance: argocd-helm-chart
       app.kubernetes.io/name: argocd-metrics
+
+

ClusterRole: argocd-helm-chart-server

diff --git a/integration-test/branch-12/target-1/output.md b/integration-test/branch-12/target-1/output.md index 25e3dbea..6f37b0ed 100644 --- a/integration-test/branch-12/target-1/output.md +++ b/integration-test/branch-12/target-1/output.md @@ -3,7 +3,7 @@ Summary: ```yaml Modified (1): -± argocd-helm-chart (+2434|-62) +± argocd-helm-chart (+2437|-65) ```
@@ -930,6 +930,31 @@ Modified (1): path: ca.crt optional: true ``` +#### ServiceMonitor: argocd/argocd-helm-chart-application-controller +```diff + app.kubernetes.io/part-of: argocd +- app.kubernetes.io/version: v2.13.1 +- helm.sh/chart: argo-cd-7.7.7 ++ app.kubernetes.io/version: v3.2.0 ++ helm.sh/chart: argo-cd-9.1.4 + name: argocd-helm-chart-application-controller + namespace: argocd + spec: + endpoints: + - honorLabels: false +- interval: 30s ++ interval: 15s + path: /metrics + port: http-metrics + namespaceSelector: + matchNames: + - argocd + selector: + matchLabels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/instance: argocd-helm-chart + app.kubernetes.io/name: argocd-metrics +``` #### ClusterRole: argocd-helm-chart-server ```diff name: argocd-helm-chart-server @@ -1621,18 +1646,7 @@ Modified (1): description: Images is a list of Kustomize image override specifications items: - description: KustomizeImage represents a Kustomize image - definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string -+ labelIncludeTemplates: -+ description: LabelIncludeTemplates specifies whether to -+ + description ``` 🚨 Diff is too long diff --git a/integration-test/branch-12/target-2/output.html b/integration-test/branch-12/target-2/output.html index d29324f5..214889cc 100644 --- a/integration-test/branch-12/target-2/output.html +++ b/integration-test/branch-12/target-2/output.html @@ -60,7 +60,7 @@

Argo CD Diff Preview

Summary:

Modified (1):
-± argocd-helm-chart (+212|-48)
+± argocd-helm-chart (+215|-51)
@@ -1016,6 +1016,35 @@

StatefulSet: argocd/argocd-helm-chart-application-co

+

ServiceMonitor: argocd/argocd-helm-chart-application-controller

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     app.kubernetes.io/part-of: argocd
-    app.kubernetes.io/version: v2.13.1
-    helm.sh/chart: argo-cd-7.7.7
+    app.kubernetes.io/version: v3.2.0
+    helm.sh/chart: argo-cd-9.1.4
   name: argocd-helm-chart-application-controller
   namespace: argocd
 spec:
   endpoints:
   - honorLabels: false
-    interval: 30s
+    interval: 15s
     path: /metrics
     port: http-metrics
   namespaceSelector:
     matchNames:
     - argocd
   selector:
     matchLabels:
       app.kubernetes.io/component: application-controller
       app.kubernetes.io/instance: argocd-helm-chart
       app.kubernetes.io/name: argocd-metrics
+
+

ClusterRole: argocd-helm-chart-server

diff --git a/integration-test/branch-12/target-2/output.md b/integration-test/branch-12/target-2/output.md index c9155250..9bdb8137 100644 --- a/integration-test/branch-12/target-2/output.md +++ b/integration-test/branch-12/target-2/output.md @@ -3,7 +3,7 @@ Summary: ```yaml Modified (1): -± argocd-helm-chart (+212|-48) +± argocd-helm-chart (+215|-51) ```
@@ -930,6 +930,31 @@ Modified (1): path: ca.crt optional: true ``` +#### ServiceMonitor: argocd/argocd-helm-chart-application-controller +```diff + app.kubernetes.io/part-of: argocd +- app.kubernetes.io/version: v2.13.1 +- helm.sh/chart: argo-cd-7.7.7 ++ app.kubernetes.io/version: v3.2.0 ++ helm.sh/chart: argo-cd-9.1.4 + name: argocd-helm-chart-application-controller + namespace: argocd + spec: + endpoints: + - honorLabels: false +- interval: 30s ++ interval: 15s + path: /metrics + port: http-metrics + namespaceSelector: + matchNames: + - argocd + selector: + matchLabels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/instance: argocd-helm-chart + app.kubernetes.io/name: argocd-metrics +``` #### ClusterRole: argocd-helm-chart-server ```diff name: argocd-helm-chart-server diff --git a/integration-test/integration_test.go b/integration-test/integration_test.go index b2a260e9..06c5e84b 100644 --- a/integration-test/integration_test.go +++ b/integration-test/integration_test.go @@ -63,6 +63,7 @@ type TestCase struct { RenderMethod string // "cli", "server-api", "repo-server-api", or "" to use global flag DisableClusterRoles string // Use no-cluster-roles/values.yaml (sets createClusterRoles: false) ArgocdConfigDir string // Custom argocd-config directory (relative to integration-test/); overrides auto-derived path + ArgocdConfigDirAPIMode string // Custom argocd-config directory for API render modes; falls back to ArgocdConfigDir when empty ArgocdUIURL string // Argo CD URL for generating application links in diff output TraverseAppOfApps string // If "true", enables recursive child app discovery (--traverse-app-of-apps) RepoRegex string // If set, use --repo-regex instead of --repo @@ -259,6 +260,8 @@ var testCases = []TestCase{ Suffix: "-1", DiffIgnore: "annotations", WatchIfNoWatchPatternFound: "false", + ArgocdConfigDir: "with-servicemonitor-crd", + ArgocdConfigDirAPIMode: "with-servicemonitor-crd-no-cluster-roles", }, { Name: "branch-12/target-2", @@ -268,6 +271,8 @@ var testCases = []TestCase{ DiffIgnore: "annotations", WatchIfNoWatchPatternFound: "false", IgnoreResources: "*:CustomResourceDefinition:*,:ConfigMap:argocd-cm", + ArgocdConfigDir: "with-servicemonitor-crd", + ArgocdConfigDirAPIMode: "with-servicemonitor-crd-no-cluster-roles", }, { Name: "branch-13/target-1", @@ -368,6 +373,23 @@ func isAPIMode(tc TestCase) bool { return m == "server-api" || m == "repo-server-api" } +func effectiveArgocdConfigDir(tc TestCase) string { + if isAPIMode(tc) && tc.ArgocdConfigDirAPIMode != "" { + return tc.ArgocdConfigDirAPIMode + } + return tc.ArgocdConfigDir +} + +func effectiveClusterConfig(tc TestCase) string { + if argocdConfigDir := effectiveArgocdConfigDir(tc); argocdConfigDir != "" { + return argocdConfigDir + } + if tc.DisableClusterRoles == "true" || isAPIMode(tc) { + return "no-cluster-roles" + } + return "default" +} + // timePattern matches timing information in output that varies between runs // Matches patterns like "1m10s]", "24s]", "110s]" var timePattern = regexp.MustCompile(`\d+m?\d*s\]`) @@ -420,6 +442,7 @@ func TestIntegration(t *testing.T) { // Track how many tests since last cluster creation testsSinceClusterCreation := 0 + currentClusterConfig := "" // Run each test case for i, tc := range shuffledCases { @@ -430,18 +453,27 @@ func TestIntegration(t *testing.T) { // Check current cluster state clusterExists := kindClusterExists() + requiredClusterConfig := effectiveClusterConfig(tc) - // Check for RBAC mismatch (only relevant if cluster exists) + // Check for cluster configuration mismatch (only relevant if cluster exists). + // An empty currentClusterConfig means this test process did not create the + // existing cluster, so the Argo CD values are unknown and must not be reused. if clusterExists { clusterHasRoles := clusterHasArgocdClusterRoles() // Mismatch if: test wants roles disabled but cluster has them, OR // test wants roles enabled but cluster doesn't have them rbacMismatch := testNeedsRolesDisabled == clusterHasRoles - - if rbacMismatch { - printToTTY("🔄 Deleting cluster due to RBAC configuration mismatch...\n") + configMismatch := currentClusterConfig != requiredClusterConfig + + if rbacMismatch || configMismatch { + reason := "RBAC configuration mismatch" + if configMismatch { + reason = fmt.Sprintf("Argo CD config mismatch: current=%s, required=%s", currentClusterConfig, requiredClusterConfig) + } + printToTTY(fmt.Sprintf("🔄 Deleting cluster due to %s...\n", reason)) _ = deleteKindCluster() clusterExists = false + currentClusterConfig = "" } } @@ -449,6 +481,7 @@ func TestIntegration(t *testing.T) { createCluster := testsSinceClusterCreation >= 15 || !clusterExists || tc.CreateCluster == "true" if createCluster { testsSinceClusterCreation = 0 + currentClusterConfig = requiredClusterConfig } // Print separator to TTY for visibility between test runs @@ -861,8 +894,8 @@ func runWithDocker(tc TestCase, createCluster bool, runDirs RunDirs) error { // When using a custom ArgoCD config directory, mount the entire directory. // Otherwise, when using API mode or DisableClusterRoles is set, mount only the values.yaml file // (which sets createClusterRoles: false) into the default argocd-config path in the container. - if tc.ArgocdConfigDir != "" { - args = append(args, "-v", fmt.Sprintf("%s/integration-test/%s:/argocd-config", repoRoot, tc.ArgocdConfigDir)) + if argocdConfigDir := effectiveArgocdConfigDir(tc); argocdConfigDir != "" { + args = append(args, "-v", fmt.Sprintf("%s/integration-test/%s:/argocd-config", repoRoot, argocdConfigDir)) } else if tc.DisableClusterRoles == "true" || isAPIMode(tc) { args = append(args, "-v", fmt.Sprintf("%s/integration-test/no-cluster-roles/values.yaml:/argocd-config/values.yaml", repoRoot)) } @@ -1051,8 +1084,8 @@ func buildArgs(tc TestCase, createCluster bool, runDirs RunDirs, repoRoot string // pass --argocd-config-dir pointing at the no-cluster-roles directory (createClusterRoles: false). // If ArgocdConfigDir is explicitly set, use that directory instead. // This avoids mutating the shared argocd-config/values.yaml on disk. - if tc.ArgocdConfigDir != "" { - args = append(args, "--argocd-config-dir", filepath.Join(repoRoot, "integration-test", tc.ArgocdConfigDir)) + if argocdConfigDir := effectiveArgocdConfigDir(tc); argocdConfigDir != "" { + args = append(args, "--argocd-config-dir", filepath.Join(repoRoot, "integration-test", argocdConfigDir)) } else if tc.DisableClusterRoles == "true" || isAPIMode(tc) { args = append(args, "--argocd-config-dir", filepath.Join(repoRoot, "integration-test", "no-cluster-roles")) } diff --git a/integration-test/with-servicemonitor-crd-no-cluster-roles/values.yaml b/integration-test/with-servicemonitor-crd-no-cluster-roles/values.yaml new file mode 100644 index 00000000..6aebb8ad --- /dev/null +++ b/integration-test/with-servicemonitor-crd-no-cluster-roles/values.yaml @@ -0,0 +1,26 @@ +# Same as with-servicemonitor-crd, but installs Argo CD without cluster roles +# for API render modes. +createClusterRoles: false + +extraObjects: + - apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + name: servicemonitors.monitoring.coreos.com + spec: + group: monitoring.coreos.com + scope: Namespaced + names: + plural: servicemonitors + singular: servicemonitor + kind: ServiceMonitor + shortNames: + - smon + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true diff --git a/integration-test/with-servicemonitor-crd/values.yaml b/integration-test/with-servicemonitor-crd/values.yaml new file mode 100644 index 00000000..14f6f8b9 --- /dev/null +++ b/integration-test/with-servicemonitor-crd/values.yaml @@ -0,0 +1,26 @@ +# Install the ServiceMonitor CRD in the test cluster so Argo CD discovers +# monitoring.coreos.com/v1 and passes it to Helm as a cluster capability. +# This lets tests cover charts that gate ServiceMonitor templates with +# .Capabilities.APIVersions.Has "monitoring.coreos.com/v1". +extraObjects: + - apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + name: servicemonitors.monitoring.coreos.com + spec: + group: monitoring.coreos.com + scope: Namespaced + names: + plural: servicemonitors + singular: servicemonitor + kind: ServiceMonitor + shortNames: + - smon + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true diff --git a/pkg/k8s/discovery.go b/pkg/k8s/discovery.go index 71c9450a..4a8741fb 100644 --- a/pkg/k8s/discovery.go +++ b/pkg/k8s/discovery.go @@ -2,26 +2,50 @@ package k8s import ( "fmt" + "sort" "strings" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime/schema" ) -// GetListOfNamespacedScopedResources returns metadata about all namespaced resource types -// Returns a map where the key is schema.GroupKind and the value is true (indicating the resource is namespaced) -// This format matches the interface expected by Argo CD's kubeutil.ResourceInfoProvider -func (c *Client) GetListOfNamespacedScopedResources() (map[schema.GroupKind]bool, error) { - namespacedScopedResources := make(map[schema.GroupKind]bool) +// GetServerVersion returns the Kubernetes server version string (e.g. "v1.29.2"). +func (c *Client) GetServerVersion() (string, error) { + v, err := c.discoveryClient.ServerVersion() + if err != nil { + return "", err + } + return v.GitVersion, nil +} - // Get all API resources from the cluster +// GetAPIVersions returns all unique GroupVersion strings (e.g. "v1", "apps/v1", +// "monitoring.coreos.com/v1") available in the cluster. This is the format +// consumed by the Argo CD repo server's ManifestRequest.ApiVersions field and +// exposed to Helm templates via .Capabilities.APIVersions. +func (c *Client) GetAPIVersions() ([]string, error) { + _, apiVersions, err := c.GetNamespacedScopedResourcesAndAPIVersions() + if err != nil { + return nil, err + } + return apiVersions, nil +} + +// GetNamespacedScopedResourcesAndAPIVersions returns metadata about all namespaced +// resource types and all unique GroupVersion strings in one discovery pass. +func (c *Client) GetNamespacedScopedResourcesAndAPIVersions() (map[schema.GroupKind]bool, []string, error) { _, apiResourceLists, err := c.discoveryClient.ServerGroupsAndResources() if err != nil { - return nil, fmt.Errorf("failed to discover API resources: %w", err) + return nil, nil, fmt.Errorf("failed to discover API resources: %w", err) } - // Iterate through all resource groups and versions + namespacedScopedResources := make(map[schema.GroupKind]bool) + seen := make(map[string]bool) + var apiVersions []string for _, apiResourceList := range apiResourceLists { + if !seen[apiResourceList.GroupVersion] { + seen[apiResourceList.GroupVersion] = true + apiVersions = append(apiVersions, apiResourceList.GroupVersion) + } // Parse GroupVersion to extract the group gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion) if err != nil { @@ -51,6 +75,17 @@ func (c *Client) GetListOfNamespacedScopedResources() (map[schema.GroupKind]bool namespacedScopedResources[gk] = true } } + sort.Strings(apiVersions) + return namespacedScopedResources, apiVersions, nil +} +// GetListOfNamespacedScopedResources returns metadata about all namespaced resource types +// Returns a map where the key is schema.GroupKind and the value is true (indicating the resource is namespaced) +// This format matches the interface expected by Argo CD's kubeutil.ResourceInfoProvider +func (c *Client) GetListOfNamespacedScopedResources() (map[schema.GroupKind]bool, error) { + namespacedScopedResources, _, err := c.GetNamespacedScopedResourcesAndAPIVersions() + if err != nil { + return nil, err + } return namespacedScopedResources, nil } diff --git a/pkg/reposerverextract/appofapps.go b/pkg/reposerverextract/appofapps.go index 7ef764dc..3b64f656 100644 --- a/pkg/reposerverextract/appofapps.go +++ b/pkg/reposerverextract/appofapps.go @@ -161,9 +161,14 @@ func RenderApplicationsFromBothBranchesWithAppOfApps( return nil, nil, time.Since(startTime), err } - namespacedScopedResources, err := argocd.K8sClient.GetListOfNamespacedScopedResources() + namespacedScopedResources, apiVersions, err := argocd.K8sClient.GetNamespacedScopedResourcesAndAPIVersions() if err != nil { - return nil, nil, time.Since(startTime), fmt.Errorf("failed to get list of namespaced scoped resources: %w", err) + return nil, nil, time.Since(startTime), fmt.Errorf("failed to initialize render context: %w", err) + } + + kubeVersion, err := argocd.K8sClient.GetServerVersion() + if err != nil { + return nil, nil, time.Since(startTime), fmt.Errorf("failed to get server version: %w", err) } // Collect all unique repository URLs referenced by the Applications so that @@ -347,7 +352,7 @@ func RenderApplicationsFromBothBranchesWithAppOfApps( ctx, cancel := context.WithTimeout(context.Background(), time.Duration(remainingTime())*time.Second) defer cancel() - manifests, childApps, err := renderAppWithChildDiscovery(ctx, repoClient, argocd, item.app, branchFolderByType, branchByType, namespacedScopedResources, creds, &repoSelector, argocd.Namespace, tempFolder, item.depth) + manifests, childApps, err := renderAppWithChildDiscovery(ctx, repoClient, argocd, item.app, branchFolderByType, branchByType, namespacedScopedResources, creds, &repoSelector, argocd.Namespace, tempFolder, item.depth, kubeVersion, apiVersions) if err != nil { results <- renderResult{err: fmt.Errorf("failed to render app %s: %w", item.app.GetLongName(), err)} return @@ -412,8 +417,10 @@ func renderAppWithChildDiscovery( argocdNamespace string, tempFolder string, depth int, + kubeVersion string, + apiVersions []string, ) ([]unstructured.Unstructured, []argoapplication.ArgoResource, error) { - allManifests, err := renderApp(ctx, repoClient, app, branchFolderByType, namespacedScopedResources, creds, repoSelector) + allManifests, err := renderApp(ctx, repoClient, app, branchFolderByType, namespacedScopedResources, creds, repoSelector, kubeVersion, apiVersions) if err != nil { return nil, nil, err } diff --git a/pkg/reposerverextract/extract.go b/pkg/reposerverextract/extract.go index ac7e3d52..053b0c04 100644 --- a/pkg/reposerverextract/extract.go +++ b/pkg/reposerverextract/extract.go @@ -92,9 +92,14 @@ func RenderApplicationsFromBothBranches( return nil, nil, time.Since(startTime), err } - namespacedScopedResources, err := argocd.K8sClient.GetListOfNamespacedScopedResources() + namespacedScopedResources, apiVersions, err := argocd.K8sClient.GetNamespacedScopedResourcesAndAPIVersions() if err != nil { - return nil, nil, time.Since(startTime), fmt.Errorf("failed to get list of namespaced scoped resources: %w", err) + return nil, nil, time.Since(startTime), fmt.Errorf("failed to initialize render context: %w", err) + } + + kubeVersion, err := argocd.K8sClient.GetServerVersion() + if err != nil { + return nil, nil, time.Since(startTime), fmt.Errorf("failed to get server version: %w", err) } // Collect all unique repository URLs referenced by the Applications so that @@ -176,7 +181,7 @@ func RenderApplicationsFromBothBranches( ctx, cancel := context.WithTimeout(context.Background(), time.Duration(remainingTime())*time.Second) defer cancel() - manifests, err := renderApp(ctx, repoClient, app, branchFolderByType, namespacedScopedResources, creds, &repoSelector) + manifests, err := renderApp(ctx, repoClient, app, branchFolderByType, namespacedScopedResources, creds, &repoSelector, kubeVersion, apiVersions) if err != nil { results <- result{err: fmt.Errorf("failed to render app %s: %w", app.GetLongName(), err)} return @@ -251,6 +256,8 @@ func renderApp( namespacedScopedResources map[schema.GroupKind]bool, creds *RepoCreds, repoSelector *repository.Selector, + kubeVersion string, + apiVersions []string, ) ([]unstructured.Unstructured, error) { branchFolder, ok := branchFolderByType[app.Branch] if !ok { @@ -265,7 +272,19 @@ func renderApp( var allManifestStrings []string for i, contentSource := range contentSources { - request, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSource, refSources, hasMultipleSources, branchFolder, creds, repoSelector) + request, streamDir, cleanup, err := buildManifestRequestForSource( + app, + contentSource, + refSources, + hasMultipleSources, + branchFolder, + creds, + manifestRequestRenderContext{ + repoSelector: repoSelector, + kubeVersion: kubeVersion, + apiVersions: apiVersions, + }, + ) if err != nil { return nil, fmt.Errorf("failed to build manifest request for content source %d: %w", i, err) } @@ -494,6 +513,16 @@ func splitSources(app argoapplication.ArgoResource) ( // // cleanup must be called by the caller when the stream directory is no longer // needed. +// manifestRequestRenderContext groups render-run settings that every +// ManifestRequest needs but that are not specific to a single source. Keeping +// these values together avoids a long positional parameter list where adjacent +// strings or slices can be accidentally swapped at call sites. +type manifestRequestRenderContext struct { + repoSelector *repository.Selector + kubeVersion string + apiVersions []string +} + func buildManifestRequestForSource( app argoapplication.ArgoResource, primarySource v1alpha1.ApplicationSource, @@ -501,7 +530,7 @@ func buildManifestRequestForSource( hasMultipleSources bool, branchFolder string, creds *RepoCreds, - repoSelector *repository.Selector, + renderContext manifestRequestRenderContext, ) (request *repoapiclient.ManifestRequest, streamDir string, cleanup func(), err error) { obj := app.Yaml.Object @@ -524,6 +553,8 @@ func buildManifestRequestForSource( AppName: app.Id, Namespace: namespace, ApplicationSource: source, + KubeVersion: renderContext.kubeVersion, + ApiVersions: renderContext.apiVersions, HasMultipleSources: hasMultipleSources, // Applications are patched to the default project before rendering. We // allow all source repos here so repo-server does not replace Helm @@ -554,11 +585,11 @@ func buildManifestRequestForSource( // repository than the PR repo. Those files are not checked out // locally, so we cannot stream them. Fall back to the remote // GenerateManifest RPC and let the repo server fetch them itself. - if !repoSelector.Matches(primarySource.RepoURL) { + if !renderContext.repoSelector.Matches(primarySource.RepoURL) { log.Debug(). Str("App", app.GetLongName()). Str("sourceRepoURL", primarySource.RepoURL). - Str("prRepo", repoSelector.String()). + Str("prRepo", renderContext.repoSelector.String()). Msg("Source repoURL does not match PR repo - using remote RPC") return newManifestRequest(&primarySource), "", nil, nil } @@ -593,11 +624,11 @@ func buildManifestRequestForSource( // The same applies when a ref source lives outside the PR repository. Copying // the local branch folder into .refs would provide the wrong repository // content, especially for ref-only sources whose Path is empty. - if !repoSelector.Matches(primarySource.RepoURL) || hasExternalRefSource(refSources, repoSelector) { + if !renderContext.repoSelector.Matches(primarySource.RepoURL) || hasExternalRefSource(refSources, renderContext.repoSelector) { log.Debug(). Str("App", app.GetLongName()). Str("sourceRepoURL", primarySource.RepoURL). - Str("prRepo", repoSelector.String()). + Str("prRepo", renderContext.repoSelector.String()). Msg("Source or ref repoURL does not match PR repo (slow path) - using remote RPC") request = newManifestRequest(&primarySource) request.RefSources = buildRefSourcesMap(refSources, creds) diff --git a/pkg/reposerverextract/extract_test.go b/pkg/reposerverextract/extract_test.go index a2322768..92907e19 100644 --- a/pkg/reposerverextract/extract_test.go +++ b/pkg/reposerverextract/extract_test.go @@ -107,7 +107,21 @@ spec: require.Empty(t, refSources) assert.False(t, hasMultipleSources) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + kubeVersion := "v1.30.1" + apiVersions := []string{"apps/v1", "v1"} + req, streamDir, cleanup, err := buildManifestRequestForSource( + app, + contentSources[0], + refSources, + hasMultipleSources, + branchFolder, + nil, + manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, ""), + kubeVersion: kubeVersion, + apiVersions: apiVersions, + }, + ) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -118,6 +132,8 @@ spec: assert.Equal(t, "apps/my-app", req.ApplicationSource.Path) assert.Empty(t, req.ApplicationSource.Chart, "should not have a Chart field") assert.Equal(t, "production", req.Namespace) + assert.Equal(t, kubeVersion, req.KubeVersion) + assert.Equal(t, apiVersions, req.ApiVersions) assert.Nil(t, req.RefSources) assertDefaultProjectFields(t, req) } @@ -149,7 +165,8 @@ spec: require.NoError(t, err) require.Len(t, contentSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, "")}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -217,7 +234,8 @@ spec: require.Len(t, contentSources, 1, "only the chart source is a content source") require.Len(t, refSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, "")}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -299,7 +317,8 @@ spec: require.Len(t, contentSources, 1) require.Len(t, refSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, "")}) require.NoError(t, err) require.NotEmpty(t, streamDir, "local chart with refs must stream a temp dir") defer cleanup() @@ -410,7 +429,8 @@ spec: } require.NotEmpty(t, chartSource.Chart, "should find the chart content source") - req, streamDir, cleanup, err := buildManifestRequestForSource(app, chartSource, refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, chartSource, refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, "")}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -470,7 +490,8 @@ spec: require.Len(t, contentSources, 1) require.Len(t, refSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, "")}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -555,7 +576,8 @@ spec: // Capture requests so we can verify per-source paths without duplicate calls. reqs := make([]struct{ path string }, len(contentSources)) for i, cs := range contentSources { - req, streamDir, cleanup, buildErr := buildManifestRequestForSource(app, cs, refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, "")) + req, streamDir, cleanup, buildErr := buildManifestRequestForSource(app, cs, refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, "")}) require.NoError(t, buildErr, "content source %d should not error", i) if cleanup != nil { defer cleanup() @@ -615,7 +637,8 @@ spec: require.Len(t, contentSources, 1) require.Empty(t, refSources) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, prRepo)) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, prRepo)}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -665,7 +688,8 @@ spec: require.NoError(t, err) require.Len(t, contentSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, prRepo)) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, prRepo)}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -706,7 +730,8 @@ spec: require.Len(t, contentSources, 1) require.Len(t, refSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, prRepo)) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, prRepo)}) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -753,7 +778,9 @@ spec: require.Len(t, contentSources, 1) require.Len(t, refSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, prRepo)) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, prRepo), + }) require.NoError(t, err) if cleanup != nil { defer cleanup() @@ -859,7 +886,8 @@ spec: require.NoError(t, err) require.Len(t, contentSources, 1) - req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, testRepoSelector(t, prRepo)) + req, streamDir, cleanup, err := buildManifestRequestForSource(app, contentSources[0], refSources, hasMultipleSources, branchFolder, nil, manifestRequestRenderContext{ + repoSelector: testRepoSelector(t, prRepo)}) require.NoError(t, err) if cleanup != nil { defer cleanup()