From dfe87e92b1042dd488d8a640fd28f7192ea3f7ff Mon Sep 17 00:00:00 2001 From: zhewang Date: Fri, 21 Jun 2024 15:57:23 +0800 Subject: [PATCH] OCM-8979 | test: Prepare shared VPC resources --- go.mod | 7 ++- go.sum | 14 +++-- pkg/aws/aws_client/client.go | 28 ++++----- pkg/aws/aws_client/ram.go | 32 ++++++++++ pkg/aws/aws_client/role.go | 44 +++++++++++++- pkg/aws/aws_client/route53.go | 22 +++++-- pkg/test/vpc_client/vpc.go | 107 ++++++++++++++++++++++++++++++++++ 7 files changed, 227 insertions(+), 27 deletions(-) create mode 100644 pkg/aws/aws_client/ram.go diff --git a/go.mod b/go.mod index 681e3fbd..9a8d2ca6 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/apparentlymart/go-cidr v1.1.0 - github.com/aws/aws-sdk-go-v2 v1.26.1 + github.com/aws/aws-sdk-go-v2 v1.30.0 github.com/aws/aws-sdk-go-v2/config v1.27.9 github.com/aws/aws-sdk-go-v2/credentials v1.17.9 github.com/aws/aws-sdk-go-v2/service/cloudformation v1.48.0 @@ -12,6 +12,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.24.3 github.com/aws/aws-sdk-go-v2/service/iam v1.27.1 github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 + github.com/aws/aws-sdk-go-v2/service/ram v1.26.1 github.com/aws/aws-sdk-go-v2/service/route53 v1.40.3 github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 github.com/go-jose/go-jose/v4 v4.0.2 @@ -34,8 +35,8 @@ require ( require ( github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect diff --git a/go.sum b/go.sum index 3619d26c..7b9135d6 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= -github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2 v1.30.0 h1:6qAwtzlfcTtcL8NHtbDQAqgM5s6NDipQTkPxyH/6kAA= +github.com/aws/aws-sdk-go-v2 v1.30.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= @@ -10,10 +10,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMN github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75lpnOHSBkPUZxZfGkrI3HI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 h1:SJ04WXGTwnHlWIODtC5kJzKbeuHt+OUNOgKg7nfnUGw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12/go.mod h1:FkpvXhA92gb3GE9LD6Og0pHHycTxW7xGpnEh5E7Opwo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 h1:hb5KgeYfObi5MHkSSZMEudnIvX30iB+E21evI4r6BnQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12/go.mod h1:CroKe/eWJdyfy9Vx4rljP5wTUjNJfb+fPz1uMYUhEGM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/cloudformation v1.48.0 h1:uMlYsoHdd2Gr9sDGq2ieUR5jVu7F5AqPYz6UBJmdRhY= @@ -32,6 +32,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHM github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= +github.com/aws/aws-sdk-go-v2/service/ram v1.26.1 h1:1UcUsMsHB7ZnpcUYNwBTX90hFjIZrhf8Xu00R9Vo+Kg= +github.com/aws/aws-sdk-go-v2/service/ram v1.26.1/go.mod h1:e/3wE+afnOAeolpqyg8fKAQK/kKya+ycDW62/X4vjK8= github.com/aws/aws-sdk-go-v2/service/route53 v1.40.3 h1:wr5gulbwbb8PSRMWjCROoP0TIMccpF8x5A7hEk2SjpA= github.com/aws/aws-sdk-go-v2/service/route53 v1.40.3/go.mod h1:/Gyl9xjGcjIVe80ar75YlmA8m6oFh0A4XfLciBmdS8s= github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8= diff --git a/pkg/aws/aws_client/client.go b/pkg/aws/aws_client/client.go index ff5428e6..92f8b879 100644 --- a/pkg/aws/aws_client/client.go +++ b/pkg/aws/aws_client/client.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/ram" "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/openshift-online/ocm-common/pkg/log" @@ -35,6 +36,7 @@ type AWSClient struct { KmsClient *kms.Client CloudWatchLogsClient *cloudwatchlogs.Client AWSConfig *aws.Config + RamClient *ram.Client } type AccessKeyMod struct { @@ -42,27 +44,27 @@ type AccessKeyMod struct { SecretAccessKey string `ini:"aws_secret_access_key,omitempty"` } -func CreateAWSClient(profileName string, region string) (*AWSClient, error) { +func CreateAWSClient(profileName string, region string, awsSharedCredentialFile ...string) (*AWSClient, error) { var cfg aws.Config var err error - if envCredential() { - log.LogInfo("Got AWS_ACCESS_KEY_ID env settings, going to build the config with the env") + if len(awsSharedCredentialFile) > 0 { + file := awsSharedCredentialFile[0] + log.LogInfo("Got aws shared credential file path: %s ", file) cfg, err = config.LoadDefaultConfig(context.TODO(), config.WithRegion(region), - config.WithCredentialsProvider( - credentials.NewStaticCredentialsProvider( - os.Getenv("AWS_ACCESS_KEY_ID"), - os.Getenv("AWS_SECRET_ACCESS_KEY"), - "")), + config.WithSharedCredentialsFiles([]string{file}), ) } else { - if envAwsProfile() { - file := os.Getenv("AWS_SHARED_CREDENTIALS_FILE") - log.LogInfo("Got file path: %s from env variable AWS_SHARED_CREDENTIALS_FILE\n", file) + if envCredential() { + log.LogInfo("Got AWS_ACCESS_KEY_ID env settings, going to build the config with the env") cfg, err = config.LoadDefaultConfig(context.TODO(), config.WithRegion(region), - config.WithSharedCredentialsFiles([]string{file}), + config.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider( + os.Getenv("AWS_ACCESS_KEY_ID"), + os.Getenv("AWS_SECRET_ACCESS_KEY"), + "")), ) } else { cfg, err = config.LoadDefaultConfig(context.TODO(), @@ -70,7 +72,6 @@ func CreateAWSClient(profileName string, region string) (*AWSClient, error) { config.WithSharedConfigProfile(profileName), ) } - } if err != nil { @@ -88,6 +89,7 @@ func CreateAWSClient(profileName string, region string) (*AWSClient, error) { ClientContext: context.TODO(), KmsClient: kms.NewFromConfig(cfg), AWSConfig: &cfg, + RamClient: ram.NewFromConfig(cfg), } awsClient.AccountID = awsClient.GetAWSAccountID() return awsClient, nil diff --git a/pkg/aws/aws_client/ram.go b/pkg/aws/aws_client/ram.go new file mode 100644 index 00000000..8e0e105e --- /dev/null +++ b/pkg/aws/aws_client/ram.go @@ -0,0 +1,32 @@ +package aws_client + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/service/ram" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (awsClient AWSClient) CreateResourceShare(resourceShareName string, resourceArns []string, principles []string) (*ram.CreateResourceShareOutput, error) { + input := &ram.CreateResourceShareInput{ + Name: &resourceShareName, + ResourceArns: resourceArns, + Principals: principles, + } + + resp, err := awsClient.RamClient.CreateResourceShare(context.TODO(), input) + if err != nil { + log.LogError("Create resource share failed with name %s: %s", resourceShareName, err.Error()) + } else { + log.LogInfo("Create resource share succeed with name %s", resourceShareName) + } + return resp, err +} + +func (awsClient AWSClient) DeleteResourceShare(resourceShareArn string) error { + input := &ram.DeleteResourceShareInput{ + ResourceShareArn: &resourceShareArn, + } + + _, err := awsClient.RamClient.DeleteResourceShare(context.TODO(), input) + return err +} diff --git a/pkg/aws/aws_client/role.go b/pkg/aws/aws_client/role.go index 03d1e8ab..9892360b 100644 --- a/pkg/aws/aws_client/role.go +++ b/pkg/aws/aws_client/role.go @@ -316,4 +316,46 @@ func (client *AWSClient) UntagRole(roleName string, tagKeys []string) error { } _, err := client.IamClient.UntagRole(context.TODO(), input) return err -} \ No newline at end of file +} + +func (client *AWSClient) CreateRoleForSharedVPC(roleName, installerRoleArn string, ingressOperatorRoleArn string) (types.Role, error) { + statement := map[string]interface{}{ + "Sid": "Statement1", + "Effect": "Allow", + "Principal": map[string]interface{}{ + "AWS": []string{installerRoleArn, ingressOperatorRoleArn}, + }, + "Action": "sts:AssumeRole", + } + + assumeRolePolicyDocument, err := completeRolePolicyDocument(statement) + if err != nil { + fmt.Println("Failed to convert Role Policy Document into JSON: ", err) + return types.Role{}, err + } + + return client.CreateRole(roleName, string(assumeRolePolicyDocument), "", make(map[string]string), "/") +} + +func (client *AWSClient) CreatePolicyForSharedVPC(policyName string) (string, error) { + statement := map[string]interface{}{ + "Sid": "Statement1", + "Effect": "Allow", + "Action": []string{ + "route53:GetChange", + "route53:GetHostedZone", + "route53:ChangeResourceRecordSets", + "route53:ListHostedZones", + "route53:ListHostedZonesByName", + "route53:ListResourceRecordSets", + "route53:ChangeTagsForResource", + "route53:GetAccountLimit", + "route53:ListTagsForResource", + "route53:UpdateHostedZoneComment", + "tag:GetResources", + "tag:UntagResources", + }, + "Resource": "*", + } + return client.CreatePolicy(policyName, statement) +} diff --git a/pkg/aws/aws_client/route53.go b/pkg/aws/aws_client/route53.go index 4829d9d5..775464bf 100644 --- a/pkg/aws/aws_client/route53.go +++ b/pkg/aws/aws_client/route53.go @@ -8,24 +8,29 @@ import ( "github.com/openshift-online/ocm-common/pkg/log" ) -func (awsClient AWSClient) CreateHostedZone(hostedZoneName string, vpcID string, private bool) (*route53.CreateHostedZoneOutput, error) { +func (awsClient AWSClient) CreateHostedZone(hostedZoneName string, callerReference string, vpcID string, region string, private bool) (*route53.CreateHostedZoneOutput, error) { + // callReference is a required field of CreateHostedZoneInput struct, which used to identifies the request as a unique string. + // Usually random string or date/time stamp can be used as callReference. input := &route53.CreateHostedZoneInput{ - Name: &hostedZoneName, + Name: &hostedZoneName, + CallerReference: &callerReference, HostedZoneConfig: &types.HostedZoneConfig{ PrivateZone: private, }, } if vpcID != "" { vpc := &types.VPC{ - VPCId: &vpcID, + VPCId: &vpcID, + VPCRegion: types.VPCRegion(region), } input.VPC = vpc } + resp, err := awsClient.Route53Client.CreateHostedZone(context.TODO(), input) if err != nil { log.LogError("Create hosted zone failed for vpc %s with name %s: %s", vpcID, hostedZoneName, err.Error()) } else { - log.LogError("Create hosted zone succeed for vpc %s with name %s", vpcID, hostedZoneName) + log.LogInfo("Create hosted zone succeed for vpc %s with name %s", vpcID, hostedZoneName) } return resp, err } @@ -47,3 +52,12 @@ func (awsClient AWSClient) ListHostedZoneByDNSName(hostedZoneName string) (*rout return awsClient.Route53Client.ListHostedZonesByName(context.TODO(), input) } + +func (awsClient AWSClient) DeleteHostedZone(hostedZoneID string) error { + input := &route53.DeleteHostedZoneInput{ + Id: &hostedZoneID, + } + + _, err := awsClient.Route53Client.DeleteHostedZone(context.TODO(), input) + return err +} diff --git a/pkg/test/vpc_client/vpc.go b/pkg/test/vpc_client/vpc.go index 0689cfc8..d9c51fd5 100644 --- a/pkg/test/vpc_client/vpc.go +++ b/pkg/test/vpc_client/vpc.go @@ -212,6 +212,69 @@ func PrepareVPC(vpcName string, region string, vpcCIDR string, checkExisting boo return vpc, err } +func PrepareSharedVPC(vpcName string, region string, awsSharedCredentialFile string, vpcCIDR string, checkExisting bool, zones ...string) (*VPC, error) { + if vpcCIDR == "" { + vpcCIDR = CON.DefaultVPCCIDR + } + logMessage := fmt.Sprintf("Going to prepare a vpc with name %s, on region %s, with cidr %s and subnets on zones %s", + vpcName, region, vpcCIDR, strings.Join(zones, ",")) + if len(zones) == 0 { + logMessage = fmt.Sprintf("Going to prepare a vpc with name %s, on region %s, with cidr %s ", + vpcName, region, vpcCIDR) + } + log.LogInfo(logMessage) + awsclient, err := aws_client.CreateAWSClient("", region, awsSharedCredentialFile) + if err != nil { + log.LogError("Create AWS Client due to error: %s", err.Error()) + return nil, err + } + if checkExisting { + log.LogInfo("Got checkExisting set to true, will check if there is existing vpc in same name") + vpcs, err := awsclient.ListVPCByName(vpcName) + if err != nil { + log.LogError("Error happened when try to find a vpc: %s", err.Error()) + return nil, err + } + if len(vpcs) != 0 { + vpcID := *vpcs[0].VpcId + log.LogInfo("Got a vpc %s with name %s on region %s. Just load it for usage", + vpcID, vpcName, region) + vpc, err := GenerateVPCByID(vpcID, region) + if err != nil { + log.LogError("Load vpc %s details meets error %s", + vpcID, err.Error()) + return nil, err + } + for _, zone := range zones { + _, err = vpc.PreparePairSubnetByZone(zone) + if err != nil { + log.LogError("Prepare subnets for vpc %s on zone %s meets error %s", + vpcID, zone, err.Error()) + return nil, err + } + } + return vpc, nil + } + log.LogInfo("Got no vpc with name %s on region %s. Going to create a new one", + vpcName, region) + } + + vpc := NewVPC(). + Name(vpcName). + AWSclient(awsclient). + SetRegion(region). + CIDR(vpcCIDR). + NewCIDRPool() + vpc, err = vpc.CreateVPCChain(zones...) + if err != nil { + log.LogError("Create vpc chain meets error: %s", err.Error()) + } else { + log.LogInfo("Create vpc chain successfully. Enjoy it.") + } + + return vpc, err +} + // NewVPC will return a new VPC instance // CIDR can be empty, then it will use default value @@ -248,6 +311,37 @@ func GenerateVPCByID(vpcID string, region string) (*VPC, error) { return vpc, nil } +func GenerateSharedVPCByID(vpcID string, region string, awsSharedCredentialFile string) (*VPC, error) { + awsClient, err := aws_client.CreateAWSClient("", region, awsSharedCredentialFile) + if err != nil { + return nil, err + } + vpc := NewVPC().AWSclient(awsClient).ID(vpcID) + vpcResp, err := vpc.AWSClient.DescribeVPC(vpcID) + if err != nil { + return nil, err + } + vpc = vpc.Name(getTagName((vpcResp.Tags))).SetRegion(awsClient.Region).CIDR(*vpcResp.CidrBlock) + if err != nil { + return nil, err + } + _, err = vpc.ListSubnets() + if err != nil { + return nil, err + } + reservedCIDRs := []string{} + for _, sub := range vpc.SubnetList { + reservedCIDRs = append(reservedCIDRs, sub.Cidr) + } + cidrPool := NewCIDRPool(vpc.CIDRValue) + err = cidrPool.Reserve(reservedCIDRs...) + if err != nil { + return nil, err + } + vpc.CIDRPool = cidrPool + return vpc, nil +} + // GenerateVPCBySubnet will return a VPC with CIDRpool and subnets based on one of the subnet ID // If you know the subnet ID on AWS, then try to generate it on AWS. func GenerateVPCBySubnet(subnetID string, region string) (*VPC, error) { @@ -262,3 +356,16 @@ func GenerateVPCBySubnet(subnetID string, region string) (*VPC, error) { vpc, err := GenerateVPCByID(*subnetDetail[0].VpcId, region) return vpc, err } + +func GenerateSharedVPCBySubnet(subnetID string, region string, awsSharedCredentialFile string) (*VPC, error) { + awsClient, err := aws_client.CreateAWSClient("", region, awsSharedCredentialFile) + if err != nil { + return nil, err + } + subnetDetail, err := awsClient.ListSubnetDetail(subnetID) + if err != nil { + return nil, err + } + vpc, err := GenerateVPCByID(*subnetDetail[0].VpcId, region) + return vpc, err +}