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
3 changes: 2 additions & 1 deletion integration-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,12 @@ Each `branch-N/target[-suffix]/` directory contains expected output files (`outp
### Branch 15: Additional Coverage
- `target`: Basic diff (no special options)

### Branch 16-19: repo-server-api Render Method
### Branch 16-20: repo-server-api Render Method
- `branch-16/target`: Config Management Plugin (kustomize-build-with-helm) via `repo-server-api`
- `branch-17/target-1` / `target-2`: App-of-apps traversal via `repo-server-api`
- `branch-18/target`: Single-source local Helm chart whose `helm.valueFiles` reference a file OUTSIDE the chart directory via a repo-root-absolute path (`/examples/out-of-chart-values/env/values.yaml`). Regression test for `repo-server-api`: the tool must stream the whole branch folder so the out-of-chart value file is reachable, otherwise the render fails with `no such file or directory`.
- `branch-19/target`: Kustomize resource outside the application directory via `repo-server-api`. It requires global `kustomize.buildOptions: --load-restrictor LoadRestrictionsNone` from `argocd-cm`, proving the direct repo-server request forwards that setting.
- `branch-20/target`: Local Helm chart that gates a ServiceMonitor on the kind-level capability `monitoring.coreos.com/v1/ServiceMonitor`, via `repo-server-api` against a cluster with the ServiceMonitor CRD. The target enables it, so the diff must show the ServiceMonitor added, as a real Argo CD sync would render it.

## Prerequisites

Expand Down
111 changes: 111 additions & 0 deletions integration-test/branch-20/target/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: arial;
}
.container {
margin: auto;
width: 910px;
}
.diffs {
margin: 20px 0 20px 0;
}
.diff_container {
width: 910px;
overflow-x: scroll;
border-radius: 8px;
background:rgb(239, 239, 239);
scrollbar-width: none;
margin: 10px 0 10px 0;
}
table {
font-family: monospace;
border-spacing: 0px;
width: 100%;
}
tr.normal_line {
background:rgb(239, 239, 239);
}
tr.added_line {
background:rgb(169, 216, 184);
}
tr.removed_line {
background:rgb(247, 149, 173);
}
tr.comment_line {
background:rgb(197, 194, 194);
}
.resource_header {
font-family: monospace;
font-size: 14px;
color: rgb(80, 80, 80);
margin: 15px 0 5px 0;
padding: 0;
}
.resource_header:first-of-type {
margin-top: 10px;
}
pre {
margin: 0;
padding-left: 15px;
padding-right: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>Argo CD Diff Preview</h1>

<p>Summary:</p>
<pre>Modified (1):
± kind-level-capabilities (+12|-1)</pre>

<div class="diffs">
<details>
<summary>
kind-level-capabilities (examples/kind-level-capabilities/application.yaml)
</summary>

<h4 class="resource_header">ServiceMonitor: default/kind-level-capabilities</h4>
<div class="diff_container">
<table>

<tr class="added_line"><td><pre>+apiVersion: monitoring.coreos.com/v1</pre></td></tr>
<tr class="added_line"><td><pre>+kind: ServiceMonitor</pre></td></tr>
<tr class="added_line"><td><pre>+metadata:</pre></td></tr>
<tr class="added_line"><td><pre>+ name: kind-level-capabilities</pre></td></tr>
<tr class="added_line"><td><pre>+ namespace: default</pre></td></tr>
<tr class="added_line"><td><pre>+spec:</pre></td></tr>
<tr class="added_line"><td><pre>+ endpoints:</pre></td></tr>
<tr class="added_line"><td><pre>+ - port: metrics</pre></td></tr>
<tr class="added_line"><td><pre>+ selector:</pre></td></tr>
<tr class="added_line"><td><pre>+ matchLabels:</pre></td></tr>
<tr class="added_line"><td><pre>+ app.kubernetes.io/name: kind-level-capabilities</pre></td></tr>
</table>
</div>

<h4 class="resource_header">ConfigMap: default/kind-level-capabilities</h4>
<div class="diff_container">
<table>

<tr class="normal_line"><td><pre> apiVersion: v1</pre></td></tr>
<tr class="normal_line"><td><pre> data:</pre></td></tr>
<tr class="removed_line"><td><pre>- serviceMonitorEnabled: &#34;false&#34;</pre></td></tr>
<tr class="added_line"><td><pre>+ serviceMonitorEnabled: &#34;true&#34;</pre></td></tr>
<tr class="normal_line"><td><pre> kind: ConfigMap</pre></td></tr>
<tr class="normal_line"><td><pre> metadata:</pre></td></tr>
<tr class="normal_line"><td><pre> name: kind-level-capabilities</pre></td></tr>
<tr class="normal_line"><td><pre> namespace: default</pre></td></tr>
</table>
</div>

</details>
</div>

<pre>_Stats_:
[Applications: 2], [Full Run: Xs], [Rendering: Xs], [Cluster: Xs], [Argo CD: Xs]</pre>
</div>
</body>
</html>
41 changes: 41 additions & 0 deletions integration-test/branch-20/target/output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Argo CD Diff Preview

Summary:
```yaml
Modified (1):
± kind-level-capabilities (+12|-1)
```

<details>
<summary>kind-level-capabilities (examples/kind-level-capabilities/application.yaml)</summary>
<br>

#### ServiceMonitor: default/kind-level-capabilities
```diff
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+ name: kind-level-capabilities
+ namespace: default
+spec:
+ endpoints:
+ - port: metrics
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: kind-level-capabilities
```
#### ConfigMap: default/kind-level-capabilities
```diff
apiVersion: v1
data:
- serviceMonitorEnabled: "false"
+ serviceMonitorEnabled: "true"
kind: ConfigMap
metadata:
name: kind-level-capabilities
namespace: default
```
</details>

_Stats_:
[Applications: 2], [Full Run: Xs], [Rendering: Xs], [Cluster: Xs], [Argo CD: Xs]
13 changes: 13 additions & 0 deletions integration-test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,19 @@ var testCases = []TestCase{
FileRegex: "examples/(kustomize-build-options|resource-application-boundary|cluster-rbac-repro)/.*",
WatchIfNoWatchPatternFound: "false",
},
// Regression test for kind-level .Capabilities.APIVersions entries in
// repo-server-api mode. The chart gates a ServiceMonitor on
// "monitoring.coreos.com/v1/ServiceMonitor", which Argo CD includes next to
// the group version. The target enables it, so the diff must show it added.
{
Name: "branch-20/target",
TargetBranch: "integration-test/branch-20/target",
BaseBranch: "integration-test/branch-20/base",
RenderMethod: "repo-server-api",
ArgocdConfigDir: "with-servicemonitor-crd-no-cluster-roles",
FileRegex: "examples/kind-level-capabilities/.*",
WatchIfNoWatchPatternFound: "false",
},
// This test verifies that disabling cluster roles without using the API fails.
// When createClusterRoles: false is set but --render-method=cli is used,
// the tool should fail because it can't access cluster resources via CLI.
Expand Down
29 changes: 20 additions & 9 deletions pkg/k8s/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ func (c *Client) GetServerVersion() (string, error) {
}

// 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.
// "monitoring.coreos.com/v1") available in the cluster, plus a
// "<GroupVersion>/<Kind>" entry for every resource kind (e.g.
// "monitoring.coreos.com/v1/ServiceMonitor"), as Argo CD sends them. 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.GetClusterScopedResourcesAndAPIVersions()
if err != nil {
Expand All @@ -31,7 +33,8 @@ func (c *Client) GetAPIVersions() ([]string, error) {
}

// GetClusterScopedResourcesAndAPIVersions returns metadata about all cluster-scoped
// resource types and all unique GroupVersion strings in one discovery pass.
// resource types and all unique API version strings (see GetAPIVersions) in one
// discovery pass.
func (c *Client) GetClusterScopedResourcesAndAPIVersions() (map[schema.GroupKind]bool, []string, error) {
_, apiResourceLists, err := c.discoveryClient.ServerGroupsAndResources()
if err != nil {
Expand All @@ -55,14 +58,22 @@ func (c *Client) GetClusterScopedResourcesAndAPIVersions() (map[schema.GroupKind

// Check each resource in the API group
for _, apiResource := range apiResourceList.APIResources {
// Only retain cluster-scoped resources. A kind missing from this map is
// treated as namespaced, which is the safe default for undiscovered CRDs.
if apiResource.Namespaced {
// Skip subresources (e.g., "pods/log", "deployments/scale")
if strings.Contains(apiResource.Name, "/") {
continue
}

// Skip subresources (e.g., "pods/log", "deployments/scale")
if strings.Contains(apiResource.Name, "/") {
// Argo CD also sends every kind as "<GroupVersion>/<Kind>", and charts gate
// on that form, e.g. `.Capabilities.APIVersions.Has "monitoring.coreos.com/v1/ServiceMonitor"`.
kindVersion := apiResourceList.GroupVersion + "/" + apiResource.Kind
if !seen[kindVersion] {
seen[kindVersion] = true
apiVersions = append(apiVersions, kindVersion)
}

// Only retain cluster-scoped resources. A kind missing from this map is
// treated as namespaced, which is the safe default for undiscovered CRDs.
if apiResource.Namespaced {
continue
}

Expand Down
58 changes: 58 additions & 0 deletions pkg/k8s/discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package k8s

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
fakediscovery "k8s.io/client-go/discovery/fake"
clienttesting "k8s.io/client-go/testing"
)

func newFakeDiscoveryClient(resources []*metav1.APIResourceList) *Client {
return &Client{
discoveryClient: &fakediscovery.FakeDiscovery{Fake: &clienttesting.Fake{Resources: resources}},
}
}

func TestGetClusterScopedResourcesAndAPIVersions(t *testing.T) {
client := newFakeDiscoveryClient([]*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{Name: "pods", Kind: "Pod", Namespaced: true},
{Name: "pods/log", Kind: "Pod", Namespaced: true},
{Name: "namespaces", Kind: "Namespace", Namespaced: false},
},
},
{
GroupVersion: "apps/v1",
APIResources: []metav1.APIResource{
{Name: "deployments", Kind: "Deployment", Namespaced: true},
{Name: "deployments/scale", Kind: "Scale", Namespaced: true},
},
},
{
GroupVersion: "monitoring.coreos.com/v1",
APIResources: []metav1.APIResource{
{Name: "servicemonitors", Kind: "ServiceMonitor", Namespaced: true},
},
},
})

clusterScoped, apiVersions, err := client.GetClusterScopedResourcesAndAPIVersions()
require.NoError(t, err)

assert.Equal(t, []string{
"apps/v1",
"apps/v1/Deployment",
"monitoring.coreos.com/v1",
"monitoring.coreos.com/v1/ServiceMonitor",
"v1",
"v1/Namespace",
"v1/Pod",
}, apiVersions)
assert.Equal(t, map[schema.GroupKind]bool{{Group: "", Kind: "Namespace"}: true}, clusterScoped)
}
Loading