Skip to content

WIP: feat(cli): OpenFrame cluster commands to apply public EKS/GKE TF modules (K3d alternative) - #223

Draft
oleg-flamingocx wants to merge 39 commits into
mainfrom
develop
Draft

WIP: feat(cli): OpenFrame cluster commands to apply public EKS/GKE TF modules (K3d alternative)#223
oleg-flamingocx wants to merge 39 commits into
mainfrom
develop

Conversation

@oleg-flamingocx

@oleg-flamingocx oleg-flamingocx commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added managed cloud cluster workflows for GKE, including create (with Terraform preview), list/status, context switching (cluster use), and delete.
    • Added cluster list --all to discover external GKE clusters.
    • Added optional monthly cost estimates for cloud dry-runs.
  • Bug Fixes
    • Cloud deletion now requires typed-name confirmation (or --force) and is safer in non-interactive mode.
    • Improved handling for missing auth/credentials, unknown clusters, and interrupted provisioning.
  • Documentation
    • Added cloud cluster getting-started and updated command examples for cloud vs local usage.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dab83eb1-9c65-46e7-9935-f9d8af1445b4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds managed EKS and GKE cluster support, including Terraform provisioning, cloud authentication, registry-backed workspaces, provider routing, discovery, kubeconfig switching, cloud safeguards, build-output standardization, CI coverage, and documentation.

Changes

Managed cloud cluster support

