From 38c847e4d7b72ec2535e5420e7cb8aa0d47df4aa Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 14 May 2026 18:06:33 -0500 Subject: [PATCH] feat: run IAM and network provisioning concurrently in SpinUpWorkflow IAM role creation and VPC/subnet creation are independent, so dispatch both child workflows before blocking on either. EKS provisioning still waits for both to complete. Saga compensation is registered for whichever steps succeed before returning any error. Co-Authored-By: Claude Sonnet 4.6 --- internal/workflows/infrastructure.go | 105 +++++++++++++++++---------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/internal/workflows/infrastructure.go b/internal/workflows/infrastructure.go index 9cad35c..f6ef9f9 100644 --- a/internal/workflows/infrastructure.go +++ b/internal/workflows/infrastructure.go @@ -92,58 +92,85 @@ func SpinUpWorkflow(ctx workflow.Context, input SpinUpInput) (err error) { clusterRoleARN := input.ClusterRoleARN nodeRoleARN := input.NodeRoleARN + // Start network unconditionally; start IAM concurrently when role ARNs are not pre-supplied. + networkFuture := workflow.ExecuteChildWorkflow(ctx, SpinUpNetworkWorkflow, SpinUpNetworkInput{ + Region: input.Region, + VpcCIDR: input.VpcCIDR, + Subnets: input.Subnets, + Environment: input.Environment, + Team: input.Team, + }) + + var iamFuture workflow.ChildWorkflowFuture if clusterRoleARN == "" { - var iamOut SpinUpEKSIAMOutput - if err = workflow.ExecuteChildWorkflow(ctx, SpinUpIAMWorkflow, SpinUpEKSIAMInput{ + iamFuture = workflow.ExecuteChildWorkflow(ctx, SpinUpIAMWorkflow, SpinUpEKSIAMInput{ Region: input.Region, ClusterName: input.ClusterName, Environment: input.Environment, Team: input.Team, - }).Get(ctx, &iamOut); err != nil { - return + }) + } + + // Collect IAM result (if started). + var iamErr error + if iamFuture != nil { + var iamOut SpinUpEKSIAMOutput + iamErr = iamFuture.Get(ctx, &iamOut) + if iamErr == nil { + clusterRoleARN = iamOut.ClusterRoleARN + nodeRoleARN = iamOut.NodeRoleARN + logger.Info( + "IAM roles created", + "clusterRole", + iamOut.ClusterRoleName, + "nodeRole", + iamOut.NodeRoleName, + ) + s.AddCompensation(func(cctx workflow.Context) { + if err := workflow.ExecuteChildWorkflow( + cctx, + SpinDownIAMWorkflow, + SpinDownEKSIAMInput{ + ClusterRoleName: iamOut.ClusterRoleName, + NodeRoleName: iamOut.NodeRoleName, + }, + ).Get(cctx, nil); err != nil { + logger.Error("saga: failed to spin down IAM roles", "error", err) + } + }) } - logger.Info( - "IAM roles created", - "clusterRole", - iamOut.ClusterRoleName, - "nodeRole", - iamOut.NodeRoleName, - ) + } + + // Always wait for network before returning — prevents orphaned resources if IAM failed. + var network SpinUpNetworkOutput + netErr := networkFuture.Get(ctx, &network) + if netErr == nil { + logger.Info("network ready", "vpcID", network.VpcID, "subnetCount", len(network.SubnetIDs)) s.AddCompensation(func(cctx workflow.Context) { - if err := workflow.ExecuteChildWorkflow( - cctx, - SpinDownIAMWorkflow, - SpinDownEKSIAMInput{ - ClusterRoleName: iamOut.ClusterRoleName, - NodeRoleName: iamOut.NodeRoleName, - }, - ).Get(cctx, nil); err != nil { - logger.Error("saga: failed to spin down IAM roles", "error", err) + if err := workflow.ExecuteChildWorkflow(cctx, SpinDownNetworkWorkflow, SpinDownNetworkInput{ + Region: input.Region, + VpcID: network.VpcID, + }). + Get(cctx, nil); err != nil { + logger.Error( + "saga: failed to spin down network", + "vpcID", + network.VpcID, + "error", + err, + ) } }) - clusterRoleARN = iamOut.ClusterRoleARN - nodeRoleARN = iamOut.NodeRoleARN } - var network SpinUpNetworkOutput - if err = workflow.ExecuteChildWorkflow(ctx, SpinUpNetworkWorkflow, SpinUpNetworkInput{ - Region: input.Region, - VpcCIDR: input.VpcCIDR, - Subnets: input.Subnets, - Environment: input.Environment, - Team: input.Team, - }).Get(ctx, &network); err != nil { + if iamErr != nil { + err = iamErr + return + } + if netErr != nil { + err = netErr return } - logger.Info("network ready", "vpcID", network.VpcID, "subnetCount", len(network.SubnetIDs)) - s.AddCompensation(func(cctx workflow.Context) { - if err := workflow.ExecuteChildWorkflow(cctx, SpinDownNetworkWorkflow, SpinDownNetworkInput{ - Region: input.Region, - VpcID: network.VpcID, - }).Get(cctx, nil); err != nil { - logger.Error("saga: failed to spin down network", "vpcID", network.VpcID, "error", err) - } - }) if err = workflow.ExecuteChildWorkflow(ctx, SpinUpEKSWorkflow, SpinUpEKSInput{ Region: input.Region,