Skip to content

Commit 9106ae5

Browse files
authored
Merge pull request #5 from gitpod-io/kylos101/tolerate-duplicate-subnets
Tolerate two subnets and Introduce Cleanup command
2 parents ab3e7cf + b9ffb31 commit 9106ae5

File tree

5 files changed

+262
-85
lines changed

5 files changed

+262
-85
lines changed

gitpod-network-check/README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ A CLI to check if your network setup is suitable for the installation of Gitpod.
1717
```
1818

1919
2. Set up AWS credentials
20-
20+
2121
`gitpod-network-check` needs access to the AWS account you are planning to use to deploy Gitpod in. Much like AWS CLI, `gitpod-network-check` uses the available AWS profile in your terminal to authenticate against the account. This means that you can rely on any locally available [AWS profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) or just set the right environment variables in your terminal for the CLI to use:
2222
```
2323
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
@@ -44,7 +44,7 @@ A CLI to check if your network setup is suitable for the installation of Gitpod.
4444
4545
To start the diagnosis, the the command: `./gitpod-network-check diagnose`
4646

47-
```
47+
```console
4848
./gitpod-network-check diagnose
4949
INFO[0000] ✅ Main Subnets are valid
5050
INFO[0000] ✅ Pod Subnets are valid
@@ -77,3 +77,18 @@ A CLI to check if your network setup is suitable for the installation of Gitpod.
7777
INFO[0191] ✅ S3 is available
7878
```
7979

80+
3. Clean up after network diagnosis
81+
82+
Dianosis is designed to do clean-up before it finishes. However, if the process terminates unexpectedly, you may clean-up AWS resources it creates like so:
83+
84+
```console
85+
./gitpod-network-check clean
86+
INFO[0000] ✅ Main Subnets are valid
87+
INFO[0000] ✅ Pod Subnets are valid
88+
INFO[0000] ✅ Instances terminated
89+
INFO[0000] Cleaning up: Waiting for 2 minutes so network interfaces are deleted
90+
INFO[0121] ✅ Role 'GitpodNetworkCheck' deleted
91+
INFO[0121] ✅ Instance profile deleted
92+
INFO[0122] ✅ Security group 'sg-0a6119dcb6a564fc1' deleted
93+
INFO[0122] ✅ Security group 'sg-07373362953212e54' deleted
94+
```

gitpod-network-check/cmd/checks.go

Lines changed: 41 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import (
55
"encoding/base64"
66
"errors"
77
"fmt"
8+
"slices"
89
"sort"
910
"strings"
1011
"time"
1112

1213
"github.com/aws/aws-sdk-go-v2/aws"
13-
"github.com/aws/aws-sdk-go-v2/config"
1414
"github.com/aws/aws-sdk-go-v2/service/ec2"
1515
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
1616
"github.com/aws/aws-sdk-go-v2/service/iam"
@@ -23,28 +23,6 @@ import (
2323
"k8s.io/apimachinery/pkg/util/wait"
2424
)
2525

26-
const gitpodRoleName = "GitpodNetworkCheck"
27-
const gitpodInstanceProfile = "GitpodNetworkCheck"
28-
29-
var networkCheckTag = []iam_types.Tag{
30-
{
31-
Key: aws.String("gitpod.io/network-check"),
32-
Value: aws.String("true"),
33-
},
34-
}
35-
36-
func initAwsConfig(ctx context.Context, region string) (aws.Config, error) {
37-
return config.LoadDefaultConfig(ctx, config.WithRegion(region))
38-
}
39-
40-
// this will be useful when we are cleaning up things at the end
41-
var (
42-
InstanceIds []string
43-
SecurityGroups []string
44-
Roles []string
45-
InstanceProfile string
46-
)
47-
4826
var checkCommand = &cobra.Command{ // nolint:gochecknoglobals
4927
PersistentPreRunE: validateSubnets,
5028
Use: "diagnose",
@@ -71,13 +49,21 @@ var checkCommand = &cobra.Command{ // nolint:gochecknoglobals
7149
return fmt.Errorf("❌ error creating IAM role and attaching policy: %v", err)
7250
}
7351
Roles = append(Roles, *role.RoleName)
52+
log.Info("✅ IAM role created and policy attached")
7453

7554
instanceProfile, err := createInstanceProfileAndAttachRole(cmd.Context(), iamClient, *role.RoleName)
7655
if err != nil {
7756
return fmt.Errorf("❌ failed to create instance profile: %v", err)
7857
}
7958
InstanceProfile = aws.ToString(instanceProfile.InstanceProfileName)
8059

60+
allSubnets := slices.Concat(networkConfig.MainSubnets, networkConfig.PodSubnets)
61+
slices.Sort(allSubnets)
62+
distinctSubnets := slices.Compact(allSubnets)
63+
if len(distinctSubnets) < len(allSubnets) {
64+
log.Infof("ℹ️ Found duplicate subnets. We'll test each subnet '%v' only once.", distinctSubnets)
65+
}
66+
8167
log.Infof("ℹ️ Launching EC2 instances in Main subnets")
8268
mainInstanceIds, err := launchInstances(cmd.Context(), ec2Client, networkConfig.MainSubnets, instanceProfile.Arn)
8369
if err != nil {
@@ -270,9 +256,13 @@ func validateSubnets(cmd *cobra.Command, args []string) error {
270256
func launchInstances(ctx context.Context, ec2Client *ec2.Client, subnets []string, profileArn *string) ([]string, error) {
271257
var instanceIds []string
272258
for _, subnet := range subnets {
259+
if _, ok := Subnets[subnet]; ok {
260+
log.Warnf("An EC2 instance was already created for subnet '%v', skipping", subnet)
261+
continue
262+
}
273263
secGroup, err := createSecurityGroups(ctx, ec2Client, subnet)
274264
if err != nil {
275-
return nil, fmt.Errorf("❌ failed to create security group: %v", err)
265+
return nil, fmt.Errorf("❌ failed to create security group for subnet '%v': %v", subnet, err)
276266
}
277267
SecurityGroups = append(SecurityGroups, secGroup)
278268
instanceId, err := launchInstanceInSubnet(ctx, ec2Client, subnet, secGroup, profileArn)
@@ -281,6 +271,10 @@ func launchInstances(ctx context.Context, ec2Client *ec2.Client, subnets []strin
281271
}
282272

283273
instanceIds = append(instanceIds, instanceId)
274+
if Subnets == nil {
275+
Subnets = make(map[string]bool)
276+
}
277+
Subnets[subnet] = true
284278
}
285279

286280
return instanceIds, nil
@@ -312,6 +306,17 @@ func launchInstanceInSubnet(ctx context.Context, ec2Client *ec2.Client, subnetID
312306
IamInstanceProfile: &types.IamInstanceProfileSpecification{
313307
Arn: instanceProfileName,
314308
},
309+
TagSpecifications: []types.TagSpecification{
310+
{
311+
ResourceType: types.ResourceTypeInstance,
312+
Tags: []types.Tag{
313+
{
314+
Key: aws.String("gitpod.io/network-check"),
315+
Value: aws.String("true"),
316+
},
317+
},
318+
},
319+
},
315320
}
316321

317322
var result *ec2.RunInstancesOutput
@@ -450,6 +455,17 @@ func createSecurityGroups(ctx context.Context, svc *ec2.Client, subnetID string)
450455
Description: aws.String("EC2 security group allowing all HTTPS outgoing traffic"),
451456
GroupName: aws.String(fmt.Sprintf("EC2-security-group-nc-%s", subnetID)),
452457
VpcId: vpcID,
458+
TagSpecifications: []types.TagSpecification{
459+
{
460+
ResourceType: types.ResourceTypeSecurityGroup,
461+
Tags: []types.Tag{
462+
{
463+
Key: aws.String("gitpod.io/network-check"),
464+
Value: aws.String("true"),
465+
},
466+
},
467+
},
468+
},
453469
}
454470

455471
createSGOutput, err := svc.CreateSecurityGroup(ctx, createSGInput)
@@ -458,7 +474,7 @@ func createSecurityGroups(ctx context.Context, svc *ec2.Client, subnetID string)
458474
}
459475

460476
sgID := createSGOutput.GroupId
461-
log.Infof("ℹ️ Created security group with ID: %s", *sgID)
477+
log.Infof("ℹ️ Created security group with ID: %s", *sgID)
462478

463479
// Authorize HTTPS outbound traffic
464480
authorizeEgressInput := &ec2.AuthorizeSecurityGroupEgressInput{
@@ -486,64 +502,6 @@ func createSecurityGroups(ctx context.Context, svc *ec2.Client, subnetID string)
486502
return *sgID, nil
487503
}
488504

489-
func cleanup(ctx context.Context, svc *ec2.Client, iamsvc *iam.Client) {
490-
if len(InstanceIds) > 0 {
491-
_, err := svc.TerminateInstances(ctx, &ec2.TerminateInstancesInput{
492-
InstanceIds: InstanceIds,
493-
})
494-
if err != nil {
495-
log.WithError(err).WithField("instanceIds", InstanceIds).Warnf("Failed to cleanup instances, please cleanup manually")
496-
}
497-
}
498-
if len(Roles) > 0 {
499-
for _, role := range Roles {
500-
_, err := iamsvc.DetachRolePolicy(ctx, &iam.DetachRolePolicyInput{PolicyArn: aws.String("arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"), RoleName: aws.String(role)})
501-
if err != nil {
502-
log.WithError(err).WithField("rolename", role).Warnf("Failed to cleanup role, please cleanup manually")
503-
}
504-
505-
_, err = iamsvc.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{
506-
RoleName: aws.String(role),
507-
InstanceProfileName: aws.String(InstanceProfile),
508-
})
509-
if err != nil {
510-
log.WithError(err).WithField("roleName", role).WithField("profileName", InstanceProfile).Warnf("Failed to remove role from instance profile")
511-
}
512-
513-
_, err = iamsvc.DeleteRole(ctx, &iam.DeleteRoleInput{RoleName: aws.String(role)})
514-
if err != nil {
515-
log.WithError(err).WithField("rolename", role).Warnf("Failed to cleanup role, please cleanup manaullay")
516-
}
517-
}
518-
519-
_, err := iamsvc.DeleteInstanceProfile(ctx, &iam.DeleteInstanceProfileInput{
520-
InstanceProfileName: aws.String(InstanceProfile),
521-
})
522-
523-
if err != nil {
524-
log.WithError(err).WithField("instanceProfile", InstanceProfile).Warnf("Failed to clean up instance profile, please cleanup manually")
525-
}
526-
}
527-
528-
log.Info("Cleaning up: Waiting for 1 minute so network interfaces are deleted")
529-
time.Sleep(time.Minute)
530-
531-
if len(SecurityGroups) > 0 {
532-
for _, sg := range SecurityGroups {
533-
deleteSGInput := &ec2.DeleteSecurityGroupInput{
534-
GroupId: aws.String(sg),
535-
}
536-
537-
_, err := svc.DeleteSecurityGroup(ctx, deleteSGInput)
538-
if err != nil {
539-
log.WithError(err).WithField("securityGroup", sg).Warnf("Failed to clean up security group, please cleanup manually")
540-
}
541-
542-
}
543-
544-
}
545-
}
546-
547505
func createIAMRoleAndAttachPolicy(ctx context.Context, svc *iam.Client) (*iam_types.Role, error) {
548506
// Define the trust relationship
549507
trustPolicy := `{

gitpod-network-check/cmd/cleanup.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmd
2+
3+
import (
4+
"github.com/aws/aws-sdk-go-v2/service/ec2"
5+
"github.com/aws/aws-sdk-go-v2/service/iam"
6+
"github.com/spf13/cobra"
7+
)
8+
9+
var cleanCommand = &cobra.Command{ // nolint:gochecknoglobals
10+
PersistentPreRunE: validateSubnets,
11+
Use: "clean",
12+
Short: "Explicitly cleans up after the network check diagnosis",
13+
SilenceUsage: false,
14+
RunE: func(cmd *cobra.Command, args []string) error {
15+
cfg, err := initAwsConfig(cmd.Context(), networkConfig.AwsRegion)
16+
if err != nil {
17+
return err
18+
}
19+
20+
ec2Client := ec2.NewFromConfig(cfg)
21+
iamClient := iam.NewFromConfig(cfg)
22+
23+
cleanup(cmd.Context(), ec2Client, iamClient)
24+
return nil
25+
},
26+
}

0 commit comments

Comments
 (0)