Layer / File(s) Summary
Cloud contracts and prerequisites
internal/cluster/models/*, internal/cluster/prerequisites/*, internal/shared/download/*
Cloud configuration, validation, provider-specific prerequisites, pinned Terraform/Infracost tools, and archive installation support are added.
Terraform engine and workspace foundation
internal/cluster/providers/terraform/*
Terraform lifecycle execution, plan summaries, cost estimation, backend parsing, progress output, and persistent workspaces are implemented.
EKS and GKE provider backends
internal/cluster/providers/eks/*, internal/cluster/providers/gke/*, internal/cluster/provider/*
EKS and GKE providers provision through Terraform, persist lifecycle state, generate exec-auth kubeconfigs, and expose cluster operations.
Backend-aware cluster service
internal/cluster/service.go, internal/cluster/service_test.go
Cluster lifecycle, listing, status, detection, REST configuration, cleanup, and resume behavior are routed by cluster type.
Cloud CLI workflows and UI
cmd/cluster/*, internal/cluster/discovery/*, internal/cluster/ui/*, internal/k8s/*
Cloud dry-runs, authentication, external discovery, cluster use, typed deletion confirmation, wizard inputs, and cloud-aware display output are added.
Validation, CI, builds, and documentation
.github/workflows/*, Makefile, docs/*, README.md, go.mod
Build artifacts move to build/, Terraform validation and cloud command guards are wired into CI, and cloud workflows are documented.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: ivan-flamingo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding CLI cluster commands for EKS/GKE cloud modules as a k3d alternative.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@oleg-flamingocx oleg-flamingocx changed the title feat(cli): OpenFrame cluster commands to apply public EKS/GKE TF modules (K3d alternative) WIP: feat(cli): OpenFrame cluster commands to apply public EKS/GKE TF modules (K3d alternative) Jul 15, 2026
@oleg-flamingocx
oleg-flamingocx marked this pull request as draft July 15, 2026 10:57
…visible cluster (#239)

- resolves local k3d (k3d-<name>/exact context), openframe-managed cloud
      (context = cluster name), and external GKE clusters found via discovery
- external clusters without a kubeconfig entry get credentials fetched via
      `gcloud container clusters get-credentials` (private-cluster hint on
      failure); for GKE the gcloud configuration matching the cluster's project
      is activated so gcloud and kubectl line up
- k8s.SwitchContext flips only current-context — never rewrites contexts,
      clusters, or users; the cluster itself is never touched
    - no-arg form offers interactive selection; logged-out gcloud yields an
      actionable auth hint; the prerequisite gate is skipped (no tools needed)
    - discovery.AuthFlow: check auth state → confirm → run `gcloud auth login`
      attached to the terminal → re-verify; `create --type gke` additionally
      ensures Application Default Credentials (terraform's google provider uses
      ADC, not CLI credentials)
    - integrated into create (after the tool gate and in the dry-run preview),
      list --all (offers login instead of a hint), and use (before discovery)
    - non-interactive sessions never prompt (CI rule) and get the exact command
      to run; declined or failed logins surface actionable errors
    - all branches covered hermetically via interactive/confirm/runLogin seams
- new getting-started/gke-workflow.md: zero-to-cluster in order — optional
      dry-run plan, create (wizard/flags) with the automatic tools→login→
      provision chain spelled out, verify, app install, day-2 (list --all,
      use incl. external clusters), resume-after-failure, remote state, delete,
      troubleshooting table for the new error messages
- docs index distinguishes the reference (cloud-clusters.md) from the
      walkthrough; cloud-clusters.md notes the EKS coming-soon gate
…y guards (#243)

Audit findings on idempotency/safety, all fixed:
- resume was unreachable through the CLI: any registry record (even
  status=failed) short-circuited into the "already exists" reuse path and
  errored on GetRestConfig. Non-Ready cloud records now fall through to the
  provider, whose terraform apply resumes from state (shouldResumeCloudCreate)
- the reuse box printed a hardcoded green "Running" and a k3d-<name> network
  line for every cluster type; it now shows the real status and k3d rows only
  for k3d
- the GKE name-collision preflight was region-scoped and missed zonal
  clusters; it now uses a project-wide clusters list --filter
- delete removed kubeconfig entries by name alone; it now only removes them
  when the context still points at the record's endpoint (mirror of the
  create-side no-clobber guard)
The generic deletion confirmation refuses before the cloud-specific
typed-confirm gate is reached in non-interactive sessions, so the step
asserted the wrong message. Assert the actual guarantee instead: non-zero
exit, an actionable refusal (either gate's message), and the workspace
surviving.
The counts alone ("12 to add") don't tell the user WHAT would be created.
PlanSummary now carries per-resource changes in terraform's diff notation
(+ / ~ / - / -/+ with the resource address, in plan order) and the cloud
dry-run prints them above the summary line
The apply stream dropped planned_change events, so a real create printed
only the plan's change_summary count while dry-run listed the resources.
The filter now forwards planned_change ("...: Plan to create") lines,
making plan detail visible in both flows.
…ere (#247)

    - dry-run preview: when infracost is installed and working, print the
      monthly estimate computed from the plan JSON (PlanSummary now carries
      terraform's machine-readable plan); any infracost failure falls back
      silently — figures are never invented
    - without infracost: an abstract cost warning plus the provider's pricing
      page and a tip to install infracost
    - CostHint and the docs drop the stale $73/$33 hardcoded figures
make build/build-all now write to build/ (already gitignored); the CI
OF_BIN path, the WSL staging step, and the dev docs follow.
…iew (#248)

    - pinned infracost v0.10.45 (all supported platforms, SHA256 from the
      release's per-asset checksums) installed into ~/.openframe/bin; the
      archive's irregular member layout is handled by a dedicated installer
    - interactive dry-run without infracost now offers the install; the user's
      only manual step is a one-time free 'infracost auth login' (surfaced when
      an estimate fails without a key)
    - non-interactive sessions never prompt or download; hermetic seam-based
      tests cover offer/accept/decline/failure paths
…249)

   A failed estimate (typically the missing free API key) now offers to run
    'infracost auth login' right there, attached to the terminal, and retries
    the estimate once after a successful login — no separate console needed.
    Non-interactive sessions never prompt; hermetic seam-based tests cover the
    declined, accepted-and-retried, and CI paths.
…create (#250)

    - create now plans, shows every resource with the summary, and asks for
      approval before applying — and applies the SAVED plan, so what the user
      approved is exactly what runs (PlanForApply/ApplyPlan in the engine)
    - declining a brand-new create removes the fresh workspace (nothing was
      applied); declining a resume keeps the workspace and its state
    - non-interactive sessions auto-approve, preserving scripted/CI behavior;
      a no-change plan asks nothing
    - docs: create flow updated (plan-confirm step, private nodes + NAT)
…create (#251)

    - create now plans, shows every resource with the summary, and asks for
      approval before applying — and applies the SAVED plan, so what the user
      approved is exactly what runs (PlanForApply/ApplyPlan in the engine)
    - declining a brand-new create removes the fresh workspace (nothing was
      applied); declining a resume keeps the workspace and its state
    - non-interactive sessions auto-approve, preserving scripted/CI behavior;
      a no-change plan asks nothing
    - docs: create flow updated (plan-confirm step, private nodes + NAT

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/cluster/ui/wizard_steps.go (1)

44-75: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align the advertised cloud support with the executable wizard flow.

The wizard prevents interactive EKS creation, while cluster help advertises EKS and root help says all cloud providers are only planned. This leaves users unable to rely on the documented capability.

  • internal/cluster/ui/wizard_steps.go#L44-L75: return models.ClusterTypeEKS when selected if EKS is part of this PR; otherwise remove it from supported-flow messaging.
  • cmd/cluster/cluster.go#L30-L30: describe both runnable cloud providers accurately; do not claim EKS until the wizard can create it.
  • cmd/root.go#L67-L67: mention the available GKE flow rather than stating cloud providers are merely planned.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/ui/wizard_steps.go` around lines 44 - 75, Align
supported-cloud messaging across PromptClusterType, cmd/cluster/cluster.go, and
cmd/root.go: keep EKS out of the executable wizard flow unless this PR
implements it, update the cluster help to advertise only runnable cloud
providers, and replace the root help’s “planned” wording with the available GKE
flow. Apply the corresponding changes at internal/cluster/ui/wizard_steps.go
lines 44-75, cmd/cluster/cluster.go line 30, and cmd/root.go line 67.
🧹 Nitpick comments (7)
internal/cluster/models/flags.go (1)

177-179: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Extend the cloud-only guard beyond --backend-config.

Only --backend-config is rejected for non-cloud types. --region, --profile, --project, --machine-type, --min-nodes, --max-nodes, and --spot are all documented as "(cloud only)" in their flag descriptions (Lines 76-82) but silently accepted (and presumably ignored) for k3d. This can mask user typos, e.g. --type k3d --min-nodes 2 succeeds but the flag is a no-op.

♻️ Proposed fix
 	if flags.BackendConfig != "" && !isCloud {
 		return fmt.Errorf("--backend-config only applies to cloud cluster types (eks, gke)")
 	}
+	if !isCloud && (flags.Region != "" || flags.Profile != "" || flags.Project != "" || flags.MachineType != "" ||
+		flags.MinNodes != 0 || flags.MaxNodes != 0 || flags.Spot) {
+		return fmt.Errorf("cloud-only flags (--region, --profile, --project, --machine-type, --min-nodes, --max-nodes, --spot) only apply to cloud cluster types (eks, gke)")
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/models/flags.go` around lines 177 - 179, Extend the
non-cloud validation in the flags validation flow around the existing
BackendConfig guard to reject any non-default cloud-only options: region,
profile, project, machine type, min-nodes, max-nodes, and spot. Reuse the
existing flags fields and return a clear error identifying that these options
only apply to cloud cluster types, while preserving valid cloud configurations.
internal/cluster/providers/terraform/workspace.go (1)

128-135: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

WriteRecord isn't actually atomic despite the doc's "atomically enough" claim.

os.WriteFile truncates and writes in place; a process kill mid-write (crash, OOM, SIGKILL) leaves a truncated/corrupt cluster.json. ReadRecord (line 116-126) does surface this as a "corrupt cluster.json" error rather than silently succeeding, so it's not catastrophic — the terraform state file still exists for manual recovery — but for a record whose own package doc calls out billing-critical resource tracking, a temp-file+rename write would remove this failure mode entirely for negligible cost.

🛠️ Suggested fix: write-then-rename for durability
 func (w *Workspace) WriteRecord(r Record) error {
 	data, err := json.MarshalIndent(r, "", "  ")
 	if err != nil {
 		return err
 	}
-	return os.WriteFile(filepath.Join(w.dir, recordFile), data, 0o600)
+	dst := filepath.Join(w.dir, recordFile)
+	tmp := dst + ".tmp"
+	if err := os.WriteFile(tmp, data, 0o600); err != nil {
+		return err
+	}
+	return os.Rename(tmp, dst)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/providers/terraform/workspace.go` around lines 128 - 135,
Update Workspace.WriteRecord to write the marshaled record to a temporary file
in w.dir, apply the intended 0600 permissions, then atomically rename it to
recordFile only after the write succeeds. Ensure failures clean up the temporary
file and do not replace the existing record.
internal/cluster/prerequisites/terraform/terraform.go (1)

29-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated commandExists helper across packages.

The same 4-line exec.LookPath wrapper is duplicated verbatim in internal/cluster/prerequisites/infracost/infracost.go (lines 26-29). Consider extracting a shared helper (e.g. in internal/platform) reused by all prerequisite installers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/prerequisites/terraform/terraform.go` around lines 29 - 32,
Extract the duplicated commandExists helper into a shared platform utility, then
update the Terraform and Infracost prerequisite installers to use that
implementation and remove their local helpers. Preserve the existing
exec.LookPath-based behavior and adjust references to the shared symbol.
internal/cluster/providers/eks/kubeconfig.go (1)

26-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared kubeconfig merge/remove logic between EKS and GKE. caData, mergeIntoDefaultKubeconfig, and removeFromDefaultKubeconfig are near-identical in both packages, differing only in the execConfig construction. A shared helper parameterized on name/endpoint/CACert/ExecConfig would eliminate the duplication and the risk of a fix landing in one provider but not the other.

  • internal/cluster/providers/eks/kubeconfig.go#L26-L128: move caData, mergeIntoDefaultKubeconfig, removeFromDefaultKubeconfig into a shared package/helper taking a generic record (name, endpoint, CACert, ExecConfig) instead of tfengine.Record directly.
  • internal/cluster/providers/gke/kubeconfig.go#L26-L119: switch to the same shared helper, passing GKE's execConfig() in place of EKS's execConfig(rec).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/providers/eks/kubeconfig.go` around lines 26 - 128, Extract
the shared CA decoding and kubeconfig merge/removal behavior from
internal/cluster/providers/eks/kubeconfig.go lines 26-128 into a common helper
that accepts cluster name, endpoint, CACert, and an ExecConfig, then update EKS
callers to use it while retaining EKS-specific execConfig construction. Update
internal/cluster/providers/gke/kubeconfig.go lines 26-119 to use the same helper
and pass GKE’s execConfig result, removing its duplicated caData,
mergeIntoDefaultKubeconfig, and removeFromDefaultKubeconfig implementations.
internal/cluster/providers/eks/templates/main.tf (2)

9-14: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

AWS provider constraint has no upper bound.

version = ">= 6.52" allows any future major version, while the eks/vpc module pins use ~> (pessimistic constraint). A future AWS provider major release could break terraform init/plan for existing and new workspaces with no warning, since nothing in this root module caps it.

♻️ Suggested fix
     aws = {
       source  = "hashicorp/aws"
-      version = ">= 6.52"
+      version = "~> 6.52"
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/providers/eks/templates/main.tf` around lines 9 - 14, Update
the aws constraint in the required_providers block to add an upper bound
consistent with the module ~> constraints, preventing unintended future major
provider versions while retaining compatibility with the current 6.52 baseline.

109-109: 🔒 Security & Privacy | 🔵 Trivial

Public EKS endpoint has no CIDR restriction.

endpoint_public_access = true with no endpoint_public_access_cidrs exposes the API server to the whole internet, relying solely on IAM for protection. Consider a --endpoint-cidrs-style flag defaulting to the operator's current IP, mirroring how enable_cluster_creator_admin_permissions is already scoped to the identity running terraform.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/providers/eks/templates/main.tf` at line 109, Restrict the
public EKS API endpoint configured by endpoint_public_access by adding an
endpoint_public_access_cidrs value sourced from a configurable endpoint-CIDRs
input, defaulting to the operator’s current public IP. Thread this input through
the existing cluster configuration and Terraform template symbols, while
preserving the current public-access behavior when explicitly configured.
internal/cluster/providers/eks/provider_test.go (1)

99-294: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test coverage gap vs. the GKE suite: decline/resume paths untested.

eks.Provider.CreateCluster shares the identical confirmApply-decline logic (lines 202-208 of provider.go) and resume-template-refresh logic (lines 177-185) with gke.Provider.CreateCluster, but only GKE's test suite exercises them (TestCreateCluster_DeclinedPlanAppliesNothing, TestCreateCluster_DeclinedResumeKeepsWorkspace, TestCreateCluster_ResumeRefreshesTemplate). A regression in either path on the EKS side would go undetected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/providers/eks/provider_test.go` around lines 99 - 294, Add
EKS tests covering the missing CreateCluster decline and resume behavior, using
the existing test helpers and symbols such as newTestProvider, CreateCluster,
and the executor call tracking. Verify a declined plan performs no apply, a
declined resume preserves the workspace, and a resumed create refreshes the
Terraform template before continuing, matching the corresponding GKE test
expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/cluster/create.go`:
- Around line 37-52: Update the create command’s Long description and examples
to reflect the actual cloud behavior: describe GKE as the supported cloud
provider, mention that EKS creation is temporarily disabled, and replace the EKS
example with a valid GKE-oriented example. Keep the local k3d guidance and other
creation details unchanged.
- Around line 279-337: Update the EKS creation gate in the cluster creation flow
to return an actionable error after showEKSComingSoonBanner() instead of
returning nil. Preserve the banner display, and ensure EKS create
invocations—including skip-wizard and non-interactive runs—exit nonzero without
proceeding to cluster creation.

In `@cmd/cluster/list.go`:
- Around line 96-139: Update isManaged in discoverExternalClusters so local k3d
entries with an empty Project do not match and suppress external GKE clusters
solely because their names collide. Require a project-aware match when filtering
discovered clusters, while preserving removal of genuinely managed GKE clusters.

In `@cmd/cluster/use.go`:
- Around line 96-139: Update the credential-fetch command in useExternalGKE to
pass found.Region via --location instead of --region, supporting both zonal and
regional GKE clusters. Add a test covering a context-less zonal cluster and
verify the generated context and command use the location value correctly.

In `@internal/cluster/prerequisites/aws/aws.go`:
- Around line 67-89: Update AwsInstaller.installLinux to install AWS CLI v2
packages: use the v2 package name for apt-based systems and aws-cli-v2 for
pacman, while removing or replacing unsupported dnf/yum entries with an official
AWS bundle fallback as appropriate. Preserve the existing sudo non-interactive
execution and manager iteration behavior.

In `@internal/cluster/provider/provider.go`:
- Around line 4-8: Update the package documentation in provider to reflect that
EKS and GKE are already routed to their real eks.New and gke.New backends,
removing the outdated statement that they return ErrProviderNotFound until their
backends land. Preserve the description of the shared Provider interface and
backend abstraction.

In `@internal/cluster/providers/eks/provider.go`:
- Around line 134-193: Add the existing name-collision preflight used by GKE to
EKS CreateCluster within the freshWorkspace block, after preparing the Terraform
variables and before ws.Scaffold. Pass the relevant context, region, profile,
and cluster name arguments expected by preflightNameCollision, and return its
error without creating the workspace when an unmanaged EKS cluster with the same
name exists.

In `@internal/cluster/providers/gke/templates/main.tf`:
- Around line 123-165: Add an explicit depends_on relationship to the module
"gke" block, referencing google_compute_router_nat.nat, so Cloud NAT is
provisioned before the private GKE node pool. Leave the existing module
configuration unchanged.

In `@internal/cluster/providers/terraform/confirm.go`:
- Around line 22-31: Update the confirmation prompt in the apply-plan flow to
describe the complete plan rather than always referencing summary.Add. Use the
existing summary counts (or an equivalent total) so plans containing only
changes or destroys produce an accurate message, while preserving the existing
ConfirmActionInteractive behavior and confirmation gate.

In `@internal/cluster/providers/terraform/cost.go`:
- Around line 38-70: The EstimateMonthlyCost function must locally bound the
infracost breakdown execution and use the existing fallback behavior when it
times out or cannot complete, including contexts without a deadline. Add a child
context with a finite timeout around execer.Execute, ensure it is canceled, and
handle timeout/failure without allowing the dry-run preview to hang; preserve
the existing successful parsing and cost formatting flow.

In `@internal/cluster/service.go`:
- Around line 272-288: Update ClusterService.ListClusters so errors from an
individual cloud provider’s ListAllClusters call are skipped rather than
returned, preserving local clusters and results already collected from earlier
providers; retain the existing error handling for the initial
manager.ListAllClusters call.

In `@internal/cluster/ui/wizard_steps.go`:
- Around line 93-105: Update the PromptRegion prompt label from “AWS Region” to
the provider-neutral “Region,” while preserving its validation, default value,
trimming, and error behavior for both AWS and GKE flows.

In `@internal/cluster/ui/wizard.go`:
- Around line 89-116: Update the PromptRegion method to accept a label argument
and use it for the prompt text instead of always displaying “AWS Region”. In the
cloud configuration flow around PromptRegion, pass “GCP Region” for GKE and “AWS
Region” for EKS, preserving the existing default-region behavior.

In `@internal/shared/download/pins_test.go`:
- Around line 315-335: Set Infracost’s Tarball field to true in its pin
definition, then update TestInfracost_Pins to assert Infracost.Tarball is
enabled alongside the existing version and asset checks.

In `@README.md`:
- Line 183: Update the EKS README example annotation to state that EKS cluster
creation is currently gated as coming soon and does not create a billed cloud
resource. Keep the command unchanged and align the wording with the existing
gating description in the cloud-clusters documentation.

---

Outside diff comments:
In `@internal/cluster/ui/wizard_steps.go`:
- Around line 44-75: Align supported-cloud messaging across PromptClusterType,
cmd/cluster/cluster.go, and cmd/root.go: keep EKS out of the executable wizard
flow unless this PR implements it, update the cluster help to advertise only
runnable cloud providers, and replace the root help’s “planned” wording with the
available GKE flow. Apply the corresponding changes at
internal/cluster/ui/wizard_steps.go lines 44-75, cmd/cluster/cluster.go line 30,
and cmd/root.go line 67.

---

Nitpick comments:
In `@internal/cluster/models/flags.go`:
- Around line 177-179: Extend the non-cloud validation in the flags validation
flow around the existing BackendConfig guard to reject any non-default
cloud-only options: region, profile, project, machine type, min-nodes,
max-nodes, and spot. Reuse the existing flags fields and return a clear error
identifying that these options only apply to cloud cluster types, while
preserving valid cloud configurations.

In `@internal/cluster/prerequisites/terraform/terraform.go`:
- Around line 29-32: Extract the duplicated commandExists helper into a shared
platform utility, then update the Terraform and Infracost prerequisite
installers to use that implementation and remove their local helpers. Preserve
the existing exec.LookPath-based behavior and adjust references to the shared
symbol.

In `@internal/cluster/providers/eks/kubeconfig.go`:
- Around line 26-128: Extract the shared CA decoding and kubeconfig
merge/removal behavior from internal/cluster/providers/eks/kubeconfig.go lines
26-128 into a common helper that accepts cluster name, endpoint, CACert, and an
ExecConfig, then update EKS callers to use it while retaining EKS-specific
execConfig construction. Update internal/cluster/providers/gke/kubeconfig.go
lines 26-119 to use the same helper and pass GKE’s execConfig result, removing
its duplicated caData, mergeIntoDefaultKubeconfig, and
removeFromDefaultKubeconfig implementations.

In `@internal/cluster/providers/eks/provider_test.go`:
- Around line 99-294: Add EKS tests covering the missing CreateCluster decline
and resume behavior, using the existing test helpers and symbols such as
newTestProvider, CreateCluster, and the executor call tracking. Verify a
declined plan performs no apply, a declined resume preserves the workspace, and
a resumed create refreshes the Terraform template before continuing, matching
the corresponding GKE test expectations.

In `@internal/cluster/providers/eks/templates/main.tf`:
- Around line 9-14: Update the aws constraint in the required_providers block to
add an upper bound consistent with the module ~> constraints, preventing
unintended future major provider versions while retaining compatibility with the
current 6.52 baseline.
- Line 109: Restrict the public EKS API endpoint configured by
endpoint_public_access by adding an endpoint_public_access_cidrs value sourced
from a configurable endpoint-CIDRs input, defaulting to the operator’s current
public IP. Thread this input through the existing cluster configuration and
Terraform template symbols, while preserving the current public-access behavior
when explicitly configured.

In `@internal/cluster/providers/terraform/workspace.go`:
- Around line 128-135: Update Workspace.WriteRecord to write the marshaled
record to a temporary file in w.dir, apply the intended 0600 permissions, then
atomically rename it to recordFile only after the write succeeds. Ensure
failures clean up the temporary file and do not replace the existing record.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e802beb-bce4-419c-a5c4-bb38571886bf

📥 Commits

Reviewing files that changed from the base of the PR and between b5eade1 and d3e37f0.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (88)
  • .github/workflows/test.yml
  • Makefile
  • README.md
  • cmd/cluster/cluster.go
  • cmd/cluster/contract_test.go
  • cmd/cluster/create.go
  • cmd/cluster/create_behavior_test.go
  • cmd/cluster/delete.go
  • cmd/cluster/delete_test.go
  • cmd/cluster/list.go
  • cmd/cluster/list_test.go
  • cmd/cluster/status_json_test.go
  • cmd/cluster/use.go
  • cmd/cluster/use_test.go
  • cmd/root.go
  • docs/README.md
  • docs/architecture/decisions.md
  • docs/development/setup/local-development.md
  • docs/getting-started/cloud-clusters.md
  • docs/getting-started/gke-workflow.md
  • go.mod
  • internal/chart/prerequisites/installer_test.go
  • internal/cluster/discovery/auth.go
  • internal/cluster/discovery/auth_test.go
  • internal/cluster/discovery/gke.go
  • internal/cluster/discovery/gke_test.go
  • internal/cluster/models/cluster.go
  • internal/cluster/models/cluster_test.go
  • internal/cluster/models/flags.go
  • internal/cluster/models/flags_test.go
  • internal/cluster/prerequisites/aws/aws.go
  • internal/cluster/prerequisites/checker.go
  • internal/cluster/prerequisites/checker_test.go
  • internal/cluster/prerequisites/docker/docker_test.go
  • internal/cluster/prerequisites/gcloud/gcloud.go
  • internal/cluster/prerequisites/infracost/infracost.go
  • internal/cluster/prerequisites/installer.go
  • internal/cluster/prerequisites/installer_test.go
  • internal/cluster/prerequisites/k3d/k3d_test.go
  • internal/cluster/prerequisites/terraform/terraform.go
  • internal/cluster/prerequisites/terraform/terraform_test.go
  • internal/cluster/provider/factory.go
  • internal/cluster/provider/factory_test.go
  • internal/cluster/provider/provider.go
  • internal/cluster/providers/eks/kubeconfig.go
  • internal/cluster/providers/eks/provider.go
  • internal/cluster/providers/eks/provider_test.go
  • internal/cluster/providers/eks/template.go
  • internal/cluster/providers/eks/template_validate_test.go
  • internal/cluster/providers/eks/templates/main.tf
  • internal/cluster/providers/gke/kubeconfig.go
  • internal/cluster/providers/gke/provider.go
  • internal/cluster/providers/gke/provider_test.go
  • internal/cluster/providers/gke/template.go
  • internal/cluster/providers/gke/template_validate_test.go
  • internal/cluster/providers/gke/templates/main.tf
  • internal/cluster/providers/k3d/manager.go
  • internal/cluster/providers/k3d/manager_test.go
  • internal/cluster/providers/terraform/backend.go
  • internal/cluster/providers/terraform/backend_test.go
  • internal/cluster/providers/terraform/confirm.go
  • internal/cluster/providers/terraform/cost.go
  • internal/cluster/providers/terraform/cost_test.go
  • internal/cluster/providers/terraform/engine.go
  • internal/cluster/providers/terraform/engine_test.go
  • internal/cluster/providers/terraform/progress.go
  • internal/cluster/providers/terraform/workspace.go
  • internal/cluster/providers/terraform/workspace_test.go
  • internal/cluster/service.go
  • internal/cluster/service_test.go
  • internal/cluster/types_test.go
  • internal/cluster/ui/operations.go
  • internal/cluster/ui/prompts.go
  • internal/cluster/ui/prompts_test.go
  • internal/cluster/ui/service.go
  • internal/cluster/ui/wizard.go
  • internal/cluster/ui/wizard_steps.go
  • internal/cluster/ui/wizard_steps_test.go
  • internal/cluster/ui/wizard_test.go
  • internal/cluster/utils/cmd_helpers.go
  • internal/k8s/contexts.go
  • internal/k8s/contexts_test.go
  • internal/platform/platform.go
  • internal/shared/config/system_test.go
  • internal/shared/config/transport_test.go
  • internal/shared/download/pins.go
  • internal/shared/download/pins_test.go
  • internal/shared/download/verify.go
💤 Files with no reviewable changes (4)
  • internal/cluster/ui/wizard_test.go
  • internal/cluster/ui/wizard_steps_test.go
  • internal/cluster/ui/prompts_test.go
  • internal/cluster/types_test.go

Comment thread cmd/cluster/create.go Outdated
Comment thread cmd/cluster/create.go
Comment thread cmd/cluster/list.go
Comment on lines +96 to +139
// discoverExternalClusters runs GKE discovery, dropping entries that are
// already managed (same name+project as a registry cluster). Auth problems
// degrade to notices, never errors: a logged-out gcloud must not break list.
// EKS discovery is not implemented yet — a notice says so.
func discoverExternalClusters(ctx context.Context, managed []models.ClusterInfo) ([]models.ClusterInfo, []string) {
notices := []string{"AWS EKS discovery is coming soon — external EKS clusters are not shown yet"}

exec := utils.CommandExecutor()
d := discovery.NewGKEDiscoverer(exec)
switch d.AuthStatus(ctx) {
case discovery.CLIMissing:
return nil, append(notices, "GKE: gcloud is not installed — install it to discover external clusters")
case discovery.NotAuthenticated:
// One unambiguous flow: offer the login right here (interactive only —
// non-interactive sessions get the same message as before).
if err := discovery.NewAuthFlow(exec).Ensure(ctx, false); err != nil {
return nil, append(notices, "GKE: "+err.Error())
}
}

result, err := d.Discover(ctx)
if err != nil {
return nil, append(notices, fmt.Sprintf("GKE discovery failed: %v", err))
}
for _, w := range result.Warnings {
notices = append(notices, "GKE discovery skipped "+w)
}

isManaged := func(c models.ClusterInfo) bool {
for _, m := range managed {
if m.Name == c.Name && (m.Project == "" || m.Project == c.Project) {
return true
}
}
return false
}
var external []models.ClusterInfo
for _, c := range result.Clusters {
if !isManaged(c) {
external = append(external, c)
}
}
return external, notices
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

isManaged can mask a real external cluster when names collide across types.

isManaged (Lines 124-131) matches only on Name and Project, and local k3d entries always have Project == "", so the m.Project == "" branch is satisfied unconditionally for any k3d cluster. A k3d cluster sharing a name with an unrelated external GKE cluster (e.g., the common default openframe-dev) will cause that GKE cluster to be silently dropped from --all output.

🐛 Proposed fix
 	isManaged := func(c models.ClusterInfo) bool {
 		for _, m := range managed {
-			if m.Name == c.Name && (m.Project == "" || m.Project == c.Project) {
+			if m.Name == c.Name && m.Type == c.Type && (m.Project == "" || m.Project == c.Project) {
 				return true
 			}
 		}
 		return false
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// discoverExternalClusters runs GKE discovery, dropping entries that are
// already managed (same name+project as a registry cluster). Auth problems
// degrade to notices, never errors: a logged-out gcloud must not break list.
// EKS discovery is not implemented yet — a notice says so.
func discoverExternalClusters(ctx context.Context, managed []models.ClusterInfo) ([]models.ClusterInfo, []string) {
notices := []string{"AWS EKS discovery is coming soon — external EKS clusters are not shown yet"}
exec := utils.CommandExecutor()
d := discovery.NewGKEDiscoverer(exec)
switch d.AuthStatus(ctx) {
case discovery.CLIMissing:
return nil, append(notices, "GKE: gcloud is not installed — install it to discover external clusters")
case discovery.NotAuthenticated:
// One unambiguous flow: offer the login right here (interactive only —
// non-interactive sessions get the same message as before).
if err := discovery.NewAuthFlow(exec).Ensure(ctx, false); err != nil {
return nil, append(notices, "GKE: "+err.Error())
}
}
result, err := d.Discover(ctx)
if err != nil {
return nil, append(notices, fmt.Sprintf("GKE discovery failed: %v", err))
}
for _, w := range result.Warnings {
notices = append(notices, "GKE discovery skipped "+w)
}
isManaged := func(c models.ClusterInfo) bool {
for _, m := range managed {
if m.Name == c.Name && (m.Project == "" || m.Project == c.Project) {
return true
}
}
return false
}
var external []models.ClusterInfo
for _, c := range result.Clusters {
if !isManaged(c) {
external = append(external, c)
}
}
return external, notices
}
// discoverExternalClusters runs GKE discovery, dropping entries that are
// already managed (same name+project as a registry cluster). Auth problems
// degrade to notices, never errors: a logged-out gcloud must not break list.
// EKS discovery is not implemented yet — a notice says so.
func discoverExternalClusters(ctx context.Context, managed []models.ClusterInfo) ([]models.ClusterInfo, []string) {
notices := []string{"AWS EKS discovery is coming soon — external EKS clusters are not shown yet"}
exec := utils.CommandExecutor()
d := discovery.NewGKEDiscoverer(exec)
switch d.AuthStatus(ctx) {
case discovery.CLIMissing:
return nil, append(notices, "GKE: gcloud is not installed — install it to discover external clusters")
case discovery.NotAuthenticated:
// One unambiguous flow: offer the login right here (interactive only —
// non-interactive sessions get the same message as before).
if err := discovery.NewAuthFlow(exec).Ensure(ctx, false); err != nil {
return nil, append(notices, "GKE: "+err.Error())
}
}
result, err := d.Discover(ctx)
if err != nil {
return nil, append(notices, fmt.Sprintf("GKE discovery failed: %v", err))
}
for _, w := range result.Warnings {
notices = append(notices, "GKE discovery skipped "+w)
}
isManaged := func(c models.ClusterInfo) bool {
for _, m := range managed {
if m.Name == c.Name && m.Type == c.Type && (m.Project == "" || m.Project == c.Project) {
return true
}
}
return false
}
var external []models.ClusterInfo
for _, c := range result.Clusters {
if !isManaged(c) {
external = append(external, c)
}
}
return external, notices
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/cluster/list.go` around lines 96 - 139, Update isManaged in
discoverExternalClusters so local k3d entries with an empty Project do not match
and suppress external GKE clusters solely because their names collide. Require a
project-aware match when filtering discovered clusters, while preserving removal
of genuinely managed GKE clusters.

Comment thread cmd/cluster/use.go
Comment on lines +67 to +89
func (a *AwsInstaller) installLinux() error {
type pm struct {
name string
args []string
}
managers := []pm{
{"apt", []string{"apt", "install", "-y", "awscli"}},
{"dnf", []string{"dnf", "install", "-y", "awscli"}},
{"yum", []string{"yum", "install", "-y", "awscli"}},
{"pacman", []string{"pacman", "-S", "--noconfirm", "aws-cli"}},
}
for _, m := range managers {
if !commandExists(m.name) {
continue
}
// Package installs need root; sudo -n keeps this non-interactive (the
// prerequisite flow already runs under a user confirmation).
if err := runCommand("sudo", append([]string{"-n"}, m.args...)...); err == nil {
return nil
}
}
return fmt.Errorf("could not install the AWS CLI automatically. %s", a.GetInstallHelp())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file =="
fd -a 'aws\.go$' . | sed 's#^\./##'

echo "== file outline =="
ast-grep outline internal/cluster/prerequisites/aws/aws.go --view compact || true

echo "== relevant lines =="
sed -n '1,140p' internal/cluster/prerequisites/aws/aws.go | nl -ba

echo "== all aws-cli package/install references =="
rg -n "awsci2|awscli|aws-cli|get-token|client\.authentication\.k8s\.io|Amazon EKS|aws eks" internal/cluster/prerequisites/aws/aws.go internal/cluster -S

Repository: flamingo-stack/openframe-cli

Length of output: 480


🌐 Web query:

AWS CLI v2 install package manager apt dnf yum pacman awscli aws-cli awscli2 v1 vs v2 current package names

💡 Result:

AWS CLI v2 is the current, recommended major version of the AWS Command Line Interface [1][2]. While official AWS documentation focuses on a universal command-line installer (available as a.zip package) [3][4] and a snap package (aws-cli) [3][4], many Linux distributions provide their own packaged versions of v2 in their official repositories [5][6]. Key differences and installation patterns: Command Name and Versioning Both AWS CLI v1 and v2 use the same command name, aws [7]. If both are installed, the one appearing first in your system's PATH will execute [7]. If you must have both available, it is recommended to create a symlink or alias (e.g., aws2) for the v2 binary [7]. Package Names by Distribution Distribution-specific repositories may use different names to distinguish v2 from the legacy v1 (often named awscli): - Arch Linux: The package is named aws-cli-v2 [5][8]. - Fedora/RHEL: The package is frequently named awscli2 [6][9]. - Other Distributions: Depending on the maintainers, you may find it under awscli2 or sometimes bundled as a newer version of the legacy awscli package. Installation Recommendations - Official Recommendation: AWS officially recommends using their provided command-line installer for version control [3][4] or the snap package (snap install aws-cli --classic) if you prefer automatic updates [3][4]. - Distribution Packages: Using your system's native package manager (apt, dnf, yum, pacman) is a common alternative that integrates with the OS update lifecycle [8][10]. Always check your specific distribution's repository for the correct package name (e.g., searching with apt search awscli or dnf search awscli) before installing, as names vary by vendor. Migration from v1 AWS CLI v2 is not backwards-compatible with all v1 features [1]. If you are migrating, AWS provides a migration guide and a v1-to-v2 migration tool to help identify and address breaking changes in scripts or workflows [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant file content =="
sed -n '1,140p' internal/cluster/prerequisites/aws/aws.go

echo "== references in aws prerequisite and related cluster code =="
rg -n "awsci2|awscli|aws-cli|get-token|client\.authentication\.k8s\.io|Amazon EKS|aws eks" internal/cluster/prerequisites/aws/aws.go internal/cluster -S || true

echo "== package names in repo =="
rg -n "aws-cli|awscli|aws-cli-v2|awscli2" . -S || true

echo "== parse package manager map and commands =="
python3 - <<'PY'
from pathlib import Path
p=Path('internal/cluster/prerequisites/aws/aws.go')
text=p.read_text()
start=text.index('func (a *AwsInstaller) installLinux()')
end=text.find('\n}', start)
print(text[start:end+2])
PY

Repository: flamingo-stack/openframe-cli

Length of output: 6729


🌐 Web query:

Debian Ubuntu apt package awscli AWS CLI v2 awscli2 package

💡 Result:

For Debian and Ubuntu systems, there is no official apt package named awscli2. While official repositories for Debian and Ubuntu distributions may contain an awscli package, its versioning and availability depend on the specific release of the operating system [1][2][3]. AWS does not maintain these distribution-specific repositories and cannot guarantee that they contain the latest version of the AWS CLI [4][5][6]. If you require the most up-to-date version of AWS CLI v2, AWS explicitly recommends using their official installation methods rather than relying on system-packaged versions [4][5]. Official Recommended Methods: 1. Official AWS CLI v2 Installer (Recommended for version control): This method downloads a bundled installer directly from AWS, which installs the CLI to /usr/local/aws-cli and creates symlinks in /usr/local/bin [4][2][3]. - Download the bundle: curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" - Extract and install: unzip awscliv2.zip && sudo./aws/install [4][3] - To update, run the installer with the --update flag: sudo./aws/install --update [3] 2. Official Snap Package (Recommended for automatic updates): AWS provides an official, supported snap package that handles automatic updates [4][5]. - Install via snap: sudo snap install aws-cli --classic [4][2] Important Considerations: - Distribution Packages: You may see an awscli package available via sudo apt install awscli. Depending on your OS version (e.g., Debian Trixie or recent Ubuntu releases), this may provide a version of the v2 CLI, but it is managed by the distribution maintainers, not AWS [2][3][7]. - Third-Party Repositories: Avoid unofficial apt repositories claiming to provide awscli2 unless you have verified their security and maintenance practices, as these are not supported or endorsed by AWS [4][8].

Citations:


🌐 Web query:

AWS EKS aws eks get-token client.authentication.k8s.io v1alpha1 removed Kubernetes 1.24 documentation

💡 Result:

The API version client.authentication.k8s.io/v1alpha1 was removed in Kubernetes 1.24 [1][2]. This removal meant that any credential plugins, including older versions of the aws eks get-token command, that continued to use v1alpha1 in their kubeconfig files caused Kubernetes clients (like kubectl 1.24+) to return an error stating that the API version was invalid [3][1][4]. To resolve this, users must update their kubeconfig files to use a supported API version, such as client.authentication.k8s.io/v1beta1 or client.authentication.k8s.io/v1 [3][1][4]. Key points regarding this change: - Migration: The recommended way to fix this is to regenerate your kubeconfig file using the AWS CLI, which will automatically update the API version to a supported one [3][1]. You can run: aws eks update-kubeconfig --name - Compatibility: AWS CLI versions 1.24.0 and later were updated to default to using the v1beta1 API version to ensure compatibility with Kubernetes 1.24+ [5][3]. - Manual Fix: Alternatively, you can manually edit your ~/.kube/config file and change the apiVersion field under the user's exec configuration from client.authentication.k8s.io/v1alpha1 to client.authentication.k8s.io/v1beta1 [3][1]. This change followed standard Kubernetes deprecation policies, where alpha APIs can be removed without prior deprecation notice [6]. The v1alpha1 version had been replaced by more stable v1beta1 and v1 versions, which are now required for cluster authentication [2][4].

Citations:


Use AWS CLI v2 packages for Linux auto-install.

installLinux installs apt/dnf/yum awscli, which on Debian/Ubuntu is the legacy v1 package, and Arch’s aws-cli is also v1 (aws-cli-v2 is the v2 package). For the EKS kubeconfig flow, install the v2 packages instead, or drop the unsupported Fedora/RHEL/RHEL-derived paths and offer an official AWS bundle fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/prerequisites/aws/aws.go` around lines 67 - 89, Update
AwsInstaller.installLinux to install AWS CLI v2 packages: use the v2 package
name for apt-based systems and aws-cli-v2 for pacman, while removing or
replacing unsupported dnf/yum entries with an official AWS bundle fallback as
appropriate. Preserve the existing sudo non-interactive execution and manager
iteration behavior.

Comment thread internal/cluster/service.go
Comment thread internal/cluster/ui/wizard_steps.go Outdated
Comment thread internal/cluster/ui/wizard.go
Comment on lines +315 to +335
func TestInfracost_Pins(t *testing.T) {
if !strings.HasPrefix(Infracost.Version, "v") {
t.Fatalf("Infracost.Version must be a v-prefixed tag, got %q", Infracost.Version)
}
for _, p := range []struct{ os, arch string }{
{"linux", "amd64"}, {"linux", "arm64"},
{"darwin", "amd64"}, {"darwin", "arm64"},
{"windows", "amd64"},
} {
asset, ok := Infracost.Asset(p.os, p.arch)
if !ok {
t.Errorf("no infracost asset for %s/%s", p.os, p.arch)
continue
}
if len(asset.SHA256) != 64 {
t.Errorf("%s/%s: SHA256 must be 64 hex chars, got %q", p.os, p.arch, asset.SHA256)
}
if !strings.Contains(asset.URL, Infracost.Version) || !strings.HasSuffix(asset.URL, p.os+"-"+p.arch+".tar.gz") {
t.Errorf("%s/%s: URL %q must contain version and end with platform.tar.gz", p.os, p.arch, asset.URL)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mark Infracost as a tarball and assert it here.

Infracost uses .tar.gz assets, but its pin lacks Tarball: true; InstallPinnedTool will therefore install compressed archive bytes as the executable. Set Tarball: true in internal/shared/download/pins.go and add an Infracost.Tarball assertion in this test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/shared/download/pins_test.go` around lines 315 - 335, Set
Infracost’s Tarball field to true in its pin definition, then update
TestInfracost_Pins to assert Infracost.Tarball is enabled alongside the existing
version and asset checks.

Comment thread README.md Outdated
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 15 file(s) based on 15 unresolved review comments.

A stacked PR containing fixes has been created.

  • Stacked PR: #252
  • Files modified:
  • README.md
  • cmd/cluster/create.go
  • cmd/cluster/list.go
  • cmd/cluster/use.go
  • internal/cluster/prerequisites/aws/aws.go
  • internal/cluster/provider/provider.go
  • internal/cluster/providers/eks/provider.go
  • internal/cluster/providers/gke/templates/main.tf
  • internal/cluster/providers/terraform/confirm.go
  • internal/cluster/providers/terraform/cost.go
  • internal/cluster/service.go
  • internal/cluster/ui/wizard.go
  • internal/cluster/ui/wizard_steps.go
  • internal/shared/download/pins.go
  • internal/shared/download/pins_test.go

Time taken: 8m 21s

    - create: gated EKS create now exits non-zero with an actionable
      coming-soon error (scripts must not see success); help texts updated
    - list: isManaged match is project-aware so a k3d cluster no longer
      shadows a same-name external GKE cluster
    - use: get-credentials uses --location (works for zonal clusters too)
    - gke: module depends_on Cloud NAT so nodes can pull images on boot
    - eks: preflight name-collision guard (mirrors GKE); aws provider
      pinned '>= 6.52, < 7.0'
    - terraform: apply prompt shows full plan counts; infracost run capped
      at 60s; cluster.json written atomically (temp file + rename)
    - flags: non-cloud types reject all cloud-only flags
    - service: ListClusters degrades gracefully when a cloud provider
      errors instead of failing the whole listing
    - wizard: region prompt labeled per provider (GCP Region / AWS Region)
    - tests: EKS banner error + CI step expect non-zero exit; zonal use
      test; EKS collision + declined-plan tests

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/cluster/ui/wizard_steps.go (1)

50-53: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not expose EKS as a dead-end option.

The wizard currently presents EKS, then refuses the selection and loops back. This makes the EKS flow in internal/cluster/ui/wizard.go unreachable, contradicting the PR’s managed EKS support objective. Return models.ClusterTypeEKS here, or remove the option until EKS is intentionally unavailable.

Also applies to: 68-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cluster/ui/wizard_steps.go` around lines 50 - 53, The wizard’s
cluster-type selection must not expose EKS while rejecting it afterward. Update
the items and selection mapping in the wizard steps flow to either return
models.ClusterTypeEKS and allow the existing EKS path in wizard.go to proceed,
or remove the EKS option entirely until support is intentionally unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/cluster/ui/wizard_steps.go`:
- Around line 50-53: The wizard’s cluster-type selection must not expose EKS
while rejecting it afterward. Update the items and selection mapping in the
wizard steps flow to either return models.ClusterTypeEKS and allow the existing
EKS path in wizard.go to proceed, or remove the EKS option entirely until
support is intentionally unavailable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b7bb4e3-96d2-401d-a40e-77df2c5ec36a

📥 Commits

Reviewing files that changed from the base of the PR and between d3e37f0 and b837bce.

📒 Files selected for processing (21)
  • .github/workflows/test.yml
  • README.md
  • cmd/cluster/cluster.go
  • cmd/cluster/create.go
  • cmd/cluster/create_behavior_test.go
  • cmd/cluster/list.go
  • cmd/cluster/use.go
  • cmd/cluster/use_test.go
  • cmd/root.go
  • internal/cluster/models/flags.go
  • internal/cluster/provider/provider.go
  • internal/cluster/providers/eks/provider.go
  • internal/cluster/providers/eks/provider_test.go
  • internal/cluster/providers/eks/templates/main.tf
  • internal/cluster/providers/gke/templates/main.tf
  • internal/cluster/providers/terraform/confirm.go
  • internal/cluster/providers/terraform/cost.go
  • internal/cluster/providers/terraform/workspace.go
  • internal/cluster/service.go
  • internal/cluster/ui/wizard.go
  • internal/cluster/ui/wizard_steps.go
🚧 Files skipped from review as they are similar to previous changes (19)
  • internal/cluster/providers/terraform/confirm.go
  • cmd/root.go
  • README.md
  • internal/cluster/providers/terraform/cost.go
  • cmd/cluster/cluster.go
  • cmd/cluster/use.go
  • internal/cluster/provider/provider.go
  • cmd/cluster/use_test.go
  • internal/cluster/providers/eks/templates/main.tf
  • cmd/cluster/create.go
  • internal/cluster/models/flags.go
  • .github/workflows/test.yml
  • internal/cluster/providers/gke/templates/main.tf
  • cmd/cluster/create_behavior_test.go
  • cmd/cluster/list.go
  • internal/cluster/providers/eks/provider_test.go
  • internal/cluster/providers/terraform/workspace.go
  • internal/cluster/providers/eks/provider.go
  • internal/cluster/service.go

    Fedora/RHEL ship v2 as 'awscli2' and Arch as 'aws-cli-v2' (the old
    'awscli'/'aws-cli' names are the retired v1). apt keeps 'awscli' (v2 on
    Debian 12+/Ubuntu 24.04+) but the install now verifies the resulting
    binary is v2 — legacy v1 repos (e.g. Ubuntu 22.04) get an actionable
    error instead of a CLI whose 'aws eks get-token' emits an auth API
    kubectl >=1.24 rejects.
…one (#266)

    The module built a regional cluster, so node counts were per-zone: --nodes 3
    provisioned 3 nodes in each of the region's ~3 zones (~9 total) while every
    display still said 3 — roughly 3x the expected node bill, silently.

    Default to a single-zone cluster (regional=false), so the node pool's counts
    are the real totals and the existing 'Nodes: N' display becomes truthful with
    no display change. The provider resolves a concrete zone from the region via
    gcloud (alphabetically-first, deterministic so a resume never moves the
    cluster). A new --ha flag opts back into a regional, multi-zone control plane
    and node pool for HA.

    NOTE: the terraform template change (regional/zone vars, zones wiring) could
    not be validated in this environment — run 'make test-tfvalidate' (needs a
    terraform binary) before merging.
    fix(gke): release PVC disks before destroy and report any orphans after

    cluster delete ran a bare terraform destroy under a live platform. PVC-
    provisioned Persistent Disks live outside terraform state, so when the
    node pool died they detached and survived as billable orphans, while the
    CLI reported a clean teardown.

    Before destroy, delete the app namespaces (argocd first so its controller
    stops re-syncing, then openframe) and wait, bounded, for their PVC-backed
    PersistentVolumes to drain so the GKE CSI driver deletes the underlying
    disks while the cluster is still alive. Every step is best-effort and
    never blocks the destroy the user asked for.

    After a successful destroy, sweep for disks still labeled
    goog-k8s-cluster-name=<name> and report them with a cleanup command
    (report-only — the CLI never deletes cloud disks directly), so a
    'cleaned up' delete never silently leaves orphans
Pre-resolve the develop->main doc conflicts on develop so the eventual
merge into main is clean. main's #263/#264 (automated Technical Writer)
restructured the docs and regenerated README/docs; those changes are
absorbed here, except for four files kept at develop's versions because
develop's are newer and reflect shipped GKE/cloud features main lacks:

  - README.md
  - docs/README.md
  - docs/development/setup/local-development.md
  - docs/architecture/decisions.md (bot deleted it; kept as it is a
    substantive hand-written architecture doc)
    Ctrl+C during a GKE create left the terminal silent about recovery: the
    apply error carried a 're-run create to resume' hint, but the interruption
    handler prints only 'Operation cancelled by user.' and discards the error
    text, so the hint (though truthful — the workspace state is preserved) was
    never shown.

    Carry the hint structurally via a ResumeHint() method on the error, and
    have the interruption handler surface it with errors.As after the cancel
    notice. The cancellation chain still satisfies errors.Is(context.Canceled),
    so it is classified as an interruption exactly as before
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.

1 participant