Skip to content

Add ArgoCD plugin support#134

Merged
ewega merged 3 commits intomainfrom
copilot/add-argocd-plugin-support
Mar 11, 2026
Merged

Add ArgoCD plugin support#134
ewega merged 3 commits intomainfrom
copilot/add-argocd-plugin-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 11, 2026

Adds ArgoCD as a supported DevLake plugin, enabling GitOps deployment frequency and change tracking data for DORA metrics.

Changes

  • internal/devlake/types.go — New ArgoCDAppScope struct (connectionId + name) matching the ArgoCD plugin's scope ID convention
  • cmd/connection_types.goargocd entry in connectionRegistry: Available: true, AccessToken auth, ScopeIDField: "name", no default endpoint (user-supplied), env keys ARGOCD_TOKEN / ARGOCD_AUTH_TOKEN
  • cmd/configure_scopes.goscopeArgoCDHandler: paginates ListRemoteScopes, presents interactive multi-select over returned applications, PUTs selections as ArgoCDAppScope scopes
  • cmd/connection_types_test.goTestConnectionRegistry_ArgoCD covering all registry fields
  • README.md — ArgoCD row added to supported plugins table

Usage

gh devlake configure connection add --plugin argocd --endpoint https://argocd.example.com
gh devlake configure scope add --plugin argocd --id <conn-id>

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copilot AI changed the title [WIP] Add ArgoCD as a supported DevOps tool in CLI Add ArgoCD plugin support Mar 11, 2026
@ewega ewega marked this pull request as ready for review March 11, 2026 21:58
Copilot AI review requested due to automatic review settings March 11, 2026 21:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds ArgoCD as a supported DevLake plugin in this GitHub CLI extension, enabling users to configure ArgoCD connections and select application scopes for GitOps/DORA-related collection.

Changes:

  • Introduces ArgoCDAppScope payload type for batch scope upserts.
  • Registers the argocd plugin in connectionRegistry and adds an interactive scope selection handler.
  • Adds registry tests and documents ArgoCD in the supported plugins table.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/devlake/types.go Adds ArgoCDAppScope for PUT /scopes payloads.
cmd/connection_types.go Registers argocd plugin metadata and wires scopeArgoCDHandler.
cmd/configure_scopes.go Implements ArgoCD remote-scope pagination, selection, and scope upsert.
cmd/connection_types_test.go Adds TestConnectionRegistry_ArgoCD for registry field coverage.
README.md Documents ArgoCD as a supported plugin.
Comments suppressed due to low confidence (1)

cmd/configure_scopes.go:1343

  • ScopeName is set to child.Name, but earlier you already fall back to child.ID when child.Name is empty for display. If child.Name is missing, this will create blueprint scopes with an empty scopeName, which makes later output/UI harder to interpret. Consider falling back to child.ID (or the selected label) when child.Name is empty.
		blueprintScopes = append(blueprintScopes, devlake.BlueprintScope{
			ScopeID:   child.ID,
			ScopeName: child.Name,
		})

Comment on lines +1311 to +1317
label := child.Name
if label == "" {
label = child.ID
}
appOptions = append(appOptions, label)
appMap[label] = child
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appMap is keyed by the user-facing label (child.Name fallback to child.ID). If multiple ArgoCD applications share the same Name (or if Name is not guaranteed unique), later entries overwrite earlier ones and the selected label can resolve to the wrong scope. Consider making the option label unambiguous (e.g., include the ID in the label like the SonarQube handler does) or keep a label→ID map and key the child map by ID.

This issue also appears on line 1340 of the same file.

Copilot uses AI. Check for mistakes.
Comment on lines +807 to +859
}{
{"Plugin", def.Plugin, "argocd"},
{"DisplayName", def.DisplayName, "ArgoCD"},
{"Available", def.Available, true},
{"Endpoint", def.Endpoint, ""},
{"SupportsTest", def.SupportsTest, true},
{"AuthMethod", def.AuthMethod, "AccessToken"},
{"ScopeIDField", def.ScopeIDField, "name"},
{"HasRepoScopes", def.HasRepoScopes, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.want {
t.Errorf("%s: got %v, want %v", tt.name, tt.got, tt.want)
}
})
}

if def.ScopeFunc == nil {
t.Error("ScopeFunc should not be nil")
}

// ArgoCD uses auth tokens, not OAuth/PAT scopes
if len(def.RequiredScopes) != 0 {
t.Errorf("RequiredScopes should be empty for ArgoCD auth tokens, got %v", def.RequiredScopes)
}
if def.ScopeHint != "" {
t.Errorf("ScopeHint should be empty for ArgoCD auth tokens, got %q", def.ScopeHint)
}

expectedEnvVars := []string{"ARGOCD_TOKEN", "ARGOCD_AUTH_TOKEN"}
if len(def.EnvVarNames) != len(expectedEnvVars) {
t.Errorf("EnvVarNames length: got %d, want %d", len(def.EnvVarNames), len(expectedEnvVars))
} else {
for i, v := range expectedEnvVars {
if def.EnvVarNames[i] != v {
t.Errorf("EnvVarNames[%d]: got %q, want %q", i, def.EnvVarNames[i], v)
}
}
}

expectedEnvFileKeys := []string{"ARGOCD_TOKEN", "ARGOCD_AUTH_TOKEN"}
if len(def.EnvFileKeys) != len(expectedEnvFileKeys) {
t.Errorf("EnvFileKeys length: got %d, want %d", len(def.EnvFileKeys), len(expectedEnvFileKeys))
} else {
for i, v := range expectedEnvFileKeys {
if def.EnvFileKeys[i] != v {
t.Errorf("EnvFileKeys[%d]: got %q, want %q", i, def.EnvFileKeys[i], v)
}
}
}
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test block is not gofmt-formatted (indentation/tabs), unlike the rest of the file. Please run gofmt on this file (or at least this new function) to keep formatting consistent and avoid noisy diffs later.

