-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OCM-9995 | feat: Adding rosa quickstart POC
- Loading branch information
den-rgb
committed
Aug 27, 2024
1 parent
b037752
commit 3cdef30
Showing
9 changed files
with
561 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package bootstrap | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
quickstartcluster "github.com/openshift/rosa/cmd/bootstrap/quick-start-cluster" | ||
) | ||
|
||
func NewRosaBootstrapCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "bootstrap", | ||
Short: "bootstrap AWS resource", | ||
Long: "bootstrap AWS resource", | ||
Args: cobra.NoArgs, | ||
Hidden: true, | ||
} | ||
cmd.AddCommand(quickstartcluster.Cmd) | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package quickstartcluster | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/openshift/rosa/pkg/rosa" | ||
"github.com/spf13/cobra" | ||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/aws/aws-sdk-go-v2/service/cloudformation" | ||
cfTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" | ||
"github.com/openshift/rosa/pkg/output" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
var Cmd = makeCmd() | ||
|
||
func makeCmd() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "quick-start-cluster", | ||
Short: "Bootstrap quick start cluster", | ||
Long: "Bootstrap quick start hcp cluster.", | ||
Example: ` # Create a quick-start-cluster | ||
rosa bootstrap quick-start-cluster`, | ||
Run: run, | ||
Args: cobra.NoArgs, | ||
} | ||
} | ||
|
||
var args struct { | ||
namePrefix string | ||
vpcCidr string | ||
availabilityZonesCount int | ||
tags map[string]string | ||
} | ||
|
||
func init() { | ||
flags := Cmd.Flags() | ||
flags.StringVar( | ||
&args.namePrefix, | ||
"name", | ||
"dmoskale-example", | ||
"Prefix for naming resources", | ||
) | ||
flags.StringVar( | ||
&args.vpcCidr, | ||
"vpc-cidr", | ||
"10.0.0.0/16", | ||
"CIDR block for the VPC", | ||
) | ||
flags.IntVar( | ||
&args.availabilityZonesCount, | ||
"availability-zones-count", | ||
2, | ||
"Number of availability zones", | ||
) | ||
flags.StringToStringVar( | ||
&args.tags, | ||
"tags", | ||
nil, | ||
"Additional tags", | ||
) | ||
output.AddFlag(Cmd) | ||
} | ||
|
||
|
||
func run(cmd *cobra.Command, _ []string) { | ||
r := rosa.NewRuntime().WithAWS().WithOCM() | ||
defer r.Cleanup() | ||
|
||
err := createVPCStack("cmd/bootstrap/quick-start-cluster/vpc.yaml", args.namePrefix, args.vpcCidr, args.availabilityZonesCount, args.tags) | ||
if err != nil { | ||
r.Reporter.Errorf(err.Error()) | ||
os.Exit(1) | ||
} | ||
|
||
r.Reporter.Infof("VPC created") | ||
r.Reporter.Infof("To view all created resource stacks, run `aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE`") | ||
r.Reporter.Infof("To delete all created resource stacks, run `aws cloudformation delete-stack --stack-name %s`", args.namePrefix) | ||
} | ||
|
||
// createVPCStack creates a CloudFormation stack to deploy a VPC | ||
func createVPCStack(templateFile, namePrefix, vpcCidr string, availabilityZonesCount int, tags map[string]string) error { | ||
// Load the AWS configuration | ||
// Initialize logger | ||
logger := logrus.New() | ||
logger.SetLevel(logrus.DebugLevel) | ||
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("eu-west-3")) | ||
if err != nil { | ||
return fmt.Errorf("unable to load SDK config, %v", err) | ||
} | ||
|
||
// Read the CloudFormation template | ||
logger.Infof("Reading CloudFormation template from file: %s", templateFile) | ||
templateBody, err := os.ReadFile(templateFile) | ||
if err != nil { | ||
return fmt.Errorf("unable to read template file, %v", err) | ||
} | ||
|
||
// Convert tags map to comma-delimited string | ||
var tagsList []string | ||
for key, value := range tags { | ||
tagsList = append(tagsList, fmt.Sprintf("%s=%s", key, value)) | ||
} | ||
tagsString := strings.Join(tagsList, ",") | ||
logger.Infof("Tags: %s", tagsString) | ||
|
||
// Create a CloudFormation client | ||
logger.Info("Creating CloudFormation client") | ||
cfClient := cloudformation.NewFromConfig(cfg) | ||
|
||
// Create the stack | ||
logger.Info("Creating CloudFormation stack") | ||
_, err = cfClient.CreateStack(context.TODO(), &cloudformation.CreateStackInput{ | ||
StackName: aws.String(namePrefix), | ||
TemplateBody: aws.String(string(templateBody)), | ||
Parameters: []cfTypes.Parameter{ | ||
{ | ||
ParameterKey: aws.String("NamePrefix"), | ||
ParameterValue: aws.String(namePrefix), | ||
}, | ||
{ | ||
ParameterKey: aws.String("VpcCidr"), | ||
ParameterValue: aws.String(vpcCidr), | ||
}, | ||
{ | ||
ParameterKey: aws.String("AvailabilityZonesCount"), | ||
ParameterValue: aws.String(fmt.Sprintf("%d", availabilityZonesCount)), | ||
}, | ||
{ | ||
ParameterKey: aws.String("Tags"), | ||
ParameterValue: aws.String(fmt.Sprintf("%v", tagsString)), | ||
}, | ||
}, | ||
Capabilities: []cfTypes.Capability{ | ||
cfTypes.CapabilityCapabilityIam, | ||
cfTypes.CapabilityCapabilityNamedIam, | ||
}, | ||
}) | ||
if err != nil { | ||
logger.Errorf("Failed to create CloudFormation stack: %v", err) | ||
return fmt.Errorf("failed to create stack, %v", err) | ||
} | ||
|
||
// Fetch and log stack events periodically | ||
go func() { | ||
ticker := time.NewTicker(10 * time.Second) | ||
defer ticker.Stop() | ||
for { | ||
<-ticker.C | ||
logStackEvents(cfClient, namePrefix, logger) | ||
} | ||
}() | ||
|
||
// Wait until the stack is created | ||
waiter := cloudformation.NewStackCreateCompleteWaiter(cfClient) | ||
err = waiter.Wait(context.TODO(), &cloudformation.DescribeStacksInput{ | ||
StackName: aws.String(namePrefix), | ||
}, 10*time.Minute, func(o *cloudformation.StackCreateCompleteWaiterOptions) { | ||
o.MinDelay = 30 * time.Second | ||
o.MaxDelay = 1 * time.Minute | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("failed to wait for stack creation, %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
const ( | ||
ColorReset = "\033[0m" | ||
ColorRed = "\033[31m" | ||
ColorGreen = "\033[32m" | ||
ColorYellow = "\033[33m" | ||
) | ||
|
||
// logStackEvents fetches and logs stack events | ||
func logStackEvents(cfClient *cloudformation.Client, stackName string, logger *logrus.Logger) { | ||
events, err := cfClient.DescribeStackEvents(context.TODO(), &cloudformation.DescribeStackEventsInput{ | ||
StackName: aws.String(stackName), | ||
}) | ||
if err != nil { | ||
logger.Errorf("Failed to describe stack events: %v", err) | ||
return | ||
} | ||
|
||
// Group events by resource and keep the latest event for each resource | ||
latestEvents := make(map[string]cfTypes.StackEvent) | ||
for _, event := range events.StackEvents { | ||
resource := aws.ToString(event.LogicalResourceId) | ||
if existingEvent, exists := latestEvents[resource]; !exists || event.Timestamp.After(*existingEvent.Timestamp) { | ||
latestEvents[resource] = event | ||
} | ||
} | ||
|
||
logger.Info("---------------------------------------------") | ||
// Log the latest event for each resource with color | ||
for resource, event := range latestEvents { | ||
statusColor := getStatusColor(event.ResourceStatus) | ||
logger.Infof("Resource: %s, Status: %s%s%s, Reason: %s", resource, statusColor, event.ResourceStatus, ColorReset, aws.ToString(event.ResourceStatusReason)) | ||
} | ||
} | ||
|
||
func getStatusColor(status cfTypes.ResourceStatus) string { | ||
switch status { | ||
case cfTypes.ResourceStatusCreateComplete, cfTypes.ResourceStatusUpdateComplete: | ||
return ColorGreen | ||
case cfTypes.ResourceStatusCreateFailed, cfTypes.ResourceStatusDeleteFailed, cfTypes.ResourceStatusUpdateFailed: | ||
return ColorRed | ||
default: | ||
return ColorYellow | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Description: Elastic IPs | ||
|
||
Parameters: | ||
NamePrefix: | ||
Type: String | ||
Description: Prefix for naming resources | ||
AvailabilityZonesCount: | ||
Type: Number | ||
Description: Number of availability zones | ||
Tags: | ||
Type: CommaDelimitedList | ||
Description: Tags for resources | ||
|
||
Conditions: | ||
HasTags: !Not [!Equals [!Join [",", !Ref Tags], ""]] | ||
|
||
Resources: | ||
ElasticIP1: | ||
Type: AWS::EC2::EIP | ||
Properties: | ||
Domain: vpc | ||
Tags: | ||
- Key: Name | ||
Value: !Sub "${NamePrefix}-eip-1" | ||
- !If | ||
- HasTags | ||
- Key: !Select [0, !Ref Tags] | ||
Value: !Select [1, !Ref Tags] | ||
- Key: Empty | ||
Value: Empty | ||
|
||
ElasticIP2: | ||
Type: AWS::EC2::EIP | ||
Properties: | ||
Domain: vpc | ||
Tags: | ||
- Key: Name | ||
Value: !Sub "${NamePrefix}-eip-2" | ||
- !If | ||
- HasTags | ||
- Key: !Select [0, !Ref Tags] | ||
Value: !Select [1, !Ref Tags] | ||
- Key: Empty | ||
Value: Empty | ||
|
||
Outputs: | ||
EIP1AllocationId: | ||
Description: Allocation ID for ElasticIP1 | ||
Value: !GetAtt ElasticIP1.AllocationId | ||
Export: | ||
Name: !Sub "${NamePrefix}-EIP1-AllocationId" | ||
|
||
EIP2AllocationId: | ||
Description: Allocation ID for ElasticIP2 | ||
Value: !GetAtt ElasticIP2.AllocationId | ||
Export: | ||
Name: !Sub "${NamePrefix}-EIP2-AllocationId" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Description: NAT Gateways | ||
|
||
Parameters: | ||
VpcId: | ||
Type: String | ||
Description: VPC ID | ||
PublicSubnets: | ||
Type: CommaDelimitedList | ||
Description: Public Subnet IDs | ||
ElasticIPs: | ||
Type: CommaDelimitedList | ||
Description: Comma-delimited list of Elastic IP allocation IDs | ||
NamePrefix: | ||
Type: String | ||
Description: Prefix for naming resources | ||
AvailabilityZonesCount: | ||
Type: Number | ||
Description: Number of availability zones | ||
Tags: | ||
Type: CommaDelimitedList | ||
Description: Tags for resources | ||
|
||
Conditions: | ||
HasTags: !Not [!Equals [!Join [",", !Ref Tags], ""]] | ||
|
||
Resources: | ||
NatGateway: | ||
Type: 'AWS::EC2::NatGateway' | ||
Properties: | ||
AllocationId: !Select [0, !Ref ElasticIPs] | ||
SubnetId: !Select [0, !Ref PublicSubnets] | ||
Tags: | ||
- Key: Name | ||
Value: !Sub "${NamePrefix}-nat-1" | ||
- !If | ||
- HasTags | ||
- Key: !Select [0, !Ref Tags] | ||
Value: !Select [1, !Ref Tags] | ||
- Key: Empty | ||
Value: Empty | ||
|
||
Outputs: | ||
NatGatewayIds: | ||
Description: The NAT Gateway IDs | ||
Value: !Ref NatGateway | ||
|
39 changes: 39 additions & 0 deletions
39
cmd/bootstrap/quick-start-cluster/private_route_tables.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Description: Private Route Tables | ||
|
||
Parameters: | ||
VpcId: | ||
Type: String | ||
Description: VPC ID | ||
NamePrefix: | ||
Type: String | ||
Description: Prefix for naming resources | ||
AvailabilityZonesCount: | ||
Type: Number | ||
Description: Number of availability zones | ||
Tags: | ||
Type: CommaDelimitedList | ||
Description: Tags for resources | ||
|
||
Conditions: | ||
HasTags: !Not [!Equals [!Join [",", !Ref Tags], ""]] | ||
|
||
Resources: | ||
PrivateRouteTable: | ||
Type: 'AWS::EC2::RouteTable' | ||
Properties: | ||
VpcId: !Ref VpcId | ||
Tags: | ||
- Key: Name | ||
Value: !Sub "${NamePrefix}-private-rtb-1" | ||
- !If | ||
- HasTags | ||
- Key: !Select [0, !Ref Tags] | ||
Value: !Select [1, !Ref Tags] | ||
- Key: Empty | ||
Value: Empty | ||
|
||
Outputs: | ||
RouteTableIds: | ||
Description: The Private Route Table IDs | ||
Value: !Ref PrivateRouteTable |
Oops, something went wrong.