Skip to content

Commit a7e0e3c

Browse files
committed
refactor: Migrate manager CLI commands into devenv CLI with shared logic in internal/cli.
1 parent 90cfc77 commit a7e0e3c

File tree

17 files changed

+1005
-982
lines changed

17 files changed

+1005
-982
lines changed

cmd/devenv/auth.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package main
22

33
import (
4-
"context"
54
"fmt"
65
"os"
76

8-
"github.com/nauticalab/devenv-engine/internal/manager/auth"
9-
"github.com/nauticalab/devenv-engine/internal/manager/client"
7+
"github.com/nauticalab/devenv-engine/internal/cli"
8+
"github.com/nauticalab/devenv-engine/internal/config"
109
"github.com/spf13/cobra"
1110
)
1211

@@ -22,34 +21,13 @@ var authListCmd = &cobra.Command{
2221
Long: `Display the current authentication information, including the authenticated user and their developer identity.`,
2322
Run: func(cmd *cobra.Command, args []string) {
2423
// Load configuration
25-
config, err := LoadCLIConfig()
24+
cfg, err := config.LoadCLIConfig()
2625
if err != nil {
2726
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
2827
os.Exit(1)
2928
}
3029

31-
if config.ManagerURL == "" {
32-
fmt.Fprintf(os.Stderr, "Error: manager URL is required. Set DEVEN_MANAGER_URL env var or configure in ~/.devenv/config.yaml\n")
33-
os.Exit(1)
34-
}
35-
36-
// Create manager client
37-
authProvider := auth.NewK8sSAProvider(nil, "", "", "")
38-
c := client.NewClient(config.ManagerURL, authProvider)
39-
40-
// Get identity
41-
whoami, err := c.WhoAmI(context.Background())
42-
if err != nil {
43-
fmt.Printf("Error getting authentication info: %v\n", err)
44-
os.Exit(1)
45-
}
46-
47-
fmt.Printf("Authenticated as: %s\n", whoami.Username)
48-
fmt.Printf("Type: %s\n", whoami.Type)
49-
fmt.Printf("Developer: %s\n", whoami.Developer)
50-
if whoami.Namespace != "" {
51-
fmt.Printf("Namespace: %s\n", whoami.Namespace)
52-
}
30+
cli.AuthRunList(cfg.ManagerURL)
5331
},
5432
}
5533

cmd/devenv/generate.go

Lines changed: 11 additions & 297 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,11 @@ package main
33
import (
44
"fmt"
55
"os"
6-
"path/filepath"
7-
"strings"
8-
"time"
96

10-
"github.com/nauticalab/devenv-engine/internal/config"
11-
"github.com/nauticalab/devenv-engine/internal/templates"
7+
"github.com/nauticalab/devenv-engine/internal/cli"
128
"github.com/spf13/cobra"
139
)
1410

15-
// DeveloperJob represents work to be done for one developer
16-
type DeveloperJob struct {
17-
Name string
18-
}
19-
20-
// ProcessingResult represents the outcome of processing one developer
21-
type ProcessingResult struct {
22-
Developer string
23-
Success bool
24-
Error error
25-
Duration time.Duration
26-
}
27-
2811
var (
2912
// Command-specific flags for generate
3013
outputDir string
@@ -55,16 +38,20 @@ Examples:
5538
os.Exit(1)
5639
}
5740

