-
Notifications
You must be signed in to change notification settings - Fork 6
fix(adhoc-sweep-fixes): CU-86akbhhau 14 review findings across 12 files #377
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
9e0e2a7
8b802bb
02525e0
334db0a
c20b9fd
f79bad1
2acd7a7
8592103
c7a8bd1
1cf1493
54a2298
c377c87
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 |
|---|---|---|
|
|
@@ -106,7 +106,9 @@ func runUpgradeChangeRef(cmd *cobra.Command, args []string, flags *InstallFlags, | |
| // let the wait sync them once progress stalls instead of timing out (N3). | ||
| req.SyncStragglersOnStall = true | ||
|
|
||
| pterm.Info.Printf("Upgrading OpenFrame to ref %q\n", flags.resolvedRef()) | ||
| if !flags.Silent { | ||
| pterm.Info.Printf("Upgrading OpenFrame to ref %q\n", flags.resolvedRef()) | ||
| } | ||
| if err := services.InstallChartsWithConfigContext(cmd.Context(), req); err != nil { | ||
| return sharedErrors.HandleGlobalError(err, verbose) | ||
| } | ||
|
|
@@ -135,10 +137,12 @@ func runUpgradeForceSync(cmd *cobra.Command, args []string, flags *InstallFlags, | |
| return sharedErrors.HandleGlobalError(previewOutOfSync(cmd.Context(), manager, verbose, prune), verbose) | ||
| } | ||
|
|
||
| if prune { | ||
| if prune && !flags.Silent { | ||
| pterm.Warning.Println("Refreshing and syncing with --prune: resources removed from git will be DELETED.") | ||
| } | ||
| pterm.Info.Println("Refreshing and syncing the OpenFrame platform...") | ||
| if !flags.Silent { | ||
| pterm.Info.Println("Refreshing and syncing the OpenFrame platform...") | ||
| } | ||
| if err := manager.RefreshAndSync(cmd.Context(), prune); err != nil { | ||
| return sharedErrors.HandleGlobalError(err, verbose) | ||
| } | ||
|
|
@@ -157,7 +161,9 @@ func runUpgradeForceSync(cmd *cobra.Command, args []string, flags *InstallFlags, | |
| if err := manager.WaitForApplications(cmd.Context(), waitCfg); err != nil { | ||
| return sharedErrors.HandleGlobalError(err, verbose) | ||
| } | ||
| pterm.Success.Println("OpenFrame platform re-synced.") | ||
| if !flags.Silent { | ||
| pterm.Success.Println("OpenFrame platform re-synced.") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -203,7 +209,16 @@ func resolveUpgradeTarget(cmd *cobra.Command, args []string, flags *InstallFlags | |
| if err != nil { | ||
| return nil, "", fmt.Errorf("could not use context %q: %w", contextName, err) | ||
| } | ||
| return cfg, clusterNameArg(args), nil | ||
| // A positional cluster name, if also given, is only a label for this | ||
| // --context-resolved config (it does not re-resolve the context), so | ||
| // only use it when the two cannot conflict: no --context-derived name | ||
| // of its own exists. Otherwise prefer contextName itself so the | ||
| // ClusterName reported in wait/status output matches the config that | ||
| // was actually resolved. | ||
| if name := clusterNameArg(args); name != "" { | ||
| return cfg, name, nil | ||
| } | ||
| return cfg, contextName, nil | ||
| } | ||
|
|
||
| if name := clusterNameArg(args); name != "" { | ||
|
Comment on lines
209
to
224
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. 𦩠π resolveUpgradeTarget silently discards clusterName in --context branch causing empty ClusterName in later WaitForApplications config In π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -177,11 +177,11 @@ func (c *CertificateInstaller) generateCertificates() error { | |
| if commandExists("apt-get") { | ||
| updateCmd := exec.Command("sudo", "apt-get", "update", "-y") | ||
| if err := updateCmd.Run(); err != nil { | ||
| pterm.Debug.Printf("apt-get update failed (certutil install is optional): %v\n", err) | ||
| pterm.Warning.Printf("apt-get update failed (certutil install is optional, NSS trust for Firefox/Chrome will not be configured): %v\n", err) | ||
| } | ||
| installCmd := exec.Command("sudo", "apt-get", "install", "-y", "libnss3-tools", "ca-certificates") | ||
| if err := installCmd.Run(); err != nil { | ||
| pterm.Debug.Printf("apt-get install of certutil/ca-certificates failed (optional): %v\n", err) | ||
| pterm.Warning.Printf("apt-get install of certutil/ca-certificates failed (optional, NSS trust for Firefox/Chrome will not be configured): %v\n", err) | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
177
to
187
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. 𦩠π apt-get/certutil setup failures are swallowed with only Debug-level logging, masking real installer misconfiguration In π€ Prompt for AI agentsfix confidence: π‘ 85 medium β react π/π to teach the reviewer |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,5 +101,6 @@ | |
| fmt.Fprintf(&b, " - %s is on %q, not %q\n", x.App, x.Got, x.Want) | ||
| } | ||
| b.WriteString("Use a branch whose chart reads repository.branch, or pin these applications' targetRevision by hand.") | ||
| return fmt.Errorf("%s", b.String()) | ||
| return selfDiagnosedError{msg: b.String()} | ||
|
Check failure on line 104 in internal/chart/providers/argocd/refassert.go
|
||
| } | ||
|
|
||
|
Comment on lines
101
to
+106
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. 𦩠π refMismatchError does not use selfDiagnosedError despite embedding per-app diagnostic text Changed π€ Prompt for AI agentsfix confidence: π‘ 80 medium β react π/π to teach the reviewer |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -246,6 +246,8 @@ type syncGroup struct { | |
| // (legacy manifests β caller keeps the ungated single-pass behaviour). A child | ||
| // missing the label β or carrying a non-numeric value β on an otherwise | ||
| // labeled install falls into defaultSyncGroup, mirroring the template default. | ||
| // A non-numeric value (e.g. a typo like "1a") is surfaced with a warning | ||
| // rather than silently absorbed, since it is likely a misconfiguration. | ||
| func groupChildren(children []unstructured.Unstructured) (groups []syncGroup, labeled bool) { | ||
|
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. 𦩠π groupChildren silently misclassifies non-numeric sync-group labels into the default group without warning In π€ Prompt for AI agentsfix confidence: π‘ 85 medium β react π/π to teach the reviewer |
||
| byNumber := map[int][]string{} | ||
| var all []string | ||
|
|
@@ -257,6 +259,9 @@ func groupChildren(children []unstructured.Unstructured) (groups []syncGroup, la | |
| labeled = true | ||
| if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil { | ||
| group = n | ||
| } else { | ||
| pterm.Warning.Printf("Application %q has a non-numeric %s label (%q); defaulting to sync group %d\n", | ||
| name, SyncGroupLabel, v, defaultSyncGroup) | ||
| } | ||
| } | ||
| byNumber[group] = append(byNumber[group], name) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,7 +131,9 @@ func (m *Manager) WaitForApplications(ctx context.Context, config config.ChartIn | |
|
|
||
| // Function to stop spinner safely | ||
| stopSpinner := func() { | ||
|
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. 𦩠π΄ stopSpinner unconditionally calls dash.Stop() even when dash is nil In π€ Prompt for AI agentsfix confidence: π’ 95 high β react π/π to teach the reviewer |
||
| dash.Stop() | ||
| if dash != nil { | ||
| dash.Stop() | ||
| } | ||
| spinnerMutex.Lock() | ||
| defer spinnerMutex.Unlock() | ||
| if !spinnerStopped && spinner != nil { | ||
|
|
@@ -290,7 +292,9 @@ func (m *Manager) WaitForApplications(ctx context.Context, config config.ChartIn | |
| case <-ticker.C: | ||
| // Check timeout | ||
| if time.Since(startTime) > timeout { | ||
|
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. 𦩠π΄ dash.Fail() called unconditionally in the timeout branch even when dash may be nil In the ticker-driven timeout branch of π€ Prompt for AI agentsfix confidence: π’ 90 high β react π/π to teach the reviewer |
||
| dash.Fail(fmt.Sprintf("Timeout after %v", timeout)) | ||
| if dash != nil { | ||
| dash.Fail(fmt.Sprintf("Timeout after %v", timeout)) | ||
| } | ||
| spinnerMutex.Lock() | ||
| if !spinnerStopped && spinner != nil { | ||
| spinner.Fail(fmt.Sprintf("Timeout after %v", timeout)) | ||
|
|
@@ -467,7 +471,9 @@ func (m *Manager) WaitForApplications(ctx context.Context, config config.ChartIn | |
| // staleness checks use the same tick. | ||
| now := time.Now() | ||
| if fatal := fatalManifest.observe(apps, now); len(fatal) > 0 { | ||
| dash.Fail("Applications cannot render manifests from the deployed revision") | ||
| if dash != nil { | ||
| dash.Fail("Applications cannot render manifests from the deployed revision") | ||
| } | ||
| spinnerMutex.Lock() | ||
| if !spinnerStopped && spinner != nil { | ||
| spinner.Fail("Applications cannot render manifests from the deployed revision") | ||
|
|
@@ -489,7 +495,9 @@ func (m *Manager) WaitForApplications(ctx context.Context, config config.ChartIn | |
| // events, so it says WHY, not just that it hung. | ||
| if cand := degraded.observe(apps, now); len(cand) > 0 { | ||
| if diag, stuck := m.diagnoseFailingApps(localCtx, cand); len(stuck) > 0 { | ||
| dash.Fail("An application is Degraded with a workload that will not recover") | ||
| if dash != nil { | ||
| dash.Fail("An application is Degraded with a workload that will not recover") | ||
| } | ||
| spinnerMutex.Lock() | ||
| if !spinnerStopped && spinner != nil { | ||
| spinner.Fail("An application is Degraded with a workload that will not recover") | ||
|
|
@@ -541,7 +549,9 @@ func (m *Manager) WaitForApplications(ctx context.Context, config config.ChartIn | |
| // this the default experience was a static "Installing ArgoCD | ||
| // applications..." for up to the full 60m timeout, with no way to tell | ||
| // a working install from a wedged one. | ||
| dash.Update(currentlyReady, totalApps, apps) | ||
| if dash != nil { | ||
| dash.Update(currentlyReady, totalApps, apps) | ||
| } | ||
| if totalApps > 0 { | ||
| spinnerMutex.Lock() | ||
| if !spinnerStopped && spinner != nil { | ||
|
|
@@ -724,7 +734,9 @@ func (m *Manager) WaitForApplications(ctx context.Context, config config.ChartIn | |
| spinnerMutex.Unlock() | ||
|
|
||
| if len(mm) > 0 { | ||
| dash.Fail("Deployed ref does not match the requested ref") | ||
| if dash != nil { | ||
| dash.Fail("Deployed ref does not match the requested ref") | ||
| } | ||
| return refMismatchError(config.AppOfApps.GitHubBranch, mm) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,6 +200,9 @@ func (d *EKSDiscoverer) describe(ctx context.Context, profile, region, name stri | |
| if err != nil { | ||
| return models.ClusterInfo{}, "", fmt.Errorf("describing cluster %s: %w", name, err) | ||
| } | ||
| if result == nil { | ||
| return models.ClusterInfo{}, "", fmt.Errorf("unparseable describe-cluster for %s", name) | ||
| } | ||
| var c eksCluster | ||
| if err := json.Unmarshal([]byte(result.Stdout), &c); err != nil { | ||
| return models.ClusterInfo{}, "", fmt.Errorf("unparseable describe-cluster for %s", name) | ||
|
Comment on lines
200
to
208
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. 𦩠π EKS describe() does not check for a nil result before Unmarshal In EKSDiscoverer.describe (internal/cluster/discovery/eks.go), added a π€ Prompt for AI agentsfix confidence: π’ 90 high β react π/π to teach the reviewer |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,6 +74,9 @@ func (d *GKEDiscoverer) configurations(ctx context.Context) ([]gcloudConfigurati | |
| if err != nil { | ||
| return nil, fmt.Errorf("listing gcloud configurations: %w", err) | ||
| } | ||
| if result == nil { | ||
| return nil, nil | ||
| } | ||
| var configs []gcloudConfiguration | ||
| if err := json.Unmarshal([]byte(result.Stdout), &configs); err != nil { | ||
| return nil, fmt.Errorf("parsing gcloud configurations: %w", err) | ||
|
Comment on lines
74
to
82
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. 𦩠π configurations() does not check for a nil result before Unmarshal Added π€ Prompt for AI agentsfix confidence: π’ 97 high β react π/π to teach the reviewer |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,13 @@ import ( | |
| // out of a terraform 409 error so an orphan can be named concretely. | ||
| var gcpResourcePathRE = regexp.MustCompile(`projects/[^'"\s]+`) | ||
|
|
||
| // safeClusterNameRE matches GKE's own cluster-name constraints (lowercase | ||
| // alphanumeric and hyphens, starting with a letter). Any name that fails this | ||
| // check cannot be a valid GKE cluster name anyway, so rejecting it here also | ||
| // guarantees it can never be used to inject syntax into a gcloud --filter | ||
| // expression (spaces, "OR", parentheses, quotes, etc.). | ||
| var safeClusterNameRE = regexp.MustCompile(`^[a-z]([-a-z0-9]*[a-z0-9])?$`) | ||
|
|
||
| // orphanFromInterruptedCreate detects the specific failure where terraform | ||
| // tries to create a resource that already exists in GCP (HTTP 409 / | ||
| // alreadyExists). This is the signature of a create interrupted (SIGINT) after | ||
|
|
@@ -153,7 +160,17 @@ func (p *Provider) ensureProjectServices(ctx context.Context, project string) er | |
| // line equal to the cluster name. Anything else β non-zero exit, empty or | ||
| // unrelated output β is treated as "does not exist"; a genuinely broken API | ||
| // call fails later with a clearer terraform error anyway. | ||
| // | ||
| // config.Name is rejected up front unless it matches GKE's own cluster-name | ||
| // character set (lowercase alphanumeric and hyphens). This is not just | ||
| // defense in depth: it also guarantees the name cannot contain characters | ||
| // meaningful to gcloud's --filter expression syntax (spaces, "OR", | ||
| // parentheses, quotes), so it cannot alter the filter's semantics and bypass | ||
| // this collision guard. | ||
| func (p *Provider) preflightNameCollision(ctx context.Context, config models.ClusterConfig) error { | ||
|
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. 𦩠π΄ GKE preflightNameCollision filter uses unescaped user-controlled cluster name in gcloud --filter, risking filter-expression injection Added a π€ Prompt for AI agentsfix confidence: π‘ 85 medium β react π/π to teach the reviewer |
||
| if !safeClusterNameRE.MatchString(config.Name) { | ||
| return fmt.Errorf("cluster name %q is not a valid GKE cluster name (must match %s)", config.Name, safeClusterNameRE.String()) | ||
| } | ||
| result, err := p.executor.Execute(ctx, "gcloud", "container", "clusters", "list", | ||
| "--project", config.Cloud.Project, "--filter=name="+config.Name, "--format=value(name)") | ||
| if err != nil || result == nil { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import ( | |
| "github.com/flamingo-stack/openframe-cli/internal/shared/download" | ||
| "github.com/hashicorp/terraform-exec/tfexec" | ||
| tfjson "github.com/hashicorp/terraform-json" | ||
| "github.com/pterm/pterm" | ||
| ) | ||
|
|
||
| // Runner is the subset of *tfexec.Terraform the engine uses; an interface so | ||
|
|
@@ -145,16 +146,25 @@ const OpLogName = "terraform.log" | |
| // propagates a sink error: exec.Cmd returns a stdout-writer error from Wait, | ||
| // so a disk filling up mid-apply would report the terraform run as FAILED | ||
| // while terraform actually completed and changed resources. The log is a | ||
| // record of the operation β it must never decide its outcome. | ||
| // record of the operation β it must never decide its outcome. The first sink | ||
| // write failure is still surfaced once, at WARN level, so a user relying on | ||
| // OpLogName for post-mortem debugging isn't silently left with a truncated | ||
| // log and no indication it happened. | ||
| type bestEffortTee struct { | ||
| progress io.Writer | ||
| sink io.Writer // nil once a write failed; never re-enabled | ||
| logPath string // used only for the one-time warning below | ||
| warned bool | ||
| } | ||
|
|
||
| func (t *bestEffortTee) Write(p []byte) (int, error) { | ||
|
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. 𦩠π opSinks opens the terraform.log file with O_APPEND but never closes it if opFailure/callers panic before defer runs β minor, but more importantly bestEffortTee silently drops disk-full errors without ever surfacing them to the user, even in verbose mode In π€ Prompt for AI agentsfix confidence: π‘ 70 medium β react π/π to teach the reviewer |
||
| if t.sink != nil { | ||
| if _, err := t.sink.Write(p); err != nil { | ||
| t.sink = nil | ||
| if !t.warned { | ||
| t.warned = true | ||
| pterm.Warning.Printfln("operation log %s stopped recording (write error: %v); the on-disk log will be incomplete", t.logPath, err) | ||
| } | ||
| } | ||
| } | ||
| return t.progress.Write(p) | ||
|
|
@@ -164,7 +174,8 @@ func (t *bestEffortTee) Write(p []byte) (int, error) { | |
| // writer, teed into dir's terraform.log when it can be opened. Logging is | ||
| // best-effort end to end β a directory that cannot take the log (read-only, | ||
| // gone) skips it, and a write failure after opening only stops the mirroring | ||
| // (bestEffortTee above); neither may ever fail the operation itself. | ||
| // (bestEffortTee above, which also warns once); neither may ever fail the | ||
| // operation itself. | ||
| // The returned close is always safe to call; logPath is empty when no log is | ||
| // being written. | ||
| func (e *Engine) opSinks(dir, op string) (w io.Writer, close func(), logPath string) { | ||
|
|
@@ -173,8 +184,12 @@ func (e *Engine) opSinks(dir, op string) (w io.Writer, close func(), logPath str | |
| if err != nil { | ||
| return progress, func() {}, "" | ||
| } | ||
| fmt.Fprintf(f, "=== terraform %s β %s ===\n", op, time.Now().UTC().Format(time.RFC3339)) | ||
| return &bestEffortTee{progress: progress, sink: f}, func() { _ = f.Close() }, f.Name() | ||
| if _, err := fmt.Fprintf(f, "=== terraform %s β %s ===\n", op, time.Now().UTC().Format(time.RFC3339)); err != nil { | ||
| pterm.Warning.Printfln("operation log %s could not be written to (%v); the on-disk log will be incomplete", f.Name(), err) | ||
| _ = f.Close() | ||
| return progress, func() {}, "" | ||
| } | ||
| return &bestEffortTee{progress: progress, sink: f, logPath: f.Name()}, func() { _ = f.Close() }, f.Name() | ||
| } | ||
|
|
||
| // opFailure wraps a failed apply/destroy, pointing at the full log when one | ||
|
|
||
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.
𦩠π upgrade.go force-sync path prints pterm.Info without checking suppressUI equivalent (--silent) unlike cluster service
Added
flags.Silentchecks around all directpterm.Info/pterm.Success/pterm.Warningcalls inrunUpgradeChangeRefandrunUpgradeForceSync(cmd/app/upgrade.go), matching the suppress-UI pattern described in the finding. This assumesInstallFlagsalready has aSilent boolfield populated from a--silentflag (used elsewhere in the codebase, e.g.extractInstallFlags);previewOutOfSyncwas left unchanged since it only runs under--dry-runand does not receiveflags, so its output was not gated β a complete fix would need to threadflags.Silent(or the silent value) intopreviewOutOfSyncas well.π€ Prompt for AI agents
fix confidence: π‘ 70 medium β react π/π to teach the reviewer