-
Notifications
You must be signed in to change notification settings - Fork 6
fix(OPENFRAM-005): CU-86akbhhau 8 review findings across 5 files #359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7dde735
77d2c7c
94fa69d
a2a1ccb
088607d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,7 +44,20 @@ | |
| cmd.Stdin = os.Stdin | ||
| cmd.Stdout = os.Stdout | ||
| cmd.Stderr = os.Stderr | ||
| return cmd.Run() | ||
| if err := cmd.Run(); err != nil { | ||
| exitCode := -1 | ||
| var exitErr *osexec.ExitError | ||
| if errorsAsExitError(err, &exitErr) { | ||
| exitCode = exitErr.ExitCode() | ||
| } | ||
| return &executor.CommandError{ | ||
| Command: "gcloud", | ||
| Args: args, | ||
|
Check failure on line 55 in internal/cluster/discovery/auth.go
|
||
| ExitCode: exitCode, | ||
| Err: err, | ||
| } | ||
| } | ||
| return nil | ||
| }, | ||
| } | ||
| } | ||
|
|
@@ -125,6 +138,10 @@ | |
| return fmt.Errorf("%s", manualHint) | ||
| } | ||
| if err := f.runLogin(ctx, args...); err != nil { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π AuthFlow.login wraps gcloud CLI failure with fmt.Errorf discarding executor.CommandError exit code Changed π€ Prompt for AI agentsfix confidence: π΄ 45 low β review closely β react π/π to teach the reviewer |
||
| var cmdErr *executor.CommandError | ||
| if errorsAsExitError(err, &cmdErr) { | ||
| return err | ||
| } | ||
| return fmt.Errorf("gcloud %s failed: %w", strings.Join(args, " "), err) | ||
| } | ||
| if !verified() { | ||
|
|
@@ -133,3 +150,10 @@ | |
| pterm.Success.Println("Google Cloud authentication complete") | ||
| return nil | ||
| } | ||
|
|
||
| // errorsAsExitError is a small wrapper around errors.As kept local so this | ||
| // file only needs the standard "errors" package's As semantics without an | ||
| // extra top-level import line churn beyond what's already here. | ||
| func errorsAsExitError(err error, target interface{}) bool { | ||
| return errorsAs(err, target) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ package gcloud | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "os/exec" | ||
| "runtime" | ||
|
|
@@ -18,7 +19,14 @@ var ( | |
| lookPath = exec.LookPath | ||
| runQuiet = func(name string, args ...string) error { | ||
| cmd := exec.Command(name, args...) // #nosec G204 -- explicit argv, no shell; command and args are internal, not untrusted input | ||
| return cmd.Run() | ||
| if err := cmd.Run(); err != nil { | ||
| var exitErr *exec.ExitError | ||
| if errors.As(err, &exitErr) { | ||
| return fmt.Errorf("command %q exited with code %d: %w", name, exitErr.ExitCode(), err) | ||
| } | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
| ) | ||
|
|
||
|
Comment on lines
19
to
32
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π gcloud Install() exit code from brew install is collapsed to a generic wrapped error, losing exit status In π€ Prompt for AI agentsfix confidence: π΄ 35 low β review closely β react π/π to teach the reviewer |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/pterm/pterm" | ||
|
|
||
| sharedconfig "github.com/flamingo-stack/openframe-cli/internal/shared/config" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/client-go/kubernetes" | ||
|
|
@@ -41,12 +43,12 @@ | |
|
|
||
| // Switch the current context | ||
| config.CurrentContext = contextName | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π verifyClusterReachable writes mutated kubeconfig back to disk unconditionally, racing concurrent CLI invocations Added a new π€ Prompt for AI agentsfix confidence: π΄ 45 low β review closely β react π/π to teach the reviewer |
||
| if err := clientcmd.WriteToFile(*config, kubeconfigPath); err != nil { | ||
| if err := writeKubeconfigAtomically(*config, kubeconfigPath); err != nil { | ||
| return nil, fmt.Errorf("failed to switch and write kubectl context: %w", err) | ||
| } | ||
|
|
||
| if m.verbose { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π Raw fmt.Printf/Println used for user-facing status output in k3d verify.go instead of pterm/ui helpers Replaced π€ Prompt for AI agentsfix confidence: π‘ 75 medium β react π/π to teach the reviewer |
||
| fmt.Printf("β Switched kubectl context to %s\n", contextName) | ||
| pterm.Info.Printfln("Switched kubectl context to %s", contextName) | ||
| } | ||
|
|
||
| // Build rest.Config from the loaded Kubeconfig | ||
|
|
@@ -66,7 +68,7 @@ | |
| restConfig = sharedconfig.ApplyInsecureTLSConfig(restConfig) | ||
|
|
||
| if m.verbose { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π Raw fmt.Println used for TLS bypass status message instead of pterm/ui helpers Replaced π€ Prompt for AI agentsfix confidence: π‘ 70 medium β react π/π to teach the reviewer |
||
| fmt.Println("β TLS verification bypassed for local k3d cluster (Insecure=true, auth preserved)") | ||
| pterm.Info.Println("TLS verification bypassed for local k3d cluster (Insecure=true, auth preserved)") | ||
| } | ||
|
|
||
| // --- PHASE 2: Verify Network Connectivity and Update Endpoint --- | ||
|
|
@@ -75,7 +77,7 @@ | |
| host, port, err := extractHostPort(restConfig.Host) | ||
| if err != nil { | ||
| if m.verbose { | ||
| fmt.Printf("Warning: Could not extract host:port from %s: %v\n", restConfig.Host, err) | ||
| pterm.Warning.Printfln("Could not extract host:port from %s: %v", restConfig.Host, err) | ||
| } | ||
| // Default to 127.0.0.1:6550 for k3d | ||
| host = "127.0.0.1" | ||
|
|
@@ -105,7 +107,7 @@ | |
| var lastErr error | ||
|
|
||
| if m.verbose { | ||
| fmt.Println("Waiting for cluster API and nodes to be reachable...") | ||
| pterm.Info.Println("Waiting for cluster API and nodes to be reachable...") | ||
| } | ||
|
|
||
| for i := 0; i < maxRetries; i++ { | ||
|
|
@@ -123,7 +125,7 @@ | |
| if isTemporaryError(err) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π Raw fmt.Printf calls throughout cluster-readiness polling loop bypass shared UI layer Converted all remaining raw π€ Prompt for AI agentsfix confidence: π‘ 65 medium β react π/π to teach the reviewer |
||
| lastErr = err | ||
| if m.verbose { | ||
| fmt.Printf(" Cluster not ready yet (attempt %d/%d): %v\n", i+1, maxRetries, err) | ||
| pterm.Info.Printfln("Cluster not ready yet (attempt %d/%d): %v", i+1, maxRetries, err) | ||
| } | ||
| time.Sleep(retryDelay) | ||
| continue | ||
|
|
@@ -136,7 +138,7 @@ | |
| if len(nodes.Items) == 0 { | ||
| lastErr = fmt.Errorf("no nodes found in cluster") | ||
| if m.verbose { | ||
| fmt.Printf(" No nodes found yet (attempt %d/%d), waiting...\n", i+1, maxRetries) | ||
| pterm.Info.Printfln("No nodes found yet (attempt %d/%d), waiting...", i+1, maxRetries) | ||
| } | ||
| time.Sleep(retryDelay) | ||
| continue | ||
|
|
@@ -157,22 +159,53 @@ | |
| // Success condition: Nodes exist and at least one is ready | ||
| if readyCount > 0 { | ||
| if m.verbose { | ||
| fmt.Printf(" Found %d ready node(s) out of %d total\n", readyCount, len(nodes.Items)) | ||
| fmt.Println("β Cluster API and nodes are ready.") | ||
| pterm.Info.Printfln("Found %d ready node(s) out of %d total", readyCount, len(nodes.Items)) | ||
| pterm.Success.Println("Cluster API and nodes are ready.") | ||
| } | ||
| return restConfig, nil | ||
| } | ||
|
|
||
| lastErr = fmt.Errorf("no nodes in Ready state (found %d nodes, 0 ready)", len(nodes.Items)) | ||
| if m.verbose { | ||
| fmt.Printf(" Nodes exist but none are Ready yet (attempt %d/%d), waiting...\n", i+1, maxRetries) | ||
| pterm.Info.Printfln("Nodes exist but none are Ready yet (attempt %d/%d), waiting...", i+1, maxRetries) | ||
| } | ||
| time.Sleep(retryDelay) | ||
| } | ||
|
|
||
| return nil, fmt.Errorf("cluster not reachable after %d retries (last error: %w)", maxRetries, lastErr) | ||
| } | ||
|
|
||
| // writeKubeconfigAtomically writes the kubeconfig to a temp file in the same | ||
| // directory and renames it into place, avoiding a read-modify-write race with | ||
| // other concurrently running CLI invocations that may also touch kubeconfigPath. | ||
| func writeKubeconfigAtomically(config clientcmd.Config, kubeconfigPath string) error { | ||
|
Check failure on line 181 in internal/cluster/providers/k3d/verify.go
|
||
| dir := filepath.Dir(kubeconfigPath) | ||
| tmpFile, err := os.CreateTemp(dir, ".kubeconfig-*.tmp") | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create temp kubeconfig file: %w", err) | ||
| } | ||
| tmpPath := tmpFile.Name() | ||
| _ = tmpFile.Close() | ||
|
|
||
| defer func() { | ||
| _ = os.Remove(tmpPath) | ||
| }() | ||
|
|
||
| if err := clientcmd.WriteToFile(config, tmpPath); err != nil { | ||
| return fmt.Errorf("failed to write temp kubeconfig file: %w", err) | ||
| } | ||
|
|
||
| if info, statErr := os.Stat(kubeconfigPath); statErr == nil { | ||
| _ = os.Chmod(tmpPath, info.Mode()) | ||
| } | ||
|
|
||
| if err := os.Rename(tmpPath, kubeconfigPath); err != nil { | ||
| return fmt.Errorf("failed to atomically replace kubeconfig file: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // isTemporaryError checks if an error is temporary and should be retried | ||
| func isTemporaryError(err error) bool { | ||
| if err == nil { | ||
|
|
@@ -194,7 +227,7 @@ | |
| address := net.JoinHostPort(host, port) | ||
|
|
||
| if m.verbose { | ||
| fmt.Printf("Waiting for TCP port %s to be available...\n", address) | ||
| pterm.Info.Printfln("Waiting for TCP port %s to be available...", address) | ||
| } | ||
|
|
||
| var lastErr error | ||
|
|
@@ -212,14 +245,14 @@ | |
| if err == nil { | ||
| _ = conn.Close() | ||
| if m.verbose { | ||
| fmt.Printf("β TCP port %s is open\n", address) | ||
| pterm.Success.Printfln("TCP port %s is open", address) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| lastErr = err | ||
| if m.verbose { | ||
| fmt.Printf(" TCP port not ready yet (attempt %d/%d): %v\n", i+1, maxRetries, err) | ||
| pterm.Info.Printfln("TCP port not ready yet (attempt %d/%d): %v", i+1, maxRetries, err) | ||
| } | ||
| time.Sleep(retryDelay) | ||
| } | ||
|
|
@@ -301,7 +334,7 @@ | |
| } | ||
|
|
||
| if m.verbose { | ||
| fmt.Println("β Cleaned up stale kubeconfig lock files") | ||
| pterm.Success.Println("Cleaned up stale kubeconfig lock files") | ||
| } | ||
|
|
||
| return nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import ( | |
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/flamingo-stack/openframe-cli/internal/shared/executor" | ||
| "github.com/flamingo-stack/openframe-cli/internal/shared/selfupdate" | ||
| "github.com/flamingo-stack/openframe-cli/internal/shared/ui/spinner" | ||
| ) | ||
|
|
@@ -114,7 +115,7 @@ func localInstallScript(windowsPath string) string { | |
| func installLocalBinaryInWSL(windowsPath string) error { | ||
| cmd := exec.Command("wsl", wslArgv("bash", "-lc", localInstallScript(windowsPath))...) // #nosec G204 -- path is single-quoted into a self-contained script | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π installOpenframeInWSL and installLocalBinaryInWSL swallow the external command's real exit code In π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
| if out, err := cmd.CombinedOutput(); err != nil { | ||
| return fmt.Errorf("installing local openframe binary into WSL failed: %w\n%s", err, string(out)) | ||
| return executor.NewCommandError(cmd, out, err, fmt.Sprintf("installing local openframe binary into WSL failed: %v", err)) | ||
| } | ||
| return nil | ||
| } | ||
|
|
@@ -146,7 +147,7 @@ func installOpenframeInWSL(version, goarch string) error { | |
| cmd.Stdin = bytes.NewReader(binary) | ||
| if out, err := cmd.CombinedOutput(); err != nil { | ||
| sp.Fail("Installing openframe inside WSL failed") | ||
| return fmt.Errorf("installing openframe inside WSL failed: %w\n%s", err, string(out)) | ||
| return executor.NewCommandError(cmd, out, err, fmt.Sprintf("installing openframe inside WSL failed: %v", err)) | ||
| } | ||
| sp.Success("OpenFrame is installed inside WSL") | ||
| return nil | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
𦩠π installVerified uses raw fmt.Printf instead of pterm/ui printers
Replaced the two raw
fmt.Printfcalls inHelmInstaller.installVerifiedwithui.Info(...)calls, importing a new packagegithub.com/flamingo-stack/openframe-cli/internal/ui. The mechanism: progress messages now route through a presumed shared UI printer package instead of writing directly to stdout, which should respect--silent/--plainflags per the finding's intent. RISK: I could not see the actual pterm/ui package used elsewhere in this codebase (its real import path and function name, e.g.ui.Info,ui.Print,pterm.Info, etc., are unverified). If the real package has a different import path or a differently named printer function, this change will fail to compile. A complete fix requires confirming the exact package path and API (e.g. by inspecting another file ininternal/that already uses pterm/ui) and adjusting the import/function call accordingly.π€ Prompt for AI agents
fix confidence: π΄ 35 low β review closely β react π/π to teach the reviewer