58-
// Execute the logic (placeholder for now)
41+
opts := cli.GenerateOptions{
42+
OutputDir: outputDir,
43+
ConfigDir: configDir,
44+
DryRun: dryRun,
45+
Verbose: verbose,
46+
}
47+
48+
// Execute the logic
5949
if allDevs {
6050
fmt.Println("Generating manifests for all developers...")
61-
if verbose {
62-
fmt.Printf("Output directory: %s\n", outputDir)
63-
}
64-
generateAllDevelopersWithProgress()
51+
cli.GenerateRunAll(opts)
6552
} else {
6653
developerName := args[0]
67-
generateSingleDeveloper(developerName)
54+
cli.GenerateRunSingle(developerName, opts)
6855
}
6956
},
7057
}
@@ -77,276 +64,3 @@ func init() {
7764
generateCmd.Flags().BoolVar(&allDevs, "all-developers", false, "Generate manifests for all developers")
7865

7966
}
80-
81-
func generateAllDevelopersWithProgress() {
82-
// Step 1: Load global config once
83-
globalConfig, err := config.LoadGlobalConfig(configDir)
84-
if err != nil {
85-
fmt.Fprintf(os.Stderr, "Error loading global config in %s: %v\n", configDir, err)
86-
os.Exit(1)
87-
}
88-
89-
if verbose {
90-
fmt.Printf("Generating system manifests in %s\n", outputDir)
91-
}
92-
93-
// Step 2: Generate system manifests once
94-
if !dryRun {
95-
if err := generateSystemManifests(globalConfig, outputDir); err != nil {
96-
fmt.Fprintf(os.Stderr, "Error generating system manifests: %v\n", err)
97-
os.Exit(1)
98-
}
99-
}
100-
101-
// Step 3: Discover all developers
102-
developers, err := findAllDevelopers(configDir)
103-
if err != nil {
104-
fmt.Fprintf(os.Stderr, "Error discovering developers: %v\n", err)
105-
os.Exit(1)
106-
}
107-
108-
if len(developers) == 0 {
109-
fmt.Printf("No developers found in %s\n", configDir)
110-
return
111-
}
112-
113-
fmt.Printf("Found %d developers to process.\n", len(developers))
114-
115-
// Step 4: Set up channels for worker communication
116-
const numWorkers = 4
117-
jobs := make(chan DeveloperJob, len(developers))
118-
results := make(chan ProcessingResult, len(developers))
119-
120-
// Step 5: Start worker goroutines
121-
for i := 0; i < numWorkers; i++ {
122-
go developerWorker(jobs, results, globalConfig)
123-
}
124-
125-
// Step 6: Send all jobs to workers
126-
for _, dev := range developers {
127-
jobs <- DeveloperJob{Name: dev}
128-
}
129-
close(jobs)
130-
131-
// Step 7: Collect results
132-
var successCount, failureCount int
133-
var failures []ProcessingResult
134-
135-
for i := 0; i < len(developers); i++ {
136-
result := <-results
137-
if result.Success {
138-
successCount++
139-
fmt.Printf("[%d/%d] ✅ %s (%.1fs)\n",
140-
i+1, len(developers), result.Developer, result.Duration.Seconds())
141-
} else {
142-
failureCount++
143-
failures = append(failures, result)
144-
fmt.Printf("[%d/%d] ❌ %s (%.1fs): %v\n",
145-
i+1, len(developers), result.Developer, result.Duration.Seconds(), result.Error)
146-
}
147-
}
148-
149-
// Step 8: Print final summary
150-
fmt.Printf("\n🎉 Batch processing complete!\n")
151-
fmt.Printf("✅ Successful: %d\n", successCount)
152-
if failureCount > 0 {
153-
fmt.Printf("❌ Failed: %d\n", failureCount)
154-
}
155-
156-
if failureCount > 0 {
157-
fmt.Printf("\nFailures:\n")
158-
for _, failure := range failures {
159-
fmt.Printf(" - %s: %v\n", failure.Developer, failure.Error)
160-
}
161-
os.Exit(1) // Exit with error if any failures
162-
}
163-
}
164-
165-
func developerWorker(jobs <-chan DeveloperJob, results chan<- ProcessingResult, globalConfig *config.BaseConfig) {
166-
for job := range jobs {
167-
startTime := time.Now()
168-
err := processSingleDeveloperForBatchWithError(job.Name, globalConfig)
169-
170-
results <- ProcessingResult{
171-
Developer: job.Name,
172-
Success: err == nil,
173-
Error: err,
174-
Duration: time.Since(startTime),
175-
}
176-
}
177-
}
178-
179-
// processSingleDeveloperForBatchWithError processes a single developer for batch mode
180-
func processSingleDeveloperForBatchWithError(developerName string, globalConfig *config.BaseConfig) error {
181-
if verbose {
182-
fmt.Printf("Processing developer: %s\n", developerName)
183-
}
184-
185-
cfg, err := config.LoadDeveloperConfigWithBaseConfig(configDir, developerName, globalConfig)
186-
if err != nil {
187-
return fmt.Errorf("failed to load config: %w", err)
188-
}
189-
190-
// Create user-specific output directory
191-
userOutputDir := filepath.Join(outputDir, developerName)
192-
193-
if !dryRun {
194-
if err := generateDeveloperManifests(cfg, userOutputDir); err != nil {
195-
return fmt.Errorf("failed to generate manifests: %w", err)
196-
}
197-
}
198-
199-
return nil
200-
}
201-
202-
func findAllDevelopers(configDir string) ([]string, error) {
203-
var developers []string
204-
205-
entries, err := os.ReadDir(configDir)
206-
if err != nil {
207-
return nil, fmt.Errorf("failed to read config directory: %w", err)
208-
}
209-
210-
for _, entry := range entries {
211-
if entry.IsDir() {
212-
// Check to make sure devenv-config.yaml exists in this directory
213-
configPath := filepath.Join(configDir, entry.Name(), "devenv-config.yaml")
214-
if _, err := os.Stat(configPath); err == nil {
215-
developers = append(developers, entry.Name())
216-
}
217-
}
218-
}
219-
220-
return developers, nil
221-
}
222-
223-
// generateSingleDeveloper handles generation for a single developer
224-
func generateSingleDeveloper(developerName string) {
225-
fmt.Printf("Generating manifests for developer: %s\n", developerName)
226-
227-
if verbose {
228-
fmt.Printf("Output directory: %s\n", outputDir)
229-
fmt.Printf("Config directory: %s\n", configDir)
230-
fmt.Printf("Dry run mode: %t\n", dryRun)
231-
}
232-
233-
userOutputDir := filepath.Join(outputDir, developerName)
234-
235-
globalConfig, err := config.LoadGlobalConfig(configDir)
236-
if err != nil {
237-
fmt.Fprintf(os.Stderr, "Error loading global config in %s: %v\n", configDir, err)
238-
os.Exit(1)
239-
}
240-
241-
if err := generateSystemManifests(globalConfig, outputDir); err != nil {
242-
fmt.Fprintf(os.Stderr, "Error generating system manifests: %v\n", err)
243-
os.Exit(1)
244-
}
245-
246-
cfg, err := config.LoadDeveloperConfigWithBaseConfig(configDir, developerName, globalConfig)
247-
if err != nil {
248-
fmt.Fprintf(os.Stderr, "Error loading config for developer %s: %v\n", developerName, err)
249-
os.Exit(1)
250-
}
251-
252-
fmt.Printf("✅ Successfully loaded configuration for developer: %s\n", cfg.Name)
253-
254-
if verbose {
255-
printConfigSummary(cfg)
256-
}
257-
258-
if !dryRun {
259-
if err := generateDeveloperManifests(cfg, userOutputDir); err != nil {
260-
fmt.Fprintf(os.Stderr, "Error generating manifests: %v\n", err)
261-
os.Exit(1)
262-
}
263-
} else {
264-
fmt.Printf("🔍 Dry run - would generate manifests to: %s\n", userOutputDir)
265-
}
266-
}
267-
268-
func generateSystemManifests(cfg *config.BaseConfig, outputDir string) error {
269-
// Create template renderer
270-
renderer := templates.NewSystemRenderer(outputDir)
271-
272-
// Render all main templates
273-
if err := renderer.RenderAll(cfg); err != nil {
274-
return fmt.Errorf("failed to render templates: %w", err)
275-
}
276-
277-
fmt.Printf("🎉 Successfully generated system manifests\n")
278-
279-
return nil
280-
}
281-
282-
// generateDeveloperManifests creates Kubernetes manifests for a developer
283-
func generateDeveloperManifests(cfg *config.DevEnvConfig, outputDir string) error {
284-
// Create template renderer
285-
renderer := templates.NewDevRenderer(outputDir)
286-
287-
// Render all main templates
288-
if err := renderer.RenderAll(cfg); err != nil {
289-
return fmt.Errorf("failed to render templates: %w", err)
290-
}
291-
292-
fmt.Printf("🎉 Successfully generated manifests for %s\n", cfg.Name)
293-
294-
return nil
295-
}
296-
297-
// Helper function to print config summary
298-
func printConfigSummary(cfg *config.DevEnvConfig) {
299-
fmt.Printf("\nConfiguration Summary:\n")
300-
fmt.Printf(" Name: %s\n", cfg.Name)
301-
302-
sshKeys, _ := cfg.GetSSHKeys()
303-
fmt.Printf(" SSH Keys: %d configured\n", len(sshKeys))
304-
305-
if cfg.SSHPort != 0 {
306-
fmt.Printf(" SSH Port: %d\n", cfg.SSHPort)
307-
}
308-
309-
if cfg.Git.Name != "" {
310-
fmt.Printf(" Git: %s <%s>\n", cfg.Git.Name, cfg.Git.Email)
311-
}
312-
313-
cpuStr := cfg.CPU() // e.g., "4000m" or "0"
314-
memStr := cfg.Memory() // e.g., "16Gi" or ""
315-
316-
hasCPU := cpuStr != "0"
317-
hasMem := memStr != ""
318-
319-
if hasCPU || hasMem {
320-
var parts []string
321-
if hasCPU {
322-
parts = append(parts, fmt.Sprintf("CPU=%s", cpuStr))
323-
}
324-
if hasMem {
325-
parts = append(parts, fmt.Sprintf("Memory=%s", memStr))
326-
}
327-
fmt.Printf(" Resources: %s\n", strings.Join(parts, ", "))
328-
}
329-
330-
if len(cfg.Volumes) > 0 {
331-
fmt.Printf(" Volumes: %d configured\n", len(cfg.Volumes))
332-
}
333-
334-
fmt.Printf(" Developer Config Dir: %s\n", cfg.GetDeveloperDir())
335-
}
336-
337-
// Helper function to format CPU value for display
338-
func formatCPU(cpu any) string {
339-
if cpu == nil {
340-
return "default"
341-
}
342-
switch v := cpu.(type) {
343-
case string:
344-
return v
345-
case int:
346-
return fmt.Sprintf("%d", v)
347-
case float64:
348-
return fmt.Sprintf("%.0f", v)
349-
default:
350-
return fmt.Sprintf("%v", v) // Fallback
351-
}
352-
}

0 commit comments

Comments
 (0)