Suggested change
}{
{"Plugin", def.Plugin, "argocd"},
{"DisplayName", def.DisplayName, "ArgoCD"},
{"Available", def.Available, true},
{"Endpoint", def.Endpoint, ""},
{"SupportsTest", def.SupportsTest, true},
{"AuthMethod", def.AuthMethod, "AccessToken"},
{"ScopeIDField", def.ScopeIDField, "name"},
{"HasRepoScopes", def.HasRepoScopes, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.want {
t.Errorf("%s: got %v, want %v", tt.name, tt.got, tt.want)
}
})
}
if def.ScopeFunc == nil {
t.Error("ScopeFunc should not be nil")
}
// ArgoCD uses auth tokens, not OAuth/PAT scopes
if len(def.RequiredScopes) != 0 {
t.Errorf("RequiredScopes should be empty for ArgoCD auth tokens, got %v", def.RequiredScopes)
}
if def.ScopeHint != "" {
t.Errorf("ScopeHint should be empty for ArgoCD auth tokens, got %q", def.ScopeHint)
}
expectedEnvVars := []string{"ARGOCD_TOKEN", "ARGOCD_AUTH_TOKEN"}
if len(def.EnvVarNames) != len(expectedEnvVars) {
t.Errorf("EnvVarNames length: got %d, want %d", len(def.EnvVarNames), len(expectedEnvVars))
} else {
for i, v := range expectedEnvVars {
if def.EnvVarNames[i] != v {
t.Errorf("EnvVarNames[%d]: got %q, want %q", i, def.EnvVarNames[i], v)
}
}
}
expectedEnvFileKeys := []string{"ARGOCD_TOKEN", "ARGOCD_AUTH_TOKEN"}
if len(def.EnvFileKeys) != len(expectedEnvFileKeys) {
t.Errorf("EnvFileKeys length: got %d, want %d", len(def.EnvFileKeys), len(expectedEnvFileKeys))
} else {
for i, v := range expectedEnvFileKeys {
if def.EnvFileKeys[i] != v {
t.Errorf("EnvFileKeys[%d]: got %q, want %q", i, def.EnvFileKeys[i], v)
}
}
}
}
}{
{"Plugin", def.Plugin, "argocd"},
{"DisplayName", def.DisplayName, "ArgoCD"},
{"Available", def.Available, true},
{"Endpoint", def.Endpoint, ""},
{"SupportsTest", def.SupportsTest, true},
{"AuthMethod", def.AuthMethod, "AccessToken"},
{"ScopeIDField", def.ScopeIDField, "name"},
{"HasRepoScopes", def.HasRepoScopes, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.want {
t.Errorf("%s: got %v, want %v", tt.name, tt.got, tt.want)
}
})
}
if def.ScopeFunc == nil {
t.Error("ScopeFunc should not be nil")
}
// ArgoCD uses auth tokens, not OAuth/PAT scopes
if len(def.RequiredScopes) != 0 {
t.Errorf("RequiredScopes should be empty for ArgoCD auth tokens, got %v", def.RequiredScopes)
}
if def.ScopeHint != "" {
t.Errorf("ScopeHint should be empty for ArgoCD auth tokens, got %q", def.ScopeHint)
}
expectedEnvVars := []string{"ARGOCD_TOKEN", "ARGOCD_AUTH_TOKEN"}
if len(def.EnvVarNames) != len(expectedEnvVars) {
t.Errorf("EnvVarNames length: got %d, want %d", len(def.EnvVarNames), len(expectedEnvVars))
} else {
for i, v := range expectedEnvVars {
if def.EnvVarNames[i] != v {
t.Errorf("EnvVarNames[%d]: got %q, want %q", i, def.EnvVarNames[i], v)
}
}
}
expectedEnvFileKeys := []string{"ARGOCD_TOKEN", "ARGOCD_AUTH_TOKEN"}
if len(def.EnvFileKeys) != len(expectedEnvFileKeys) {
t.Errorf("EnvFileKeys length: got %d, want %d", len(def.EnvFileKeys), len(expectedEnvFileKeys))
} else {
for i, v := range expectedEnvFileKeys {
if def.EnvFileKeys[i] != v {
t.Errorf("EnvFileKeys[%d]: got %q, want %q", i, def.EnvFileKeys[i], v)
}
}
}
}

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 11, 2026

@copilot Please address the review findings on this PR: 1) make ArgoCD app selection labels/mapping unambiguous so duplicate names cannot overwrite each other or resolve to the wrong scope; 2) run gofmt on the new ArgoCD registry test block in cmd/connection_types_test.go. After updating, rerun build/test/vet and let the review agent re-check.

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot Please address the review findings on this PR: 1) make ArgoCD app selection labels/mapping unambiguous so duplicate names cannot overwrite each other or resolve to the wrong scope; 2) run gof...

Both issues addressed in bce5d6f:

  1. Unambiguous labels: The app selection label is now "name (id)" when a name exists, falling back to just id — matching the SonarQube pattern. Since the ID is always unique, duplicate display names can no longer overwrite each other in the map.

  2. gofmt formatting: Rewrote the TestConnectionRegistry_ArgoCD function with proper tab indentation; gofmt -l now returns no issues.

Copy link
Copy Markdown
Contributor

@ewega ewega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed after follow-up fixes; no remaining merge blockers found.

@ewega ewega merged commit da53593 into main Mar 11, 2026
4 checks passed
@ewega ewega deleted the copilot/add-argocd-plugin-support branch March 11, 2026 22:20
@ewega ewega mentioned this pull request Mar 12, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants