Skip to content

feat: add cluster deploy command for parallel IAM and VPC deployment#11

Open
rrp-bot wants to merge 6 commits into
openshift-online:mainfrom
rrp-bot:feature/cluster-deploy-parallel
Open

feat: add cluster deploy command for parallel IAM and VPC deployment#11
rrp-bot wants to merge 6 commits into
openshift-online:mainfrom
rrp-bot:feature/cluster-deploy-parallel

Conversation

@rrp-bot

@rrp-bot rrp-bot commented Mar 26, 2026

Copy link
Copy Markdown

Summary

  • Adds a new rosactl cluster deploy CLUSTER_NAME command that deploys both IAM and VPC CloudFormation stacks concurrently using goroutines
  • Reduces total deployment time compared to running cluster-iam create and cluster-vpc create sequentially
  • Accepts the union of flags from both individual commands; --oidc-issuer-url, --region, and --availability-zones are required

Usage

rosactl cluster deploy my-cluster \
  --oidc-issuer-url https://d1234.cloudfront.net/my-cluster \
  --region us-east-1 \
  --availability-zones us-east-1a,us-east-1b,us-east-1c

Optional VPC flags (--vpc-cidr, --public-subnet-cidrs, --private-subnet-cidrs, --single-nat-gateway) carry the same defaults as cluster-vpc create.

Implementation

  • New package internal/commands/cluster with cluster.go (parent command) and deploy.go (deploy subcommand)
  • Both IAM and VPC service calls run in separate goroutines; errors from either are collected and returned together
  • The new cluster command is registered alongside existing commands in root.go

Test plan

  • go build ./... passes (verified locally)
  • rosactl cluster --help shows the deploy subcommand
  • rosactl cluster deploy --help shows all expected flags
  • End-to-end: run against a real AWS account or LocalStack and confirm both rosa-<name>-iam and rosa-<name>-vpc stacks are created

🤖 Generated with Claude Code

Adds a new `rosactl cluster deploy` command that creates both IAM and VPC
CloudFormation stacks concurrently using goroutines, reducing total deployment
time compared to running them sequentially.

Usage:
  rosactl cluster deploy CLUSTER_NAME \
    --oidc-issuer-url https://... \
    --region us-east-1 \
    --availability-zones us-east-1a,us-east-1b,us-east-1c

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown

Walkthrough

Adds a new top-level cluster Cobra command with a deploy subcommand; introduces exported CreateOptions, AddFlags, and RunCreate APIs for both IAM and VPC command packages; and adds integration tests exercising parallel cluster deployment against LocalStack.

Changes

Cohort / File(s) Summary
Cluster command group & deploy
internal/commands/cluster/cluster.go, internal/commands/cluster/deploy.go
New cluster command group and deploy subcommand. deploy composes IAM and VPC option structs, registers flags via exported AddFlags, validates required flags, runs IAM and VPC create operations concurrently with a cancellable context, and aggregates per-component errors.
IAM command API changes
internal/commands/clusteriam/create.go
Replaced unexported options with exported CreateOptions, added exported AddFlags, and renamed/ exported RunCreate. Validation now requires Region at runtime and OIDCIssuerURL prefix validated in RunCreate. Review exported API and flag wiring.
VPC command API changes
internal/commands/clustervpc/create.go
Replaced unexported options with exported CreateOptions, added exported AddFlags, and renamed/exported RunCreate. RunCreate now validates Region, uses config.WithRegion, and references renamed fields (CamelCase). Check flag parsing and request construction.
CLI integration
internal/commands/root.go
Root command now registers the new cluster subcommand.
Tests (LocalStack)
test/localstack/localstack_test.go, test/localstack/lambda_test.go
Added integration test for rosactl cluster deploy and ensured per-test ECR repositoryName generation/cleanup. Tests assert CloudFormation stack existence/status and CLI output.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as "rosactl (cluster deploy)"
    participant Flags as "Flag parsing"
    participant OIDC as "OIDC provider (TLS thumbprint)"
    participant AWS as "AWS SDK / Config"
    participant IAM as "CloudFormation (IAM stack)"
    participant VPC as "CloudFormation (VPC stack)"

    User->>CLI: run `rosactl cluster deploy <name>` with flags
    CLI->>Flags: parse flags, require: region, availability-zones
    CLI->>CLI: populate IAM & VPC CreateOptions
    CLI->>AWS: load config (region)
    par Concurrent operations
        CLI->>OIDC: fetch TLS thumbprint (if needed)
        OIDC-->>CLI: return thumbprint
        CLI->>IAM: RunCreate(ctx, iamOpts)
        IAM-->>CLI: result (success|error)
    and
        CLI->>VPC: RunCreate(ctx, vpcOpts)
        VPC-->>CLI: result (success|error)
    end
    CLI->>CLI: collect results, cancel on first error
    alt all success
        CLI-->>User: print completion message
    else any failure
        CLI-->>User: return aggregated error report
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a new cluster deploy command with parallel deployment capability for IAM and VPC resources.
Description check ✅ Passed The description provides a clear summary of the feature, usage examples, implementation details, and test plan, all directly related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@psav

psav commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

I don't like that it's called cluster - perhaps cluster-infra would be a better name for it?

Comment thread internal/commands/cluster/deploy.go Outdated
},
}

cmd.Flags().StringVar(&opts.oidcIssuerURL, "oidc-issuer-url", "", "OIDC issuer URL from Management Cluster (required)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There must be a way to combine these from the definitions already - I hate having to maintain two lists of options. One for the combined and one for the separate tasks.

@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.

🧹 Nitpick comments (2)
internal/commands/cluster/deploy.go (2)

159-172: Consider canceling remaining operations on first failure.

Currently, if one deployment fails, the other continues to completion. This is reasonable for showing all errors, but it may waste time and resources if one operation fails early.

Consider using a cancellable context with context.WithCancel to abort the remaining operation when the first failure occurs. This is optional since the current behavior has the benefit of reporting all errors at once.

♻️ Optional: Cancel on first failure
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
 	results := make(chan deployResult, 2)
 	var wg sync.WaitGroup

 	// IAM deployment goroutine
 	wg.Add(1)
 	go func() {
 		defer wg.Done()
 		// ... existing code ...
 		if err != nil {
+			cancel()
 			results <- deployResult{"IAM", err}
 			return
 		}
 		// ...
 	}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/commands/cluster/deploy.go` around lines 159 - 172, The loop
collecting results currently waits for all goroutines to finish even if one
fails; modify the deployment to use a cancellable context (context.WithCancel)
that you pass into the worker goroutines which send into the results channel,
and call cancel() as soon as you observe the first non-nil result.err in the
result-reading loop (the loop that appends to errs). Ensure each worker (the
functions that read from the shared context or are spawned where results are
produced) checks ctx.Done() and returns early when cancelled so work is aborted,
and keep the existing aggregation of errs (errs slice and the final fmt.Errorf)
so you still report errors from already completed tasks.

100-126: Concurrent output may interleave.

Both goroutines use fmt.Printf/fmt.Println without synchronization, which can cause interleaved output in the terminal. This is a minor cosmetic issue and doesn't affect correctness.

If cleaner output is desired, consider buffering logs per goroutine and printing them sequentially after completion, or using a synchronized logger.

Also applies to: 130-152

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/commands/cluster/deploy.go` around lines 100 - 126, The goroutine
that calls crypto.GetOIDCThumbprint and clusteriam.CreateIAM (and the analogous
goroutine at 130-152) prints directly with fmt.Printf/Println causing
interleaved terminal output; change these goroutines to accumulate their log
text into a local buffer (e.g., strings.Builder or bytes.Buffer) instead of
printing immediately, then emit the full buffered string once at the end (after
resp handling) or send it along with deployResult so a single synchronized
emitter prints outputs, or alternatively use a shared logger guarded by a
sync.Mutex; update the goroutines that reference opts.oidcIssuerURL,
opts.clusterName, clusteriam.CreateIAM, crypto.GetOIDCThumbprint, wg and results
accordingly so printing is performed atomically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/commands/cluster/deploy.go`:
- Around line 159-172: The loop collecting results currently waits for all
goroutines to finish even if one fails; modify the deployment to use a
cancellable context (context.WithCancel) that you pass into the worker
goroutines which send into the results channel, and call cancel() as soon as you
observe the first non-nil result.err in the result-reading loop (the loop that
appends to errs). Ensure each worker (the functions that read from the shared
context or are spawned where results are produced) checks ctx.Done() and returns
early when cancelled so work is aborted, and keep the existing aggregation of
errs (errs slice and the final fmt.Errorf) so you still report errors from
already completed tasks.
- Around line 100-126: The goroutine that calls crypto.GetOIDCThumbprint and
clusteriam.CreateIAM (and the analogous goroutine at 130-152) prints directly
with fmt.Printf/Println causing interleaved terminal output; change these
goroutines to accumulate their log text into a local buffer (e.g.,
strings.Builder or bytes.Buffer) instead of printing immediately, then emit the
full buffered string once at the end (after resp handling) or send it along with
deployResult so a single synchronized emitter prints outputs, or alternatively
use a shared logger guarded by a sync.Mutex; update the goroutines that
reference opts.oidcIssuerURL, opts.clusterName, clusteriam.CreateIAM,
crypto.GetOIDCThumbprint, wg and results accordingly so printing is performed
atomically.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68aef778-5590-4c5b-97ad-6037e8d09121

📥 Commits

Reviewing files that changed from the base of the PR and between 04a10a4 and a4b8129.

📒 Files selected for processing (3)
  • internal/commands/cluster/cluster.go
  • internal/commands/cluster/deploy.go
  • internal/commands/root.go

Export CreateOptions, AddFlags, and RunCreate from the clusteriam and
clustervpc command packages so the deploy command composes them directly
instead of maintaining a second copy of every flag.

Adding or changing a flag on cluster-iam create or cluster-vpc create
now automatically applies to cluster deploy with no extra work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rrp-bot

rrp-bot commented Mar 26, 2026

Copy link
Copy Markdown
Author

Done — refactored in the latest commit. CreateOptions, AddFlags, and RunCreate are now exported from the clusteriam and clustervpc command packages. The deploy command composes them directly:

cmdiam.AddFlags(cmd, &opts.iam)
cmdvpc.AddFlags(cmd, &opts.vpc)

Adding or changing a flag on either individual create command now automatically applies to cluster deploy — no second list to maintain.

…definitions

--region is already defined as a persistent flag on the root command, making
it available to every subcommand automatically. Remove the per-command local
flag registrations and read the inherited value in each RunE instead.

Validation (empty region) is done in RunCreate/runDeploy since marking an
inherited persistent flag required per-command would affect all commands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rrp-bot

rrp-bot commented Mar 26, 2026

Copy link
Copy Markdown
Author

Updated in the latest commit. --region is already a persistent flag on the root command, so every subcommand inherits it automatically. I've removed all the per-command local re-definitions — each RunE now just reads the inherited value with cmd.Flags().GetString("region"), and validation (empty check) happens at the start of each RunCreate/runDeploy.

Add context.WithCancel so that if IAM or VPC creation fails the other
operation's context is cancelled, aborting it at the next AWS SDK call
rather than running it to completion unnecessarily.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rrp-bot

rrp-bot commented Mar 26, 2026

Copy link
Copy Markdown
Author

Addressed both CodeRabbit nitpicks:

Context cancellation — implemented. A context.WithCancel is now created in runDeploy and passed into both goroutines. Whichever finishes with an error calls cancel(), aborting the other at the next AWS SDK checkpoint. We still collect and report all errors (the channel is buffered for 2) so nothing is silently swallowed.

Interleaved output — acknowledged as a known trade-off. Both goroutines delegate to cmdiam.RunCreate/cmdvpc.RunCreate, which own their own fmt.Print calls. Fixing this properly would require changing RunCreate to accept an io.Writer so output can be buffered per-goroutine and flushed sequentially — that's a worthwhile follow-up but out of scope here.

…mand

Adds a Ginkgo spec that exercises `rosactl cluster deploy` against LocalStack,
verifying that both the IAM and VPC CloudFormation stacks are created by a
single command invocation.

Follows the same conventions as the existing IAM/VPC tests:
- Accepts CREATE_COMPLETE or CREATE_FAILED (LocalStack resource limitations)
- Skips gracefully in offline environments where the OIDC thumbprint TLS
  connection cannot reach s3.amazonaws.com, which also triggers context
  cancellation of the VPC goroutine

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rrp-bot

rrp-bot commented Mar 26, 2026

Copy link
Copy Markdown
Author

Added a LocalStack integration test for cluster deploy in test/localstack/localstack_test.go — new Describe("Cluster Deploy (parallel)") block.

The test runs rosactl cluster deploy and then asserts that both rosa-<cluster>-iam and rosa-<cluster>-vpc stacks exist afterward, which is the meaningful thing to verify for this command (vs the existing per-command tests). It follows the same conventions as the existing IAM/VPC specs — accepting CREATE_COMPLETE or CREATE_FAILED for LocalStack limitations, and skipping gracefully if the OIDC thumbprint TLS fetch can't reach the internet (which also triggers context cancellation of the VPC goroutine via the change in the previous commit).

repositoryName was declared in the var block but never assigned before
being passed to CreateRepository, causing ECR to reject it with
InvalidParameterException on every Lambda test run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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: 4

🧹 Nitpick comments (1)
internal/commands/clusteriam/create.go (1)

23-28: Keep the required-flag contract with the reusable helper.

AddFlags is now the reuse point, but the fact that --oidc-issuer-url is mandatory still lives outside it. That makes future composite commands easy to wire incorrectly and lets help/validation drift from the standalone command.

Also applies to: 57-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/commands/clusteriam/create.go` around lines 23 - 28, AddFlags
currently registers the --oidc-issuer-url flag but doesn't enforce the
"required" contract; update AddFlags (and the corresponding reuse point for the
other occurrence) to mark the flag required after registering it (e.g., call
cmd.MarkFlagRequired("oidc-issuer-url")) so the requirement lives with the
reusable helper; reference AddFlags, CreateOptions, and the
cmd.Flags().StringVar registration when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/commands/clustervpc/create.go`:
- Around line 78-93: RunCreate currently only validates Region; add the same
upfront ClusterName validation used in internal/commands/clusteriam/create.go so
invalid cluster names are rejected before any VPC work begins. In RunCreate
(function RunCreate, CreateOptions struct usage), check opts.ClusterName at the
top and call the shared validation helper (or extract the validation logic from
clusteriam/create.go) to return an error when the name is invalid, ensuring the
VPC workflow never starts for bad names.
- Around line 26-34: The flag description in AddFlags incorrectly advertises
"--availability-zones" as optional/auto-detected which is not true for all
callers; update the StringVar call for opts.AvailabilityZones in AddFlags to use
a neutral description like "Comma-separated availability zones" (remove
"(optional, auto-detected if empty)"), and let specific commands that require
the flag override or mark it required in their own command setup rather than
relying on this shared helper.

In `@test/localstack/localstack_test.go`:
- Around line 309-313: The test hardcodes availability zones
"us-east-1a,us-east-1b,us-east-1c" when constructing deployCmd
(exec.CommandContext with arguments "cluster", "deploy", testCluster, etc.),
which breaks when AWS_REGION is not us-east-1; change the AZ argument to be
derived from the awsRegion variable by programmatically constructing the three
AZ strings (e.g., awsRegion+"a", awsRegion+"b", awsRegion+"c") joined with
commas and pass that computed string for "--availability-zones" so the test uses
the correct AZs for the configured region.
- Around line 323-359: The current DescribeStacks error handling for the VPC and
IAM checks (calls to cfnClient.DescribeStacks around vpcStackName and
iamStackName) treats any "does not exist" error as an expected offline skip;
change this to only Skip when the error text clearly indicates the known
thumbprint/TLS failure (e.g., error.Error() contains "thumbprint" or
"certificate" or the specific TLS/OIDC thumbprint failure string you use
elsewhere), and for any other error call Expect(err).NotTo(HaveOccurred(), ...)
so unrelated deploy failures fail the spec; update both the VPC and IAM
DescribeStacks error branches to perform this specific-message check before
skipping.

---

Nitpick comments:
In `@internal/commands/clusteriam/create.go`:
- Around line 23-28: AddFlags currently registers the --oidc-issuer-url flag but
doesn't enforce the "required" contract; update AddFlags (and the corresponding
reuse point for the other occurrence) to mark the flag required after
registering it (e.g., call cmd.MarkFlagRequired("oidc-issuer-url")) so the
requirement lives with the reusable helper; reference AddFlags, CreateOptions,
and the cmd.Flags().StringVar registration when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 07106456-f3f9-4f4d-9f51-9e22854addf5

📥 Commits

Reviewing files that changed from the base of the PR and between a4b8129 and 7b52ca6.

📒 Files selected for processing (5)
  • internal/commands/cluster/deploy.go
  • internal/commands/clusteriam/create.go
  • internal/commands/clustervpc/create.go
  • test/localstack/lambda_test.go
  • test/localstack/localstack_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/commands/cluster/deploy.go

Comment on lines +26 to +34
// AddFlags registers the VPC-specific flags on cmd bound to opts.
// --region is intentionally excluded: it is a persistent root-level flag
// inherited by all commands.
func AddFlags(cmd *cobra.Command, opts *CreateOptions) {
cmd.Flags().StringVar(&opts.VpcCidr, "vpc-cidr", "10.0.0.0/16", "CIDR block for the VPC")
cmd.Flags().StringVar(&opts.PublicSubnetCidrs, "public-subnet-cidrs", "10.0.101.0/24,10.0.102.0/24,10.0.103.0/24", "Comma-separated public subnet CIDRs")
cmd.Flags().StringVar(&opts.PrivateSubnetCidrs, "private-subnet-cidrs", "10.0.0.0/19,10.0.32.0/19,10.0.64.0/19", "Comma-separated private subnet CIDRs")
cmd.Flags().StringVar(&opts.AvailabilityZones, "availability-zones", "", "Comma-separated availability zones (optional, auto-detected if empty)")
cmd.Flags().BoolVar(&opts.SingleNatGateway, "single-nat-gateway", true, "Use single NAT gateway (true=cost savings, false=HA per-AZ)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don't advertise --availability-zones as optional in the shared helper.

AddFlags now feeds cluster deploy too, where --availability-zones is required. Reusing the current description will make rosactl cluster deploy --help contradict the command's actual contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/commands/clustervpc/create.go` around lines 26 - 34, The flag
description in AddFlags incorrectly advertises "--availability-zones" as
optional/auto-detected which is not true for all callers; update the StringVar
call for opts.AvailabilityZones in AddFlags to use a neutral description like
"Comma-separated availability zones" (remove "(optional, auto-detected if
empty)"), and let specific commands that require the flag override or mark it
required in their own command setup rather than relying on this shared helper.

Comment on lines +78 to 93
func RunCreate(ctx context.Context, opts *CreateOptions) error {
if opts.Region == "" {
return fmt.Errorf("--region is required")
}

fmt.Printf("🌐 Creating cluster VPC resources for: %s\n", opts.ClusterName)
fmt.Printf(" Region: %s\n", opts.Region)
fmt.Printf(" VPC CIDR: %s\n", opts.VpcCidr)
fmt.Printf(" Single NAT Gateway: %t\n", opts.SingleNatGateway)
fmt.Println()

// Load AWS config
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(opts.region))
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(opts.Region))
if err != nil {
return fmt.Errorf("failed to load AWS config: %w", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate ClusterName before the VPC workflow starts.

internal/commands/clusteriam/create.go:67-70 rejects invalid names up front, but RunCreate here only checks Region. In the parallel deploy flow, that can turn one bad cluster name into inconsistent IAM/VPC behavior instead of one deterministic validation failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/commands/clustervpc/create.go` around lines 78 - 93, RunCreate
currently only validates Region; add the same upfront ClusterName validation
used in internal/commands/clusteriam/create.go so invalid cluster names are
rejected before any VPC work begins. In RunCreate (function RunCreate,
CreateOptions struct usage), check opts.ClusterName at the top and call the
shared validation helper (or extract the validation logic from
clusteriam/create.go) to return an error when the name is invalid, ensuring the
VPC workflow never starts for bad names.

Comment on lines +309 to +313
deployCmd := exec.CommandContext(ctx, binaryPath, "cluster", "deploy", testCluster,
"--region", awsRegion,
"--oidc-issuer-url", "https://test-oidc.s3.amazonaws.com",
"--availability-zones", "us-east-1a,us-east-1b,us-east-1c",
"--single-nat-gateway=false",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Derive the AZ list from awsRegion.

This spec reads AWS_REGION from the environment, but the new command still hardcodes us-east-1a/b/c. Any non-default region will fail for the wrong reason.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/localstack/localstack_test.go` around lines 309 - 313, The test
hardcodes availability zones "us-east-1a,us-east-1b,us-east-1c" when
constructing deployCmd (exec.CommandContext with arguments "cluster", "deploy",
testCluster, etc.), which breaks when AWS_REGION is not us-east-1; change the AZ
argument to be derived from the awsRegion variable by programmatically
constructing the three AZ strings (e.g., awsRegion+"a", awsRegion+"b",
awsRegion+"c") joined with commas and pass that computed string for
"--availability-zones" so the test uses the correct AZs for the configured
region.

Comment on lines +323 to +359
// Both CloudFormation stacks run in parallel; allow enough time for both.
Eventually(deploySession, 120*time.Second).Should(gexec.Exit())

output := string(deploySession.Out.Contents())
errOutput := string(deploySession.Err.Contents())
GinkgoWriter.Printf("\nCLI Deploy Output:\n%s\n", output)
if errOutput != "" {
GinkgoWriter.Printf("\nCLI Deploy Errors:\n%s\n", errOutput)
}

Expect(output).To(ContainSubstring("Deploying cluster resources"))

// VPC does not require a network round-trip for thumbprint fetching, so
// it should always be attempted. If it is absent the IAM thumbprint fetch
// failed and its context cancellation aborted VPC before it started —
// acceptable in offline environments.
By("Verifying VPC stack was created")
vpcResult, err := cfnClient.DescribeStacks(ctx, &cloudformation.DescribeStacksInput{
StackName: aws.String(vpcStackName),
})
if err != nil && strings.Contains(err.Error(), "does not exist") {
Skip("VPC stack not created — IAM thumbprint failure likely cancelled VPC via context (expected in offline environments)")
}
Expect(err).NotTo(HaveOccurred(), "VPC stack should exist after deploy command")
Expect(vpcResult.Stacks).To(HaveLen(1))
vpcStatus := string(vpcResult.Stacks[0].StackStatus)
GinkgoWriter.Printf("VPC Stack status: %s\n", vpcStatus)
// Accept CREATE_COMPLETE or CREATE_FAILED (LocalStack NAT Gateway limitation)
Expect(vpcStatus).To(Or(Equal("CREATE_COMPLETE"), Equal("CREATE_FAILED")))

By("Verifying IAM stack was created")
iamResult, err := cfnClient.DescribeStacks(ctx, &cloudformation.DescribeStacksInput{
StackName: aws.String(iamStackName),
})
if err != nil && strings.Contains(err.Error(), "does not exist") {
Skip("IAM stack not created — OIDC thumbprint fetch likely failed in LocalStack (expected in offline environments)")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't convert any early deploy failure into a skip.

errOutput is only logged, and both missing-stack branches call Skip(...) on a plain "does not exist" result. That means unrelated regressions in cluster deploy can be reported as expected offline behavior instead of failing the spec. Please gate the skip on the known thumbprint/TLS failure message and fail everything else.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/localstack/localstack_test.go` around lines 323 - 359, The current
DescribeStacks error handling for the VPC and IAM checks (calls to
cfnClient.DescribeStacks around vpcStackName and iamStackName) treats any "does
not exist" error as an expected offline skip; change this to only Skip when the
error text clearly indicates the known thumbprint/TLS failure (e.g.,
error.Error() contains "thumbprint" or "certificate" or the specific TLS/OIDC
thumbprint failure string you use elsewhere), and for any other error call
Expect(err).NotTo(HaveOccurred(), ...) so unrelated deploy failures fail the
spec; update both the VPC and IAM DescribeStacks error branches to perform this
specific-message check before skipping.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 1, 2026
@openshift-ci

openshift-ci Bot commented Apr 1, 2026

Copy link
Copy Markdown

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants