Skip to content

Commit a6133ae

Browse files
committed
feat: fix lint issues
1 parent d07924b commit a6133ae

7 files changed

Lines changed: 195 additions & 31 deletions

File tree

cli/cmd/bootstrap_integration_test.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestBootstrapIntegration_PermissionDenied(t *testing.T) {
2020
mockClient.EnsureNamespaceForbidden = true
2121

2222
ctx := context.Background()
23-
err := mockClient.EnsureNamespace(ctx, "argocd")
23+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
2424
require.Error(t, err)
2525
assert.Contains(t, err.Error(), "permission denied")
2626
assert.Contains(t, err.Error(), "cannot create namespace")
@@ -32,9 +32,10 @@ func TestBootstrapIntegration_SecretCreationFails(t *testing.T) {
3232
mockClient.CreateSecretForbidden = true
3333

3434
ctx := context.Background()
35-
require.NoError(t, mockClient.EnsureNamespace(ctx, "argocd"))
35+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
36+
require.NoError(t, err)
3637

37-
_, _, err := mockClient.CreateRepoSSHSecret(ctx, "ssh://git@example.com/repo.git", "key-data", false)
38+
_, _, err = mockClient.CreateRepoSSHSecret(ctx, "ssh://git@example.com/repo.git", "key-data", false)
3839
require.Error(t, err)
3940
assert.Contains(t, err.Error(), "permission denied")
4041
assert.Contains(t, err.Error(), "cannot create secrets")
@@ -74,7 +75,8 @@ func TestBootstrapIntegration_SuccessfulFlow(t *testing.T) {
7475
ctx := context.Background()
7576

7677
// Step 1: Ensure namespace
77-
require.NoError(t, mockClient.EnsureNamespace(ctx, "argocd"))
78+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
79+
require.NoError(t, err)
7880
assert.True(t, mockClient.Namespaces["argocd"])
7981

8082
// Step 2: Create repo SSH secret
@@ -99,10 +101,11 @@ func TestBootstrapIntegration_DryRun(t *testing.T) {
99101
mockClient := k8s.NewMockClient()
100102

101103
ctx := context.Background()
102-
require.NoError(t, mockClient.EnsureNamespace(ctx, "argocd"))
104+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
105+
require.NoError(t, err)
103106

104107
// Create secret in dry-run mode
105-
_, _, err := mockClient.CreateRepoSSHSecret(ctx, "ssh://git@example.com/repo.git", "key", true)
108+
_, _, err = mockClient.CreateRepoSSHSecret(ctx, "ssh://git@example.com/repo.git", "key", true)
106109
require.NoError(t, err)
107110

108111
// Secret should not be stored
@@ -115,7 +118,8 @@ func TestBootstrapIntegration_GitCryptKey(t *testing.T) {
115118
mockClient := k8s.NewMockClient()
116119
ctx := context.Background()
117120

118-
require.NoError(t, mockClient.EnsureNamespace(ctx, "argocd"))
121+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
122+
require.NoError(t, err)
119123

120124
keyData := []byte("mock-git-crypt-key-data")
121125
created, err := mockClient.CreateGitCryptKeySecret(ctx, keyData)
@@ -135,15 +139,16 @@ func TestBootstrapIntegration_SequentialErrors(t *testing.T) {
135139

136140
// First attempt: permission denied
137141
mockClient.EnsureNamespaceForbidden = true
138-
err := mockClient.EnsureNamespace(ctx, "argocd")
142+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
139143
require.Error(t, err)
140144
assert.Contains(t, err.Error(), "permission denied")
141145

142146
// Simulate fixing the permission issue
143147
mockClient.EnsureNamespaceForbidden = false
144148

145149
// Retry: should succeed
146-
require.NoError(t, mockClient.EnsureNamespace(ctx, "argocd"))
150+
_, err = mockClient.EnsureNamespace(ctx, "argocd")
151+
require.NoError(t, err)
147152
assert.True(t, mockClient.Namespaces["argocd"])
148153
}
149154

@@ -165,8 +170,9 @@ func TestBootstrapIntegration_AppOfAppsWithEnv(t *testing.T) {
165170
mockClient := k8s.NewMockClient()
166171
ctx := context.Background()
167172

168-
require.NoError(t, mockClient.EnsureNamespace(ctx, "argocd"))
169-
_, _, err := mockClient.CreateRepoSSHSecret(ctx, "ssh://git@example.com/repo.git", "key", false)
173+
_, err := mockClient.EnsureNamespace(ctx, "argocd")
174+
require.NoError(t, err)
175+
_, _, err = mockClient.CreateRepoSSHSecret(ctx, "ssh://git@example.com/repo.git", "key", false)
170176
require.NoError(t, err)
171177

172178
_, _, err = mockClient.ApplyAppOfApps(ctx, "ssh://git@example.com/repo.git", "main", tt.env, tt.appPath, false)

cli/cmd/template.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,21 @@ func runCustomize(cmd *cobra.Command, args []string) error {
9393
currentRepo = defaultRepo
9494
}
9595

96+
// Detect current app path from apps/values.yaml
97+
currentAppPath, err := detectCurrentAppPath(workspaceRoot)
98+
if err != nil {
99+
if verbose {
100+
fmt.Printf("⚠️ Could not detect current app path: %v. Using default.\n", err)
101+
}
102+
currentAppPath = defaultAppPath
103+
}
104+
96105
// Show summary
97106
fmt.Println("\n📝 Template Customization Summary")
98107
fmt.Println("═══════════════════════════════════")
99108
fmt.Printf("Organization: %s → %s\n", currentOrg, orgFlag)
100109
fmt.Printf("Repository: %s → %s\n", currentRepo, repoFlag)
101-
fmt.Printf("App Path: %s\n", appPathFlag)
110+
fmt.Printf("App Path: %s → %s\n", currentAppPath, appPathFlag)
102111
fmt.Printf("Go Module: github.com/%s/%s → github.com/%s/%s\n",
103112
currentOrg, currentRepo, orgFlag, repoFlag)
104113

@@ -167,7 +176,7 @@ func runCustomize(cmd *cobra.Command, args []string) error {
167176
},
168177
{
169178
name: "App path in values",
170-
pattern: fmt.Sprintf("path: %s", defaultAppPath),
179+
pattern: fmt.Sprintf("path: %s", currentAppPath),
171180
replace: fmt.Sprintf("path: %s", appPathFlag),
172181
files: []string{"apps/values.yaml"},
173182
},
@@ -324,6 +333,24 @@ func detectCurrentValues(workspaceRoot string) (org, repo string, err error) {
324333
return matches[1], matches[2], nil
325334
}
326335

336+
func detectCurrentAppPath(workspaceRoot string) (string, error) {
337+
valuesPath := filepath.Join(workspaceRoot, "apps", "values.yaml")
338+
content, err := os.ReadFile(valuesPath) //#nosec G304 -- path is constructed from detected workspace root, not user input
339+
if err != nil {
340+
return "", err
341+
}
342+
343+
// Parse "path: <value>" from values.yaml
344+
// Look for source.path or just path: in the file
345+
re := regexp.MustCompile(`(?m)^\s*path:\s+(\S+)`)
346+
matches := re.FindStringSubmatch(string(content))
347+
if len(matches) != 2 {
348+
return "", fmt.Errorf("could not parse path from apps/values.yaml")
349+
}
350+
351+
return matches[1], nil
352+
}
353+
327354
func getWorkspaceRoot() (string, error) {
328355
// Get current working directory
329356
cwd, err := os.Getwd()

cli/cmd/template_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,80 @@ go 1.25.0
127127
assert.Error(t, err)
128128
}
129129

130+
func TestDetectCurrentAppPath(t *testing.T) {
131+
tests := []struct {
132+
name string
133+
valuesYAML string
134+
expectedPath string
135+
wantErr bool
136+
}{
137+
{
138+
name: "standard app path",
139+
valuesYAML: `source:
140+
repoURL: git@github.com:org/repo.git
141+
targetRevision: main
142+
path: apps
143+
destination:
144+
server: https://kubernetes.default.svc
145+
`,
146+
expectedPath: "apps",
147+
wantErr: false,
148+
},
149+
{
150+
name: "custom app path",
151+
valuesYAML: `source:
152+
repoURL: git@github.com:org/repo.git
153+
targetRevision: main
154+
path: custom/my-apps
155+
destination:
156+
server: https://kubernetes.default.svc
157+
`,
158+
expectedPath: "custom/my-apps",
159+
wantErr: false,
160+
},
161+
{
162+
name: "indented path",
163+
valuesYAML: `spec:
164+
source:
165+
repoURL: git@github.com:org/repo.git
166+
targetRevision: main
167+
path: nested-apps
168+
`,
169+
expectedPath: "nested-apps",
170+
wantErr: false,
171+
},
172+
{
173+
name: "missing path",
174+
valuesYAML: `source:
175+
repoURL: git@github.com:org/repo.git
176+
targetRevision: main
177+
`,
178+
wantErr: true,
179+
},
180+
}
181+
182+
for _, tt := range tests {
183+
t.Run(tt.name, func(t *testing.T) {
184+
tmpDir := t.TempDir()
185+
appsDir := filepath.Join(tmpDir, "apps")
186+
err := os.MkdirAll(appsDir, 0755)
187+
require.NoError(t, err)
188+
189+
valuesPath := filepath.Join(appsDir, "values.yaml")
190+
err = os.WriteFile(valuesPath, []byte(tt.valuesYAML), 0644)
191+
require.NoError(t, err)
192+
193+
path, err := detectCurrentAppPath(tmpDir)
194+
if tt.wantErr {
195+
assert.Error(t, err)
196+
} else {
197+
assert.NoError(t, err)
198+
assert.Equal(t, tt.expectedPath, path)
199+
}
200+
})
201+
}
202+
}
203+
130204
func TestApplyReplacement(t *testing.T) {
131205
// Create temporary workspace
132206
tmpDir := t.TempDir()
@@ -426,3 +500,50 @@ import (
426500
require.NoError(t, err)
427501
assert.Contains(t, string(updatedGoFile), `"github.com/mycompany/k8s-gitops/cli/internal/config"`)
428502
}
503+
504+
func TestReCustomization_AppPath(t *testing.T) {
505+
// Test that re-customization with a different app-path works correctly
506+
// This validates the fix for detecting current app path instead of hardcoding "apps"
507+
tmpDir := t.TempDir()
508+
509+
// Create directories
510+
appsDir := filepath.Join(tmpDir, "apps")
511+
err := os.MkdirAll(appsDir, 0755)
512+
require.NoError(t, err)
513+
514+
// Create apps/values.yaml with a custom path (already customized once)
515+
appsValuesContent := `repoSource:
516+
url: git@github.com:mycompany/k8s-gitops.git
517+
path: custom/apps
518+
destination:
519+
server: https://kubernetes.default.svc
520+
`
521+
err = os.WriteFile(filepath.Join(appsDir, "values.yaml"), []byte(appsValuesContent), 0644)
522+
require.NoError(t, err)
523+
524+
// Detect current app path
525+
currentAppPath, err := detectCurrentAppPath(tmpDir)
526+
require.NoError(t, err)
527+
assert.Equal(t, "custom/apps", currentAppPath)
528+
529+
// Now re-customize with a different app path
530+
newAppPath := "kubernetes/applications"
531+
532+
// Apply replacement using detected current path
533+
r := replacement{
534+
name: "App path",
535+
pattern: fmt.Sprintf("path: %s", currentAppPath),
536+
replace: fmt.Sprintf("path: %s", newAppPath),
537+
files: []string{"apps/values.yaml"},
538+
}
539+
540+
count, err := applyReplacement(tmpDir, r, false)
541+
require.NoError(t, err)
542+
assert.Equal(t, 1, count, "Expected 1 file to be updated")
543+
544+
// Verify the change
545+
updatedAppsValues, err := os.ReadFile(filepath.Join(appsDir, "values.yaml"))
546+
require.NoError(t, err)
547+
assert.Contains(t, string(updatedAppsValues), "path: kubernetes/applications")
548+
assert.NotContains(t, string(updatedAppsValues), "path: custom/apps")
549+
}

cli/cmd/vault_token.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func runVaultToken(cmd *cobra.Command, args []string) error {
5656

5757
ctx := context.Background()
5858

59-
if err := client.EnsureNamespace(ctx, "vault"); err != nil {
59+
if _, err := client.EnsureNamespace(ctx, "vault"); err != nil {
6060
return err
6161
}
6262

cli/internal/k8s/client_mock.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,21 @@ func NewMockClient() *MockClient {
3636
}
3737

3838
// EnsureNamespace simulates namespace creation with mock state.
39-
func (m *MockClient) EnsureNamespace(ctx context.Context, name string) error {
39+
func (m *MockClient) EnsureNamespace(ctx context.Context, name string) (bool, error) {
4040
if m.EnsureNamespaceErr != nil {
41-
return m.EnsureNamespaceErr
41+
return false, m.EnsureNamespaceErr
4242
}
4343
if m.EnsureNamespaceForbidden {
44-
return fmt.Errorf("permission denied: cannot create namespace %s: Forbidden", name)
44+
return false, fmt.Errorf("permission denied: cannot create namespace %s: Forbidden", name)
4545
}
46+
47+
// Check if namespace already exists
48+
if m.Namespaces[name] {
49+
return false, nil
50+
}
51+
4652
m.Namespaces[name] = true
47-
return nil
53+
return true, nil
4854
}
4955

5056
// CreateRepoSSHSecret simulates secret creation with mock state.
@@ -178,7 +184,7 @@ func (m *MockClient) GetApplication(name string) *unstructured.Unstructured {
178184
// ClientInterface defines the interface that both Client and MockClient implement.
179185
// This is useful for testing code that uses a K8s client.
180186
type ClientInterface interface {
181-
EnsureNamespace(ctx context.Context, name string) error
187+
EnsureNamespace(ctx context.Context, name string) (bool, error)
182188
CreateRepoSSHSecret(ctx context.Context, repoURL, sshPrivateKey string, dryRun bool) (*corev1.Secret, bool, error)
183189
CreateGitCryptKeySecret(ctx context.Context, keyData []byte) (bool, error)
184190
ApplyAppOfApps(ctx context.Context, repoURL, targetRevision, env, appPath string, dryRun bool) (string, bool, error)

cli/internal/k8s/secrets.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import (
1010
)
1111

1212
// EnsureNamespace creates a namespace if it does not already exist.
13-
func (c *Client) EnsureNamespace(ctx context.Context, name string) error {
13+
func (c *Client) EnsureNamespace(ctx context.Context, name string) (bool, error) {
1414
_, err := c.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
1515
if err == nil {
16-
return nil
16+
return false, nil
1717
}
1818
if !apierrors.IsNotFound(err) {
1919
if apierrors.IsForbidden(err) {
20-
return fmt.Errorf("permission denied: cannot get namespace %s: %w\n hint: verify your cluster role has permission to get namespaces", name, err)
20+
return false, fmt.Errorf("permission denied: cannot get namespace %s: %w\n hint: verify your cluster role has permission to get namespaces", name, err)
2121
}
22-
return fmt.Errorf("failed to get namespace %s: %w", name, err)
22+
return false, fmt.Errorf("failed to get namespace %s: %w", name, err)
2323
}
2424

2525
ns := &corev1.Namespace{
@@ -30,11 +30,11 @@ func (c *Client) EnsureNamespace(ctx context.Context, name string) error {
3030
_, err = c.Clientset.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
3131
if err != nil {
3232
if apierrors.IsForbidden(err) {
33-
return fmt.Errorf("permission denied: cannot create namespace %s: %w\n hint: verify your cluster role has permission to create namespaces", name, err)
33+
return false, fmt.Errorf("permission denied: cannot create namespace %s: %w\n hint: verify your cluster role has permission to create namespaces", name, err)
3434
}
35-
return fmt.Errorf("failed to create namespace %s: %w", name, err)
35+
return false, fmt.Errorf("failed to create namespace %s: %w", name, err)
3636
}
37-
return nil
37+
return true, nil
3838
}
3939

4040
// CreateRepoSSHSecret creates or updates the repo-ssh-key secret in the argocd namespace.
@@ -102,7 +102,7 @@ func (c *Client) CreateRepoSSHSecret(ctx context.Context, repoURL, sshPrivateKey
102102
// The key data is the raw symmetric key used by git-crypt.
103103
// Returns a boolean indicating if it was created (true) or updated (false).
104104
func (c *Client) CreateGitCryptKeySecret(ctx context.Context, keyData []byte) (bool, error) {
105-
if err := c.EnsureNamespace(ctx, "argocd"); err != nil {
105+
if _, err := c.EnsureNamespace(ctx, "argocd"); err != nil {
106106
return false, err
107107
}
108108

0 commit comments

Comments
 (0)