diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..051ce4c --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,2 @@ +run: + timeout: 10m \ No newline at end of file diff --git a/go.mod b/go.mod index 4fdcd80..c8be96e 100644 --- a/go.mod +++ b/go.mod @@ -3,29 +3,48 @@ module github.com/openshift-online/ocm-common go 1.21 require ( - github.com/aws/aws-sdk-go-v2 v1.22.2 + github.com/apparentlymart/go-cidr v1.1.0 + github.com/aws/aws-sdk-go-v2 v1.26.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 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0 + 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/route53 v1.40.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 github.com/hashicorp/go-version v1.6.0 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.30.0 github.com/openshift-online/ocm-sdk-go v0.1.391 + github.com/sirupsen/logrus v1.9.3 go.uber.org/mock v0.3.0 golang.org/x/crypto v0.22.0 gopkg.in/square/go-jose.v2 v2.6.0 ) require ( - github.com/aws/smithy-go v1.16.0 + github.com/aws/smithy-go v1.20.1 github.com/kr/pretty v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) 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.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/glog v1.0.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/go.sum b/go.sum index e1f90ff..ef7b139 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,43 @@ -github.com/aws/aws-sdk-go-v2 v1.22.2 h1:lV0U8fnhAnPz8YcdmZVV60+tr6CakHzqA6P8T46ExJI= -github.com/aws/aws-sdk-go-v2 v1.22.2/go.mod h1:Kd0OJtkW3Q0M0lUWGszapWjEvrXDzRW+D21JNsroB+c= +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.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= +github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= +github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= +github.com/aws/aws-sdk-go-v2/config v1.27.9/go.mod h1:dK1FQfpwpql83kbD873E9vz4FyAxuJtR22wzoXn3qq0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao= +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.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= +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= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.48.0/go.mod h1:G2qcp9xrwch6TH9AlzWoYbV9QScyZhLCoMCQ1+BD404= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0 h1:ltCQObuImVYmIrMX65ikB9W83MEun3Ry2Sk11ecZ8Xw= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0/go.mod h1:TeZ9dVQzGaLG+SBIgdLIDbJ6WmfFvksLeG3EHGnNfZM= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.24.3 h1:pjgSJEvgJzv+e0frrqspeYdHz2JSW1KAGMXRe1FuQ1M= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.24.3/go.mod h1:dhRVzB/bmggoMEBhYXKZrTE+jqN34O4+webZSjGi12c= github.com/aws/aws-sdk-go-v2/service/iam v1.27.1 h1:rPkEOnwPOVop34lpAlA4Dv6x67Ys3moXkPDvBfjgSSo= github.com/aws/aws-sdk-go-v2/service/iam v1.27.1/go.mod h1:qdQ8NUrhmXE80S54w+LrtHUY+1Fp7cQSRZbJUZKrAcU= -github.com/aws/smithy-go v1.16.0 h1:gJZEH/Fqh+RsvlJ1Zt4tVAtV6bKkp3cC+R6FCZMNzik= -github.com/aws/smithy-go v1.16.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc= +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/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= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= +github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= +github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -26,6 +60,10 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -46,6 +84,8 @@ github.com/openshift-online/ocm-sdk-go v0.1.391 h1:BCC/sM1gVooxCL76MiPux2kng8MUb github.com/openshift-online/ocm-sdk-go v0.1.391/go.mod h1:/+VFIw1iW2H0jEkFH4GnbL/liWareyzsL0w7mDIudB4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -58,8 +98,11 @@ golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+ golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= @@ -71,6 +114,9 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/aws/aws_client/client.go b/pkg/aws/aws_client/client.go new file mode 100644 index 0000000..0525ec0 --- /dev/null +++ b/pkg/aws/aws_client/client.go @@ -0,0 +1,106 @@ +package aws_client + +import ( + "context" + "os" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/cloudformation" + "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/sts" + + "github.com/openshift-online/ocm-common/pkg/log" + + elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing" + "github.com/aws/aws-sdk-go-v2/service/route53" +) + +type AWSClient struct { + Ec2Client *ec2.Client + Route53Client *route53.Client + StackFormationClient *cloudformation.Client + ElbClient *elb.Client + StsClient *sts.Client + Region string + IamClient *iam.Client + ClientContext context.Context + AccountID string + KmsClient *kms.Client +} + +func CreateAWSClient(profileName string, region 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") + 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"), + "")), + ) + } 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) + cfg, err = config.LoadDefaultConfig(context.TODO(), + config.WithRegion(region), + config.WithSharedCredentialsFiles([]string{file}), + ) + } else { + cfg, err = config.LoadDefaultConfig(context.TODO(), + config.WithRegion(region), + config.WithSharedConfigProfile(profileName), + ) + } + + } + + if err != nil { + return nil, err + } + + awsClient := &AWSClient{ + Ec2Client: ec2.NewFromConfig(cfg), + Route53Client: route53.NewFromConfig(cfg), + StackFormationClient: cloudformation.NewFromConfig(cfg), + ElbClient: elb.NewFromConfig(cfg), + Region: region, + StsClient: sts.NewFromConfig(cfg), + IamClient: iam.NewFromConfig(cfg), + ClientContext: context.TODO(), + KmsClient: kms.NewFromConfig(cfg), + } + awsClient.AccountID = awsClient.GetAWSAccountID() + return awsClient, nil +} + +func (client *AWSClient) GetAWSAccountID() string { + input := &sts.GetCallerIdentityInput{} + out, err := client.StsClient.GetCallerIdentity(client.ClientContext, input) + if err != nil { + return "" + } + return *out.Account +} + +func (client *AWSClient) EC2() *ec2.Client { + return client.Ec2Client +} + +func (client *AWSClient) Route53() *route53.Client { + return client.Route53Client +} +func (client *AWSClient) CloudFormation() *cloudformation.Client { + return client.StackFormationClient +} +func (client *AWSClient) ELB() *elb.Client { + return client.ElbClient +} diff --git a/pkg/aws/aws_client/eip.go b/pkg/aws/aws_client/eip.go new file mode 100644 index 0000000..5b4c449 --- /dev/null +++ b/pkg/aws/aws_client/eip.go @@ -0,0 +1,89 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) AllocateEIPAddress() (*ec2.AllocateAddressOutput, error) { + inputs := &ec2.AllocateAddressInput{ + Address: nil, + CustomerOwnedIpv4Pool: nil, + Domain: "", + DryRun: nil, + NetworkBorderGroup: nil, + PublicIpv4Pool: nil, + TagSpecifications: nil, + } + + respEIP, err := client.Ec2Client.AllocateAddress(context.TODO(), inputs) + if err != nil { + log.LogError("Create eip failed " + err.Error()) + return nil, err + } + log.LogInfo("Allocated EIP %s with ip %s", *respEIP.AllocationId, *respEIP.PublicIp) + return respEIP, err +} + +func (client *AWSClient) DisassociateAddress(associateID string) (*ec2.DisassociateAddressOutput, error) { + inputDisassociate := &ec2.DisassociateAddressInput{ + AssociationId: aws.String(associateID), + DryRun: nil, + PublicIp: nil, + } + + respDisassociate, err := client.Ec2Client.DisassociateAddress(context.TODO(), inputDisassociate) + if err != nil { + log.LogError("Disassociate eip failed " + err.Error()) + return nil, err + } + log.LogInfo("Disassociate eip success") + return respDisassociate, err +} + +func (client *AWSClient) AllocateEIPAndAssociateInstance(instanceID string) (string, error) { + allocRes, err := client.AllocateEIPAddress() + if err != nil { + log.LogError("Failed allocated EIP: %s", err) + } else { + log.LogInfo("Successfully allocated EIP: %s", *allocRes.PublicIp) + } + assocRes, err := client.EC2().AssociateAddress(context.TODO(), + &ec2.AssociateAddressInput{ + AllocationId: allocRes.AllocationId, + InstanceId: aws.String(instanceID), + }) + if err != nil { + defer func() { + _, err := client.ReleaseAddress(*allocRes.AllocationId) + log.LogError("Associate EIP allocation %s failed to instance ID %s", *allocRes.AllocationId, instanceID) + if err != nil { + log.LogError("Failed allocated EIP: %s", err) + } + }() + return "", err + + } + log.LogInfo("Successfully allocated %s with instance %s.\n\tallocation id: %s, association id: %s\n", + *allocRes.PublicIp, instanceID, *allocRes.AllocationId, *assocRes.AssociationId) + return *allocRes.PublicIp, nil +} + +func (client *AWSClient) ReleaseAddress(allocationID string) (*ec2.ReleaseAddressOutput, error) { + inputRelease := &ec2.ReleaseAddressInput{ + AllocationId: aws.String(allocationID), + DryRun: nil, + NetworkBorderGroup: nil, + PublicIp: nil, + } + respRelease, err := client.Ec2Client.ReleaseAddress(context.TODO(), inputRelease) + if err != nil { + log.LogError("Release eip failed " + err.Error()) + return nil, err + } + log.LogInfo("Release eip success: " + allocationID) + return respRelease, err +} diff --git a/pkg/aws/aws_client/elb.go b/pkg/aws/aws_client/elb.go new file mode 100644 index 0000000..0ca65ce --- /dev/null +++ b/pkg/aws/aws_client/elb.go @@ -0,0 +1,42 @@ +package aws_client + +import ( + "context" + + elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing" + + elbtypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) DescribeLoadBalancers(vpcID string) ([]elbtypes.LoadBalancerDescription, error) { + + listenedELB := []elbtypes.LoadBalancerDescription{} + input := &elb.DescribeLoadBalancersInput{} + resp, err := client.ElbClient.DescribeLoadBalancers(context.TODO(), input) + if err != nil { + return nil, err + } + // for _, lb := range resp.LoadBalancers { + for _, lb := range resp.LoadBalancerDescriptions { + + // if *lb.VpcId == vpcID { + if *lb.VPCId == vpcID { + log.LogInfo("Got load balancer %s", *lb.LoadBalancerName) + listenedELB = append(listenedELB, lb) + } + } + + return listenedELB, err +} + +func (client *AWSClient) DeleteELB(ELB elbtypes.LoadBalancerDescription) error { + log.LogInfo("Goint to delete ELB %s", *ELB.LoadBalancerName) + + deleteELBInput := &elb.DeleteLoadBalancerInput{ + // LoadBalancerArn: ELB.LoadBalancerArn, + LoadBalancerName: ELB.LoadBalancerName, + } + _, err := client.ElbClient.DeleteLoadBalancer(context.TODO(), deleteELBInput) + return err +} diff --git a/pkg/aws/aws_client/env.go b/pkg/aws/aws_client/env.go new file mode 100644 index 0000000..b5cb648 --- /dev/null +++ b/pkg/aws/aws_client/env.go @@ -0,0 +1,13 @@ +package aws_client + +import "os" + +func envCredential() bool { + if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" { + return true + } + return false +} +func envAwsProfile() bool { + return os.Getenv("AWS_SHARED_CREDENTIALS_FILE") != "" +} diff --git a/pkg/aws/aws_client/image.go b/pkg/aws/aws_client/image.go new file mode 100644 index 0000000..1a90866 --- /dev/null +++ b/pkg/aws/aws_client/image.go @@ -0,0 +1,35 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CopyImage(sourceImageID string, sourceRegion string, name string) (string, error) { + copyImageInput := &ec2.CopyImageInput{ + Name: &name, + SourceImageId: &sourceImageID, + SourceRegion: &sourceRegion, + } + output, err := client.EC2().CopyImage(context.TODO(), copyImageInput) + if err != nil { + log.LogError("Error happens when copy image: %s", err) + return "", err + } + return *output.ImageId, nil +} + +func (client *AWSClient) DescribeImage(imageID string) (*ec2.DescribeImagesOutput, error) { + describeImageInput := &ec2.DescribeImagesInput{ + ImageIds: []string{imageID}, + } + output, err := client.EC2().DescribeImages(context.TODO(), describeImageInput) + if err != nil { + log.LogError("Describe image %s meet error: %s", imageID, err) + return nil, err + } + + return output, nil +} diff --git a/pkg/aws/aws_client/instance.go b/pkg/aws/aws_client/instance.go new file mode 100644 index 0000000..18f6c6d --- /dev/null +++ b/pkg/aws/aws_client/instance.go @@ -0,0 +1,256 @@ +package aws_client + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/service/iam" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) LaunchInstance(subnetID string, imageID string, count int, instanceType string, keyName string, securityGroupIds []string, wait bool) (*ec2.RunInstancesOutput, error) { + input := &ec2.RunInstancesInput{ + ImageId: aws.String(imageID), + MinCount: aws.Int32(int32(count)), + MaxCount: aws.Int32(int32(count)), + InstanceType: types.InstanceType(instanceType), + KeyName: aws.String(keyName), + SecurityGroupIds: securityGroupIds, + SubnetId: &subnetID, + } + output, err := client.Ec2Client.RunInstances(context.TODO(), input) + if wait && err == nil { + instanceIDs := []string{} + for _, instance := range output.Instances { + instanceIDs = append(instanceIDs, *instance.InstanceId) + } + log.LogInfo("Waiting for below instances ready: %s", strings.Join(instanceIDs, ",")) + _, err = client.WaitForInstancesRunning(instanceIDs, 10) + if err != nil { + log.LogError("Error happened for instance running: %s", err) + } else { + log.LogInfo("All instances running") + } + } + return output, err +} + +// ListInstance pass parameter like +// map[string][]string{"vpc-id":[]string{"" }}, map[string][]string{"tag:Name":[]string{"" }} +// instanceIDs can be empty. And if you would like to get more info from the instances like security groups, it should be set +func (client *AWSClient) ListInstances(instanceIDs []string, filters ...map[string][]string) ([]types.Instance, error) { + FilterInput := []types.Filter{} + for _, filter := range filters { + for k, v := range filter { + awsFilter := types.Filter{ + Name: &k, + Values: v, + } + FilterInput = append(FilterInput, awsFilter) + } + } + getInstanceInput := &ec2.DescribeInstancesInput{ + Filters: FilterInput, + } + if len(instanceIDs) != 0 { + getInstanceInput.InstanceIds = instanceIDs + } + resp, err := client.EC2().DescribeInstances(context.TODO(), getInstanceInput) + if err != nil { + log.LogError("List instances failed with filters %v: %s", filters, err) + } + var instances []types.Instance + for _, reserv := range resp.Reservations { + instances = append(instances, reserv.Instances...) + } + return instances, err +} + +func (client *AWSClient) WaitForInstanceReady(instanceID string, timeout time.Duration) error { + instanceIDs := []string{ + instanceID, + } + log.LogInfo("Waiting for below instances ready: %s ", strings.Join(instanceIDs, "|")) + _, err := client.WaitForInstancesRunning(instanceIDs, 10) + return err +} + +func (client *AWSClient) CheckInstanceState(instanceIDs ...string) (*ec2.DescribeInstanceStatusOutput, error) { + log.LogInfo("Check instances status of %s", strings.Join(instanceIDs, ",")) + includeAll := true + input := &ec2.DescribeInstanceStatusInput{ + InstanceIds: instanceIDs, + IncludeAllInstances: &includeAll, + } + output, err := client.Ec2Client.DescribeInstanceStatus(context.TODO(), input) + return output, err +} + +// timeout indicates the minutes +func (client *AWSClient) WaitForInstancesRunning(instanceIDs []string, timeout time.Duration) (allRunning bool, err error) { + startTime := time.Now() + + for time.Now().Before(startTime.Add(timeout * time.Minute)) { + allRunning = true + output, err := client.CheckInstanceState(instanceIDs...) + if err != nil { + log.LogError("Error happened when describe instant status: %s", strings.Join(instanceIDs, ",")) + return false, err + } + if len(output.InstanceStatuses) == 0 { + log.LogWarning("Instance status description for %s is 0", strings.Join(instanceIDs, ",")) + } + for _, ins := range output.InstanceStatuses { + log.LogInfo("Instance ID %s is in status of %s", *ins.InstanceId, ins.InstanceStatus.Status) + log.LogInfo("Instance ID %s is in state of %s", *ins.InstanceId, ins.InstanceState.Name) + if ins.InstanceState.Name != types.InstanceStateNameRunning && ins.InstanceStatus.Status != types.SummaryStatusOk { + allRunning = false + } + + } + if allRunning { + return true, nil + } + time.Sleep(time.Minute) + } + err = fmt.Errorf("timeout for waiting instances running") + return +} +func (client *AWSClient) WaitForInstancesTerminated(instanceIDs []string, timeout time.Duration) (allTerminated bool, err error) { + startTime := time.Now() + for time.Now().Before(startTime.Add(timeout * time.Minute)) { + allTerminated = true + output, err := client.CheckInstanceState(instanceIDs...) + if err != nil { + log.LogError("Error happened when describe instant status: %s", strings.Join(instanceIDs, ",")) + return false, err + } + if len(output.InstanceStatuses) == 0 { + log.LogWarning("Instance status description for %s is 0", strings.Join(instanceIDs, ",")) + } + for _, ins := range output.InstanceStatuses { + log.LogInfo("Instance ID %s is in status of %s", *ins.InstanceId, ins.InstanceStatus.Status) + log.LogInfo("Instance ID %s is in state of %s", *ins.InstanceId, ins.InstanceState.Name) + if ins.InstanceState.Name != types.InstanceStateNameTerminated { + allTerminated = false + } + + } + if allTerminated { + return true, nil + } + time.Sleep(time.Minute) + } + err = fmt.Errorf("timeout for waiting instances terminated") + return + +} + +// Search instance types for specified region/availability zones +func (client *AWSClient) ListAvaliableInstanceTypesForRegion(region string, availabilityZones ...string) ([]string, error) { + var params *ec2.DescribeInstanceTypeOfferingsInput + if len(availabilityZones) > 0 { + params = &ec2.DescribeInstanceTypeOfferingsInput{ + Filters: []types.Filter{{Name: aws.String("location"), Values: availabilityZones}}, + LocationType: types.LocationTypeAvailabilityZone, + } + } else { + params = &ec2.DescribeInstanceTypeOfferingsInput{ + Filters: []types.Filter{{Name: aws.String("location"), Values: []string{region}}}, + } + } + var instanceTypes []types.InstanceTypeOffering + paginator := ec2.NewDescribeInstanceTypeOfferingsPaginator(client.Ec2Client, params) + for paginator.HasMorePages() { + page, err := paginator.NextPage(context.TODO()) + if err != nil { + return nil, err + } + instanceTypes = append(instanceTypes, page.InstanceTypeOfferings...) + } + machineTypeList := make([]string, len(instanceTypes)) + for i, v := range instanceTypes { + machineTypeList[i] = string(v.InstanceType) + } + return machineTypeList, nil +} + +// List avaliablezone for specific region +// zone type are: local-zone/availability-zone/wavelength-zone +func (client *AWSClient) ListAvaliableZonesForRegion(region string, zoneType string) ([]string, error) { + var zones []string + availabilityZones, err := client.Ec2Client.DescribeAvailabilityZones(context.TODO(), &ec2.DescribeAvailabilityZonesInput{ + Filters: []types.Filter{ + { + Name: aws.String("region-name"), + Values: []string{region}, + }, + { + Name: aws.String("zone-type"), + Values: []string{zoneType}, + }, + }, + }) + if err != nil { + return nil, err + } + + if len(availabilityZones.AvailabilityZones) < 1 { + return zones, nil + } + + for _, v := range availabilityZones.AvailabilityZones { + zones = append(zones, *v.ZoneName) + } + return zones, nil +} +func (client *AWSClient) TerminateInstances(instanceIDs []string, wait bool, timeout time.Duration) error { + if len(instanceIDs) == 0 { + log.LogInfo("Got no instances to terminate.") + return nil + } + terminateInput := &ec2.TerminateInstancesInput{ + InstanceIds: instanceIDs, + } + _, err := client.EC2().TerminateInstances(context.TODO(), terminateInput) + if err != nil { + log.LogError("Error happens when terminate instances %s : %s", strings.Join(instanceIDs, ","), err) + return err + } else { + log.LogInfo("Terminate instances %s successfully", strings.Join(instanceIDs, ",")) + } + if wait { + err = client.WaitForInstanceTerminated(instanceIDs, timeout) + if err != nil { + log.LogError("Waiting for instances %s termination timeout %s ", strings.Join(instanceIDs, ","), err) + return err + } + + } + return nil +} + +func (client *AWSClient) WaitForInstanceTerminated(instanceIDs []string, timeout time.Duration) error { + log.LogInfo("Waiting for below instances terminated: %s ", strings.Join(instanceIDs, ",")) + _, err := client.WaitForInstancesTerminated(instanceIDs, timeout) + return err +} + +func (client *AWSClient) GetTagsOfInstanceProfile(instanceProfileName string) ([]iamtypes.Tag, error) { + input := &iam.ListInstanceProfileTagsInput{ + InstanceProfileName: &instanceProfileName, + } + resp, err := client.IamClient.ListInstanceProfileTags(context.TODO(), input) + if err != nil { + return nil, err + } + tags := resp.Tags + return tags, err +} diff --git a/pkg/aws/aws_client/internet_gateway.go b/pkg/aws/aws_client/internet_gateway.go new file mode 100644 index 0000000..bf065be --- /dev/null +++ b/pkg/aws/aws_client/internet_gateway.go @@ -0,0 +1,87 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateInternetGateway() (*ec2.CreateInternetGatewayOutput, error) { + inputCreateInternetGateway := &ec2.CreateInternetGatewayInput{ + DryRun: nil, + TagSpecifications: nil, + } + respCreateInternetGateway, err := client.Ec2Client.CreateInternetGateway(context.TODO(), inputCreateInternetGateway) + if err != nil { + log.LogError("Create igw error " + err.Error()) + return nil, err + } + log.LogInfo("Create igw success: " + *respCreateInternetGateway.InternetGateway.InternetGatewayId) + return respCreateInternetGateway, err +} + +func (client *AWSClient) AttachInternetGateway(internetGatewayID string, vpcID string) (*ec2.AttachInternetGatewayOutput, error) { + + input := &ec2.AttachInternetGatewayInput{ + InternetGatewayId: aws.String(internetGatewayID), + VpcId: aws.String(vpcID), + DryRun: nil, + } + resp, err := client.Ec2Client.AttachInternetGateway(context.TODO(), input) + if err != nil { + log.LogError("Attach igw error " + err.Error()) + return nil, err + } + log.LogInfo("Attach igw success: " + internetGatewayID) + return resp, err +} + +func (client *AWSClient) DetachInternetGateway(internetGatewayID string, vpcID string) (*ec2.DetachInternetGatewayOutput, error) { + input := &ec2.DetachInternetGatewayInput{ + InternetGatewayId: aws.String(internetGatewayID), + VpcId: aws.String(vpcID), + DryRun: nil, + } + resp, err := client.Ec2Client.DetachInternetGateway(context.TODO(), input) + if err != nil { + log.LogError("Detach igw %s error from vpc %s:"+err.Error(), internetGatewayID, vpcID) + return nil, err + } + log.LogInfo("Detach igw %s success from vpc %s", internetGatewayID, vpcID) + return resp, err +} +func (client *AWSClient) ListInternetGateWay(vpcID string) ([]types.InternetGateway, error) { + vpcFilter := "attachment.vpc-id" + filter := []types.Filter{ + types.Filter{ + Name: &vpcFilter, + Values: []string{ + vpcID, + }, + }, + } + input := &ec2.DescribeInternetGatewaysInput{ + Filters: filter, + } + resp, err := client.Ec2Client.DescribeInternetGateways(context.TODO(), input) + if err != nil { + return nil, err + } + return resp.InternetGateways, err +} +func (client *AWSClient) DeleteInternetGateway(internetGatewayID string) (*ec2.DeleteInternetGatewayOutput, error) { + inputDeleteInternetGateway := &ec2.DeleteInternetGatewayInput{ + InternetGatewayId: aws.String(internetGatewayID), + DryRun: nil, + } + respDeleteInternetGateway, err := client.Ec2Client.DeleteInternetGateway(context.TODO(), inputDeleteInternetGateway) + if err != nil { + log.LogError("Delete igw error " + err.Error()) + return nil, err + } + log.LogInfo("Delete igw success: " + internetGatewayID) + return respDeleteInternetGateway, err +} diff --git a/pkg/aws/aws_client/key_pair.go b/pkg/aws/aws_client/key_pair.go new file mode 100644 index 0000000..41ae26a --- /dev/null +++ b/pkg/aws/aws_client/key_pair.go @@ -0,0 +1,39 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateKeyPair(keyName string) (*ec2.CreateKeyPairOutput, error) { + + input := &ec2.CreateKeyPairInput{ + KeyName: &keyName, + } + + output, err := client.Ec2Client.CreateKeyPair(context.TODO(), input) + if err != nil { + log.LogError("Create key pair error " + err.Error()) + return nil, err + } + log.LogInfo("Create key pair success: " + *output.KeyPairId) + + return output, err +} + +func (client *AWSClient) DeleteKeyPair(keyName string) (*ec2.DeleteKeyPairOutput, error) { + input := &ec2.DeleteKeyPairInput{ + KeyName: &keyName, + } + + output, err := client.Ec2Client.DeleteKeyPair(context.TODO(), input) + if err != nil { + log.LogError("Delete key pair error " + err.Error()) + return nil, err + } + log.LogInfo("Delete key pair success") + return output, err + +} diff --git a/pkg/aws/aws_client/kms.go b/pkg/aws/aws_client/kms.go new file mode 100644 index 0000000..a8b7350 --- /dev/null +++ b/pkg/aws/aws_client/kms.go @@ -0,0 +1,103 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateKMSKeys(tagKey string, tagValue string, description string, policy string, multiRegion bool) (keyID string, keyArn string, err error) { + //Create the key + + result, err := client.KmsClient.CreateKey(context.TODO(), &kms.CreateKeyInput{ + Tags: []types.Tag{ + { + TagKey: aws.String(tagKey), + TagValue: aws.String(tagValue), + }, + }, + Description: &description, + Policy: aws.String(policy), + MultiRegion: &multiRegion, + }) + + if err != nil { + log.LogError("Got error creating key: %s", err) + + } + + return *result.KeyMetadata.KeyId, *result.KeyMetadata.Arn, err +} + +func (client *AWSClient) DescribeKMSKeys(keyID string) (kms.DescribeKeyOutput, error) { + // Create the key + result, err := client.KmsClient.DescribeKey(context.TODO(), &kms.DescribeKeyInput{ + KeyId: &keyID, + }) + if err != nil { + log.LogError("Got error describe key: %s", err) + } + return *result, err +} +func (client *AWSClient) ScheduleKeyDeletion(kmsKeyId string, pendingWindowInDays int32) (*kms.ScheduleKeyDeletionOutput, error) { + result, err := client.KmsClient.ScheduleKeyDeletion(context.TODO(), &kms.ScheduleKeyDeletionInput{ + KeyId: aws.String(kmsKeyId), + PendingWindowInDays: &pendingWindowInDays, + }) + + if err != nil { + log.LogError("Got error when ScheduleKeyDeletion: %s", err) + } + + return result, err +} + +func (client *AWSClient) GetKMSPolicy(keyID string, policyName string) (kms.GetKeyPolicyOutput, error) { + + if policyName == "" { + policyName = "default" + } + result, err := client.KmsClient.GetKeyPolicy(context.TODO(), &kms.GetKeyPolicyInput{ + KeyId: &keyID, + PolicyName: &policyName, + }) + if err != nil { + log.LogError("Got error get KMS key policy: %s", err) + } + return *result, err +} + +func (client *AWSClient) PutKMSPolicy(keyID string, policyName string, policy string) (kms.PutKeyPolicyOutput, error) { + if policyName == "" { + policyName = "default" + } + result, err := client.KmsClient.PutKeyPolicy(context.TODO(), &kms.PutKeyPolicyInput{ + KeyId: &keyID, + PolicyName: &policyName, + Policy: &policy, + }) + if err != nil { + log.LogError("Got error put KMS key policy: %s", err) + } + return *result, err +} + +func (client *AWSClient) TagKeys(kmsKeyId string, tagKey string, tagValue string) (*kms.TagResourceOutput, error) { + + output, err := client.KmsClient.TagResource(context.TODO(), &kms.TagResourceInput{ + KeyId: &kmsKeyId, + Tags: []types.Tag{ + { + TagKey: aws.String(tagKey), + TagValue: aws.String(tagValue), + }, + }, + }) + if err != nil { + log.LogError("Got error add tag for KMS key: %s", err) + } + return output, err +} diff --git a/pkg/aws/aws_client/nat_gateway.go b/pkg/aws/aws_client/nat_gateway.go new file mode 100644 index 0000000..d1f7917 --- /dev/null +++ b/pkg/aws/aws_client/nat_gateway.go @@ -0,0 +1,72 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateNatGateway(subnetID string, allocationID string, vpcID string) (*ec2.CreateNatGatewayOutput, error) { + inputCreateNat := &ec2.CreateNatGatewayInput{ + SubnetId: aws.String(subnetID), + AllocationId: aws.String(allocationID), + ClientToken: nil, + ConnectivityType: "", + DryRun: nil, + TagSpecifications: nil, + } + respCreateNat, err := client.Ec2Client.CreateNatGateway(context.TODO(), inputCreateNat) + if err != nil { + log.LogError("Create nat error " + err.Error()) + return nil, err + } + log.LogInfo("Create nat success: " + *respCreateNat.NatGateway.NatGatewayId) + err = client.WaitForResourceExisting(*respCreateNat.NatGateway.NatGatewayId, 10*60) + return respCreateNat, err +} + +// DeleteNatGateway will wait for seconds for nat gateway becomes status of deleted +func (client *AWSClient) DeleteNatGateway(natGatewayID string, timeout ...int) (*ec2.DeleteNatGatewayOutput, error) { + inputDeleteNatGateway := &ec2.DeleteNatGatewayInput{ + NatGatewayId: aws.String(natGatewayID), + DryRun: nil, + } + respDeleteNatGateway, err := client.Ec2Client.DeleteNatGateway(context.TODO(), inputDeleteNatGateway) + if err != nil { + log.LogError("Delete Nat Gateway error " + err.Error()) + return nil, err + } + timeoutTime := 60 + if len(timeout) != 0 { + timeoutTime = timeout[0] + } + err = client.WaitForResourceDeleted(natGatewayID, timeoutTime) + if err != nil { + return respDeleteNatGateway, err + } + log.LogInfo("Delete Nat Gateway success " + *respDeleteNatGateway.NatGatewayId) + return respDeleteNatGateway, err +} + +func (client *AWSClient) ListNatGateWays(vpcID string) ([]types.NatGateway, error) { + vpcFilter := "vpc-id" + filter := []types.Filter{ + types.Filter{ + Name: &vpcFilter, + Values: []string{ + vpcID, + }, + }, + } + input := &ec2.DescribeNatGatewaysInput{ + Filter: filter, + } + output, err := client.Ec2Client.DescribeNatGateways(context.TODO(), input) + if err != nil { + return nil, err + } + return output.NatGateways, nil +} diff --git a/pkg/aws/aws_client/network_acl.go b/pkg/aws/aws_client/network_acl.go new file mode 100644 index 0000000..88485fc --- /dev/null +++ b/pkg/aws/aws_client/network_acl.go @@ -0,0 +1,73 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) ListNetWorkAcls(vpcID string) ([]types.NetworkAcl, error) { + vpcFilter := "vpc-id" + customizedAcls := []types.NetworkAcl{} + filter := []types.Filter{ + types.Filter{ + Name: &vpcFilter, + Values: []string{ + vpcID, + }, + }, + } + describeACLInput := &ec2.DescribeNetworkAclsInput{ + Filters: filter, + } + output, err := client.Ec2Client.DescribeNetworkAcls(context.TODO(), describeACLInput) + if err != nil { + return nil, err + } + customizedAcls = append(customizedAcls, output.NetworkAcls...) + return customizedAcls, nil +} + +// RuleAction : deny/allow +// Protocol: TCP --> 6 +func (client *AWSClient) AddNetworkAclEntry(networkAclId string, egress bool, protocol string, ruleAction string, ruleNumber int32, fromPort int32, toPort int32, cidrBlock string) (*ec2.CreateNetworkAclEntryOutput, error) { + input := &ec2.CreateNetworkAclEntryInput{ + Egress: aws.Bool(egress), + NetworkAclId: aws.String(networkAclId), + Protocol: aws.String(protocol), + RuleAction: types.RuleAction(ruleAction), + RuleNumber: aws.Int32(ruleNumber), + CidrBlock: aws.String(cidrBlock), + PortRange: &types.PortRange{ + From: aws.Int32(fromPort), + To: aws.Int32(toPort), + }, + } + resp, err := client.Ec2Client.CreateNetworkAclEntry(context.TODO(), input) + if err != nil { + log.LogError("Create NetworkAcl rule failed " + err.Error()) + return nil, err + } + log.LogInfo("Create NetworkAcl rule success " + networkAclId) + return resp, err +} + +func (client *AWSClient) DeleteNetworkAclEntry(networkAclId string, egress bool, ruleNumber int32) (*ec2.DeleteNetworkAclEntryOutput, error) { + input := &ec2.DeleteNetworkAclEntryInput{ + Egress: aws.Bool(egress), + NetworkAclId: aws.String(networkAclId), + RuleNumber: aws.Int32(ruleNumber), + } + resp, err := client.Ec2Client.DeleteNetworkAclEntry(context.TODO(), input) + if err != nil { + log.LogError("Delete NetworkAcl rule failed " + err.Error()) + return nil, err + } + log.LogInfo("Delete NetworkAcl rule success " + networkAclId) + return resp, err + +} diff --git a/pkg/aws/aws_client/network_interface.go b/pkg/aws/aws_client/network_interface.go new file mode 100644 index 0000000..6a6ef6e --- /dev/null +++ b/pkg/aws/aws_client/network_interface.go @@ -0,0 +1,54 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) DescribeNetWorkInterface(vpcID string) ([]types.NetworkInterface, error) { + vpcFilter := "vpc-id" + filter := []types.Filter{ + types.Filter{ + Name: &vpcFilter, + Values: []string{ + vpcID, + }, + }, + } + input := &ec2.DescribeNetworkInterfacesInput{ + Filters: filter, + } + resp, err := client.Ec2Client.DescribeNetworkInterfaces(context.TODO(), input) + if err != nil { + return nil, err + } + return resp.NetworkInterfaces, err +} + +func (client *AWSClient) DeleteNetworkInterface(networkinterface types.NetworkInterface) error { + association := networkinterface.Association + if association != nil { + if association.AllocationId != nil { + _, err := client.ReleaseAddress(*association.AllocationId) + if err != nil { + log.LogError("Release address failed for %s: %s", *networkinterface.NetworkInterfaceId, err) + return err + } + + } + + } + deleteNIInput := &ec2.DeleteNetworkInterfaceInput{ + NetworkInterfaceId: networkinterface.NetworkInterfaceId, + } + _, err := client.Ec2Client.DeleteNetworkInterface(context.TODO(), deleteNIInput) + if err != nil { + log.LogError("Delete network interface %s failed: %s", *networkinterface.NetworkInterfaceId, err) + } else { + log.LogInfo("Deleted network interface %s", *networkinterface.NetworkInterfaceId) + } + return err +} diff --git a/pkg/aws/aws_client/oidc.go b/pkg/aws/aws_client/oidc.go new file mode 100644 index 0000000..e943bbf --- /dev/null +++ b/pkg/aws/aws_client/oidc.go @@ -0,0 +1,15 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/iam" +) + +func (client *AWSClient) DeleteOIDCProvider(providerArn string) error { + input := &iam.DeleteOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: &providerArn, + } + _, err := client.IamClient.DeleteOpenIDConnectProvider(context.TODO(), input) + return err +} diff --git a/pkg/aws/aws_client/policy.go b/pkg/aws/aws_client/policy.go new file mode 100644 index 0000000..18b6dcb --- /dev/null +++ b/pkg/aws/aws_client/policy.go @@ -0,0 +1,163 @@ +package aws_client + +import ( + "context" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateIAMPolicy(policyName string, policyDocument string, tags map[string]string) (*types.Policy, error) { + var policyTags []types.Tag + for tagKey, tagValue := range tags { + policyTags = append(policyTags, types.Tag{ + Key: &tagKey, + Value: &tagValue, + }) + } + description := "Policy for ocm-qe testing" + input := &iam.CreatePolicyInput{ + PolicyName: &policyName, + PolicyDocument: &policyDocument, + Tags: policyTags, + Description: &description, + } + output, err := client.IamClient.CreatePolicy(context.TODO(), input) + if err != nil { + return nil, err + } + err = client.WaitForResourceExisting("policy-"+*output.Policy.Arn, 10) // add a prefix to meet the resourceExisting split rule + return output.Policy, err +} + +func (client *AWSClient) GetIAMPolicy(policyArn string) (*types.Policy, error) { + input := &iam.GetPolicyInput{ + PolicyArn: &policyArn, + } + out, err := client.IamClient.GetPolicy(context.TODO(), input) + return out.Policy, err +} + +func (client *AWSClient) DeleteIAMPolicy(arn string) error { + input := &iam.DeletePolicyInput{ + PolicyArn: &arn, + } + err := client.DeletePolicyVersions(arn) + if err != nil { + return err + } + _, err = client.IamClient.DeletePolicy(context.TODO(), input) + return err +} + +func (client *AWSClient) AttachIAMPolicy(roleName string, policyArn string) error { + input := &iam.AttachRolePolicyInput{ + PolicyArn: &policyArn, + RoleName: &roleName, + } + _, err := client.IamClient.AttachRolePolicy(context.TODO(), input) + return err + +} +func (client *AWSClient) DetachIAMPolicy(roleAName string, policyArn string) error { + input := &iam.DetachRolePolicyInput{ + RoleName: &roleAName, + PolicyArn: &policyArn, + } + _, err := client.IamClient.DetachRolePolicy(context.TODO(), input) + return err +} +func (client *AWSClient) GetCustomerIAMPolicies() ([]types.Policy, error) { + + maxItem := int32(1000) + input := &iam.ListPoliciesInput{ + Scope: "Local", + MaxItems: &maxItem, + } + out, err := client.IamClient.ListPolicies(context.TODO(), input) + if err != nil { + return nil, err + } + + return out.Policies, err + +} +func CleanByOutDate(policy types.Policy) bool { + now := time.Now().UTC() + return policy.CreateDate.Add(7 * time.Hour * 24).Before(now) +} + +func CleanByName(policy types.Policy) bool { + return strings.Contains(*policy.PolicyName, "sdq-ci-") +} + +func (client *AWSClient) FilterNeedCleanPolicies(cleanRule func(types.Policy) bool) ([]types.Policy, error) { + needClean := []types.Policy{} + + policies, err := client.GetCustomerIAMPolicies() + if err != nil { + return needClean, err + } + for _, policy := range policies { + if cleanRule(policy) { + + needClean = append(needClean, policy) + } + } + return needClean, nil +} + +func (client *AWSClient) DeletePolicy(arn string) error { + input := &iam.DeletePolicyInput{ + PolicyArn: &arn, + } + err := client.DeletePolicyVersions(arn) + if err != nil { + return err + } + _, err = client.IamClient.DeletePolicy(context.TODO(), input) + return err +} + +func (client *AWSClient) DeletePolicyVersions(policyArn string) error { + input := &iam.ListPolicyVersionsInput{ + PolicyArn: &policyArn, + } + out, err := client.IamClient.ListPolicyVersions(context.TODO(), input) + if err != nil { + return err + } + for _, version := range out.Versions { + if version.IsDefaultVersion { + continue + } + input := &iam.DeletePolicyVersionInput{ + PolicyArn: &policyArn, + VersionId: version.VersionId, + } + _, err = client.IamClient.DeletePolicyVersion(context.TODO(), input) + if err != nil { + return err + } + } + return nil +} +func (client *AWSClient) CleanPolicies(cleanRule func(types.Policy) bool) error { + policies, err := client.FilterNeedCleanPolicies(cleanRule) + if err != nil { + return err + } + for _, policy := range policies { + if *policy.AttachmentCount == 0 { + log.LogInfo("Can be deleted: %s", *policy.Arn) + err = client.DeletePolicy(*policy.Arn) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/aws/aws_client/resource.go b/pkg/aws/aws_client/resource.go new file mode 100644 index 0000000..bcbe182 --- /dev/null +++ b/pkg/aws/aws_client/resource.go @@ -0,0 +1,334 @@ +package aws_client + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) ResourceExisting(resourceID string) bool { + splitedResource := strings.SplitN(resourceID, "-", 2) //Just split the first - + resourceType := splitedResource[0] + switch resourceType { + case "sg": + input := &ec2.DescribeSecurityGroupsInput{ + GroupIds: []string{ + resourceID, + }, + } + output, err := client.Ec2Client.DescribeSecurityGroups(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(output.SecurityGroups) != 0 { + return true + } + case "subnet": + subnetInput := &ec2.DescribeSubnetsInput{ + SubnetIds: []string{resourceID}, + } + subnetOutput, err := client.Ec2Client.DescribeSubnets(context.TODO(), subnetInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(subnetOutput.Subnets) != 0 { + return true + } + + vpcInput := &ec2.DescribeVpcsInput{ + VpcIds: []string{resourceID}, + } + vpcOutput, err := client.Ec2Client.DescribeVpcs(context.TODO(), vpcInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(vpcOutput.Vpcs) != 0 { + return true + } + case "rtb": + rbtInput := &ec2.DescribeRouteTablesInput{ + RouteTableIds: []string{ + resourceID, + }, + } + rbtOutput, err := client.Ec2Client.DescribeRouteTables(context.TODO(), rbtInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(rbtOutput.RouteTables) != 0 { + return true + } + case "vpc": + vpcInput := &ec2.DescribeVpcsInput{ + VpcIds: []string{ + resourceID, + }, + } + vpcOutput, err := client.Ec2Client.DescribeVpcs(context.TODO(), vpcInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(vpcOutput.Vpcs) != 0 { + return true + } + case "eipalloc": + input := &ec2.DescribeAddressesInput{ + AllocationIds: []string{ + resourceID, + }, + } + eipOutput, err := client.Ec2Client.DescribeAddresses(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(eipOutput.Addresses) != 0 { + return true + } + case "igw": + input := &ec2.DescribeInternetGatewaysInput{ + InternetGatewayIds: []string{ + resourceID, + }, + } + output, err := client.Ec2Client.DescribeInternetGateways(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(output.InternetGateways) != 0 { + return true + } + case "nat": + input := &ec2.DescribeNatGatewaysInput{ + NatGatewayIds: []string{ + resourceID, + }, + } + output, err := client.Ec2Client.DescribeNatGateways(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return false + } else { + log.LogError(err.Error()) + return false + } + } + if len(output.NatGateways) != 0 { + log.LogDebug("Current NAT gateway %s status %s ", resourceID, output.NatGateways[0].State) + status := string(output.NatGateways[0].State) + if status == "available" { + return true + } + + } + // role should use "role-" to pass + case "role": + role, _ := client.GetRole(splitedResource[1]) + return role != nil + // policy should use "policy-" as parameter + case "policy": + policy, _ := client.GetIAMPolicy(splitedResource[1]) + return policy != nil + default: + log.LogError("Unknow resource type: %s of resource %s .Please define it in the method ResourceExisting.", resourceType, resourceID) + } + return false +} + +func (client *AWSClient) ResourceDeleted(resourceID string) bool { + var deleted bool = true + splitedResource := strings.Split(resourceID, "-") + resourceType := splitedResource[0] + switch resourceType { + case "subnet": + subnetInput := &ec2.DescribeSubnetsInput{ + SubnetIds: []string{resourceID}, + } + subnetOutput, err := client.Ec2Client.DescribeSubnets(context.TODO(), subnetInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return true + } else { + log.LogError(err.Error()) + return false + } + } + if len(subnetOutput.Subnets) != 0 { + deleted = false + } + case "rtb": + rbtInput := &ec2.DescribeRouteTablesInput{ + RouteTableIds: []string{ + resourceID, + }, + } + rbtOutput, err := client.Ec2Client.DescribeRouteTables(context.TODO(), rbtInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return true + } else { + log.LogError(err.Error()) + return false + } + } + if len(rbtOutput.RouteTables) != 0 { + deleted = false + } + case "vpc": + vpcInput := &ec2.DescribeVpcsInput{ + VpcIds: []string{ + resourceID, + }, + } + vpcOutput, err := client.Ec2Client.DescribeVpcs(context.TODO(), vpcInput) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return true + } else { + log.LogError(err.Error()) + return false + } + } + if len(vpcOutput.Vpcs) != 0 { + deleted = false + } + case "eipalloc": + input := &ec2.DescribeAddressesInput{ + AllocationIds: []string{ + resourceID, + }, + } + eipOutput, err := client.Ec2Client.DescribeAddresses(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return true + } else { + log.LogError(err.Error()) + return false + } + } + if len(eipOutput.Addresses) != 0 { + deleted = false + } + case "igw": + input := &ec2.DescribeInternetGatewaysInput{ + InternetGatewayIds: []string{ + resourceID, + }, + } + output, err := client.Ec2Client.DescribeInternetGateways(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return true + } else { + log.LogError(err.Error()) + return false + } + } + if len(output.InternetGateways) != 0 { + deleted = false + } + case "sg": + input := &ec2.DescribeSecurityGroupsInput{ + GroupIds: []string{ + resourceID, + }, + } + output, err := client.Ec2Client.DescribeSecurityGroups(context.TODO(), input) + if err != nil { + if strings.Contains(err.Error(), "NotFound") { + return true + } else { + log.LogError(err.Error()) + return false + } + } + if len(output.SecurityGroups) != 0 { + deleted = false + } + case "nat": + input := &ec2.DescribeNatGatewaysInput{ + NatGatewayIds: []string{ + resourceID, + }, + } + output, err := client.Ec2Client.DescribeNatGateways(context.TODO(), input) + if err != nil { + log.LogError(err.Error()) + return false + } + if len(output.NatGateways) != 0 { + log.LogDebug("Current NAT gateway %s status %s", resourceID, output.NatGateways[0].State) + status := string(output.NatGateways[0].State) + if status != "deleted" { + deleted = false + } + + } + default: + log.LogError("Unknow resource type: %s of resource %s .Please define it in the method ResourceExisting.", resourceType, resourceID) + } + return deleted +} + +// WaitForResourceExisting will wait for the resource created in seconds +func (client AWSClient) WaitForResourceExisting(resourceID string, timeout int) error { + now := time.Now() + for now.Add(time.Duration(timeout) * time.Second).After(time.Now()) { + if client.ResourceExisting(resourceID) { + return nil + } + time.Sleep(2 * time.Second) + } + return fmt.Errorf("timeout after %d seconds for waiting resource created: %s", timeout, resourceID) +} + +// WaitForResourceExisting will wait for the resource created in seconds +func (client AWSClient) WaitForResourceDeleted(resourceID string, timeout int) error { + now := time.Now() + for now.Add(time.Duration(timeout) * time.Second).After(time.Now()) { + if client.ResourceDeleted(resourceID) { + return nil + } + time.Sleep(2 * time.Second) + } + return fmt.Errorf("Timeout after %d seconds for waiting resource deleted: %s", timeout, resourceID) +} diff --git a/pkg/aws/aws_client/role.go b/pkg/aws/aws_client/role.go new file mode 100644 index 0000000..d486302 --- /dev/null +++ b/pkg/aws/aws_client/role.go @@ -0,0 +1,91 @@ +package aws_client + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" +) + +func (client *AWSClient) CreateRole(roleName string, + assumeRolePolicyDocument string, + permissionBoundry string, + tags map[string]string, + path string, +) (types.Role, error) { + var roleTags []types.Tag + for tagKey, tagValue := range tags { + roleTags = append(roleTags, types.Tag{ + Key: &tagKey, + Value: &tagValue, + }) + } + description := "This is created role for ocm-qe automation testing" + input := &iam.CreateRoleInput{ + RoleName: &roleName, + AssumeRolePolicyDocument: &assumeRolePolicyDocument, + Path: &path, + PermissionsBoundary: &permissionBoundry, + Tags: roleTags, + Description: &description, + } + resp, err := client.IamClient.CreateRole(context.TODO(), input) + if err != nil { + return *resp.Role, err + } + err = client.WaitForResourceExisting("role-"+*resp.Role.RoleName, 10) // add a prefix to meet the resourceExisting split rule + return *resp.Role, err +} + +func (client *AWSClient) GetRole(roleName string) (*types.Role, error) { + input := &iam.GetRoleInput{ + RoleName: &roleName, + } + out, err := client.IamClient.GetRole(context.TODO(), input) + return out.Role, err +} +func (client *AWSClient) DeleteRole(roleName string) error { + + input := &iam.DeleteRoleInput{ + RoleName: &roleName, + } + _, err := client.IamClient.DeleteRole(context.TODO(), input) + return err +} + +func (client *AWSClient) DeleteRoleAndPolicy(roleName string, managedPolicy bool) error { + input := &iam.ListAttachedRolePoliciesInput{ + RoleName: &roleName, + } + output, err := client.IamClient.ListAttachedRolePolicies(client.ClientContext, input) + if err != nil { + return err + } + + if err != nil { + return err + } + fmt.Println(output.AttachedPolicies) + for _, policy := range output.AttachedPolicies { + err = client.DetachIAMPolicy(roleName, *policy.PolicyArn) + if err != nil { + return err + } + if !managedPolicy { + err = client.DeletePolicy(*policy.PolicyArn) + if err != nil { + return err + } + } + + } + err = client.DeleteRole(roleName) + return err +} + +func (client *AWSClient) ListRoles() ([]types.Role, error) { + input := &iam.ListRolesInput{} + out, err := client.IamClient.ListRoles(context.TODO(), input) + return out.Roles, err +} diff --git a/pkg/aws/aws_client/route53.go b/pkg/aws/aws_client/route53.go new file mode 100644 index 0000000..20d0dfc --- /dev/null +++ b/pkg/aws/aws_client/route53.go @@ -0,0 +1,49 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (awsClient AWSClient) CreateHostedZone(hostedZoneName string, vpcID string, private bool) (*route53.CreateHostedZoneOutput, error) { + input := &route53.CreateHostedZoneInput{ + Name: &hostedZoneName, + HostedZoneConfig: &types.HostedZoneConfig{ + PrivateZone: private, + }, + } + if vpcID != "" { + vpc := &types.VPC{ + VPCId: &vpcID, + } + 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: %s", vpcID, hostedZoneName, err.Error()) + } + return resp, err +} + +func (awsClient AWSClient) GetHostedZone(hostedZoneID string) (*route53.GetHostedZoneOutput, error) { + input := &route53.GetHostedZoneInput{ + Id: &hostedZoneID, + } + + return awsClient.Route53Client.GetHostedZone(context.TODO(), input) +} + +func (awsClient AWSClient) ListHostedZoneByDNSName(hostedZoneName string) (*route53.ListHostedZonesByNameOutput, error) { + var maxItems int32 = 1 + input := &route53.ListHostedZonesByNameInput{ + DNSName: &hostedZoneName, + MaxItems: &maxItems, + } + + return awsClient.Route53Client.ListHostedZonesByName(context.TODO(), input) +} diff --git a/pkg/aws/aws_client/route_table.go b/pkg/aws/aws_client/route_table.go new file mode 100644 index 0000000..fda8670 --- /dev/null +++ b/pkg/aws/aws_client/route_table.go @@ -0,0 +1,198 @@ +package aws_client + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateRouteTable(vpcID string) (*ec2.CreateRouteTableOutput, error) { + inputCreateRouteTable := &ec2.CreateRouteTableInput{ + VpcId: aws.String(vpcID), + DryRun: nil, + TagSpecifications: nil, + } + + respCreateRT, err := client.Ec2Client.CreateRouteTable(context.TODO(), inputCreateRouteTable) + if err != nil { + log.LogError("Create route table failed " + err.Error()) + return nil, err + } + err = client.WaitForResourceExisting(*respCreateRT.RouteTable.RouteTableId, 20) + return respCreateRT, err +} + +func (client *AWSClient) AssociateRouteTable(routeTableID string, subnetID string, vpcID string) (*ec2.AssociateRouteTableOutput, error) { + inputAssociateRouteTable := &ec2.AssociateRouteTableInput{ + RouteTableId: aws.String(routeTableID), + DryRun: nil, + GatewayId: nil, + SubnetId: aws.String(subnetID), + } + + respAssociateRouteTable, err := client.Ec2Client.AssociateRouteTable(context.TODO(), inputAssociateRouteTable) + if err != nil { + log.LogError("Associate route table failed " + err.Error()) + return nil, err + } + log.LogInfo("Associate route table success " + *respAssociateRouteTable.AssociationId) + return respAssociateRouteTable, err +} + +// ListRouteTable will list all of the route tables created based on the VPC +func (client *AWSClient) ListCustomerRouteTables(vpcID string) ([]types.RouteTable, error) { + vpcFilterName := "vpc-id" + Filters := []types.Filter{ + types.Filter{ + Name: &vpcFilterName, + Values: []string{ + vpcID, + }, + }, + } + ListRouteTable := &ec2.DescribeRouteTablesInput{ + Filters: Filters, + } + resp, err := client.Ec2Client.DescribeRouteTables(context.TODO(), ListRouteTable) + if err != nil { + return nil, err + } + customRouteTables := []types.RouteTable{} + for _, rt := range resp.RouteTables { + isMain := false + for _, rta := range rt.Associations { + if *rta.Main { + isMain = true + log.LogInfo("Got main association for rt %s", *rt.RouteTableId) + } + } + if !isMain { + customRouteTables = append(customRouteTables, rt) + log.LogInfo("Got custom rt %s ", *rt.RouteTableId) + } + } + return customRouteTables, nil +} + +func (client *AWSClient) ListRTAssociations(routeTableID string) ([]string, error) { + associations := []string{} + ListRouteTable := &ec2.DescribeRouteTablesInput{ + RouteTableIds: []string{routeTableID}, + } + resp, err := client.Ec2Client.DescribeRouteTables(context.TODO(), ListRouteTable) + if err != nil { + return associations, err + } + for _, rt := range resp.RouteTables { + for _, rta := range rt.Associations { + associations = append(associations, *rta.RouteTableAssociationId) + } + } + return associations, err +} + +func (client *AWSClient) DisassociateRouteTableAssociation(associationID string) (*ec2.DisassociateRouteTableOutput, error) { + input := &ec2.DisassociateRouteTableInput{ + AssociationId: aws.String(associationID), + DryRun: nil, + } + + resp, err := client.Ec2Client.DisassociateRouteTable(context.TODO(), input) + if err != nil { + log.LogError("Disassociate route table failed " + err.Error()) + return nil, err + } + log.LogInfo("Disassociate route table success " + associationID) + return resp, err +} + +func (client *AWSClient) DisassociateRouteTableAssociations(routeTableID string) error { + associationIDs, err := client.ListRTAssociations(routeTableID) + if err != nil { + err = fmt.Errorf("List associations of route table %s failed: %s", routeTableID, err) + return err + } + for _, assoID := range associationIDs { + _, err = client.DisassociateRouteTableAssociation(assoID) + if err != nil { + return err + } + } + return nil +} + +func (client *AWSClient) CreateRoute(routeTableID string, targetID string) (*types.Route, error) { + prefix := strings.Split(targetID, "-")[0] + route := &types.Route{} + createRouteInput := &ec2.CreateRouteInput{ + RouteTableId: aws.String(routeTableID), + DestinationCidrBlock: aws.String(CON.RouteDestinationCidrBlock), + } + switch prefix { + case "cagw": + createRouteInput.CarrierGatewayId = &targetID + route.CarrierGatewayId = &targetID + case "eigw": + createRouteInput.EgressOnlyInternetGatewayId = &targetID + route.EgressOnlyInternetGatewayId = &targetID + case "vpce": + createRouteInput.LocalGatewayId = &targetID + route.LocalGatewayId = &targetID + case "i": + createRouteInput.InstanceId = &targetID + route.InstanceId = &targetID + case "igw": + createRouteInput.GatewayId = &targetID + route.GatewayId = &targetID + case "nat": + createRouteInput.NatGatewayId = &targetID + route.NatGatewayId = &targetID + case "eni": + createRouteInput.NetworkInterfaceId = &targetID + route.NetworkInterfaceId = &targetID + case "tgw": + createRouteInput.TransitGatewayId = &targetID + route.TransitGatewayId = &targetID + default: + return nil, fmt.Errorf("the type %s is not define in the route creation func, please define it in CreateRoute", prefix) + } + + _, err := client.Ec2Client.CreateRoute(context.TODO(), createRouteInput) + if err != nil { + log.LogError("Create route failed " + err.Error()) + return nil, err + } + log.LogInfo("Create route success for route table: " + routeTableID) + return route, err +} + +func (client *AWSClient) DeleteRouteTable(routeTableID string) error { + input := &ec2.DeleteRouteTableInput{ + RouteTableId: &routeTableID, + } + _, err := client.Ec2Client.DeleteRouteTable(context.TODO(), input) + if err != nil { + return err + } + err = client.WaitForResourceDeleted(routeTableID, 5) + return err +} +func (client *AWSClient) DeleteRouteTableChain(routeTableID string) error { + err := client.DisassociateRouteTableAssociations(routeTableID) + if err != nil { + return err + } + err = client.DeleteRouteTable(routeTableID) + if err != nil { + log.LogError("Delete route table %s chain failed %s", routeTableID, err) + } else { + log.LogInfo("Delete route table %s chain successfully %s", routeTableID, err) + } + return err +} diff --git a/pkg/aws/aws_client/security_group.go b/pkg/aws/aws_client/security_group.go new file mode 100644 index 0000000..dd9dd01 --- /dev/null +++ b/pkg/aws/aws_client/security_group.go @@ -0,0 +1,180 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) ListSecurityGroups(vpcID string) ([]types.SecurityGroup, error) { + vpcFilter := "vpc-id" + customizedSGs := []types.SecurityGroup{} + filter := []types.Filter{ + types.Filter{ + Name: &vpcFilter, + Values: []string{ + vpcID, + }, + }, + } + describeSGInput := &ec2.DescribeSecurityGroupsInput{ + Filters: filter, + } + output, err := client.Ec2Client.DescribeSecurityGroups(context.TODO(), describeSGInput) + if err != nil { + return nil, err + } + for _, sg := range output.SecurityGroups { + if *sg.GroupName == "default" && *sg.Description == "default VPC security group" { + continue + } + customizedSGs = append(customizedSGs, sg) + } + return customizedSGs, nil +} + +func (client *AWSClient) ReleaseInboundOutboundRules(sgID string) error { + filterKey := "group-id" + filter := []types.Filter{ + types.Filter{ + Name: &filterKey, + Values: []string{ + sgID, + }, + }, + } + describeSGInput := &ec2.DescribeSecurityGroupRulesInput{ + Filters: filter, + } + resp, err := client.Ec2Client.DescribeSecurityGroupRules(context.TODO(), describeSGInput) + if err != nil { + log.LogError("Describe rules failed for SG %s: %s", sgID, err.Error()) + return err + } + rules := resp.SecurityGroupRules + ingressRules := []string{} + egressRules := []string{} + for _, rule := range rules { + if *rule.IsEgress { + egressRules = append(egressRules, *rule.SecurityGroupRuleId) + continue + } + ingressRules = append(ingressRules, *rule.SecurityGroupRuleId) + + } + if len(ingressRules) != 0 { + releaseIngressRuleInput := &ec2.RevokeSecurityGroupIngressInput{ + GroupId: &sgID, + SecurityGroupRuleIds: ingressRules, + } + _, err = client.Ec2Client.RevokeSecurityGroupIngress(context.TODO(), releaseIngressRuleInput) + if err != nil { + log.LogError("Release inbound rules failed for SG %s: %s", sgID, err.Error()) + return err + } + } + if len(egressRules) != 0 { + releaseEgressRuleInput := &ec2.RevokeSecurityGroupEgressInput{ + GroupId: &sgID, + SecurityGroupRuleIds: egressRules, + } + _, err = client.Ec2Client.RevokeSecurityGroupEgress(context.TODO(), releaseEgressRuleInput) + if err != nil { + log.LogError("Release outbound rules failed for SG %s: %s", sgID, err.Error()) + return err + } + } + log.LogInfo("Release rules successfully for SG %s", sgID) + return nil +} + +func (client *AWSClient) DeleteSecurityGroup(groupID string) (*ec2.DeleteSecurityGroupOutput, error) { + + err := client.ReleaseInboundOutboundRules(groupID) + if err != nil { + return nil, err + } + + input := &ec2.DeleteSecurityGroupInput{ + DryRun: nil, + GroupId: aws.String(groupID), + GroupName: nil, + } + + resp, err := client.Ec2Client.DeleteSecurityGroup(context.TODO(), input) + if err != nil { + log.LogError("Delete security group %s failed %s", groupID, err.Error()) + return nil, err + } + log.LogInfo("Delete security group %s success ", groupID) + return resp, err +} +func (client *AWSClient) AuthorizeSecurityGroupIngress(groupID string, cidr string, protocol string, fromPort int32, toPort int32) (*ec2.AuthorizeSecurityGroupIngressOutput, error) { + input := &ec2.AuthorizeSecurityGroupIngressInput{ + CidrIp: aws.String(cidr), + DryRun: nil, + FromPort: aws.Int32(fromPort), + GroupId: aws.String(groupID), + GroupName: nil, + IpPermissions: nil, + IpProtocol: aws.String(protocol), + SourceSecurityGroupName: nil, + SourceSecurityGroupOwnerId: nil, + TagSpecifications: nil, + ToPort: aws.Int32(toPort), + } + + resp, err := client.Ec2Client.AuthorizeSecurityGroupIngress(context.TODO(), input) + if err != nil { + log.LogError("Authorize security group failed " + err.Error()) + return nil, err + } + log.LogInfo("Authorize security group success " + groupID) + return resp, err +} + +func (client *AWSClient) CreateSecurityGroup(vpcID string, groupName string, sgDescription string) (*ec2.CreateSecurityGroupOutput, error) { + input := &ec2.CreateSecurityGroupInput{ + Description: aws.String(sgDescription), + GroupName: aws.String(groupName), + DryRun: nil, + TagSpecifications: nil, + VpcId: aws.String(vpcID), + } + + resp, err := client.Ec2Client.CreateSecurityGroup(context.TODO(), input) + if err != nil { + log.LogError("Create security group failed " + err.Error()) + return nil, err + } + log.LogInfo("Create security group %s success for %s", *resp.GroupId, vpcID) + err = client.WaitForResourceExisting(*resp.GroupId, 4) + if err != nil { + log.LogError("Wait for security group ready failed %s", err) + } + tags := map[string]string{ + "Name": CON.AdditionalSecurityGroupName, + } + _, err = client.TagResource(*resp.GroupId, tags) + if err != nil { + log.LogError("Created tagged failed %s", err) + } + log.LogInfo("Created tagged security group with ID %s", *resp.GroupId) + return resp, err +} + +func (client *AWSClient) GetSecurityGroupWithID(sgID string) (*ec2.DescribeSecurityGroupsOutput, error) { + + describeSGInput := &ec2.DescribeSecurityGroupsInput{ + GroupIds: []string{sgID}, + } + output, err := client.Ec2Client.DescribeSecurityGroups(context.TODO(), describeSGInput) + if err != nil { + return nil, err + } + return output, nil +} diff --git a/pkg/aws/aws_client/subnet.go b/pkg/aws/aws_client/subnet.go new file mode 100644 index 0000000..ce8fb11 --- /dev/null +++ b/pkg/aws/aws_client/subnet.go @@ -0,0 +1,114 @@ +package aws_client + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) CreateSubnet(vpcID string, region string, zone string, subnetCidr string) (*types.Subnet, error) { + if region == "" { + region = CON.DefaultAWSRegion + } + if zone == "" { + zone = CON.DefaultAWSZone + } + + input := &ec2.CreateSubnetInput{ + VpcId: aws.String(vpcID), + AvailabilityZone: aws.String(fmt.Sprintf(region + zone)), + AvailabilityZoneId: nil, + CidrBlock: aws.String(subnetCidr), + DryRun: nil, + Ipv6CidrBlock: nil, + Ipv6Native: nil, + OutpostArn: nil, + TagSpecifications: nil, + } + respCreateSubnet, err := client.Ec2Client.CreateSubnet(context.TODO(), input) + if err != nil { + log.LogError("create subnet error " + err.Error()) + return nil, err + } + log.LogInfo("Created subnet %s for vpc %s", *respCreateSubnet.Subnet.SubnetId, vpcID) + err = client.WaitForResourceExisting(*respCreateSubnet.Subnet.SubnetId, 4) + if err != nil { + return nil, err + } + return respCreateSubnet.Subnet, err +} + +func (client *AWSClient) ListSubnetByVpcID(vpcID string) ([]types.Subnet, error) { + subnetFilter := []types.Filter{ + { + Name: aws.String("vpc-id"), + Values: []string{ + vpcID, + }, + }, + } + + return client.ListSubnetsByFilter(subnetFilter) +} + +func (client *AWSClient) DeleteSubnet(subnetID string) (*ec2.DeleteSubnetOutput, error) { + input := &ec2.DeleteSubnetInput{ + SubnetId: aws.String(subnetID), + DryRun: nil, + } + + resp, err := client.Ec2Client.DeleteSubnet(context.TODO(), input) + if err != nil { + log.LogError("Delete subnet error " + err.Error()) + return nil, err + } + log.LogInfo("Delete subnet success " + subnetID) + return resp, err +} + +func (client *AWSClient) ListSubnetDetail(subnetIDs ...string) ([]types.Subnet, error) { + // subnetFilter := []types.Filter{types.Filter{Name: aws.String("vpc-id"), Values: []string{vpcID}}} + var subs = []types.Subnet{} + if len(subnetIDs) == 0 { + return subs, nil + } + + input := &ec2.DescribeSubnetsInput{ + DryRun: nil, + Filters: nil, + MaxResults: nil, + NextToken: nil, + SubnetIds: subnetIDs, + } + + resp, err := client.Ec2Client.DescribeSubnets(context.TODO(), input) + + if err != nil { + return subs, err + } + subs = resp.Subnets + return subs, nil +} + +// List subnet by filters +func (client *AWSClient) ListSubnetsByFilter(filter []types.Filter) ([]types.Subnet, error) { + input := &ec2.DescribeSubnetsInput{ + DryRun: nil, + Filters: filter, + MaxResults: nil, + NextToken: nil, + SubnetIds: nil, + } + + resp, err := client.Ec2Client.DescribeSubnets(context.TODO(), input) + if err != nil { + return nil, fmt.Errorf("describe subnet by filter error " + err.Error()) + } + + return resp.Subnets, err +} diff --git a/pkg/aws/aws_client/tag.go b/pkg/aws/aws_client/tag.go new file mode 100644 index 0000000..439563e --- /dev/null +++ b/pkg/aws/aws_client/tag.go @@ -0,0 +1,54 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) TagResource(resourceID string, tags map[string]string) (*ec2.CreateTagsOutput, error) { + awsTags := []types.Tag{} + for key, value := range tags { + Key := key + Value := value + tag := types.Tag{ + Key: &Key, + Value: &Value, + } + awsTags = append(awsTags, tag) + } + updateBody := &ec2.CreateTagsInput{ + Resources: []string{resourceID}, + Tags: awsTags, + } + + output, err := client.Ec2Client.CreateTags(context.TODO(), updateBody) + if err != nil { + log.LogError("Tag resource %s failed: %s", resourceID, err.Error()) + } else { + log.LogInfo("Tag resource %s successfully", resourceID) + } + return output, err +} + +func (client *AWSClient) RemoveResourceTag(resourceID string, tagKey string, tagValue string) (*ec2.DeleteTagsOutput, error) { + tags := []types.Tag{ + types.Tag{ + Key: &tagKey, + Value: &tagValue, + }, + } + updateBody := &ec2.DeleteTagsInput{ + Resources: []string{resourceID}, + Tags: tags, + } + output, err := client.Ec2Client.DeleteTags(context.TODO(), updateBody) + if err != nil { + log.LogError("Remove resource tag %s:%s from %s failed", tagKey, tagValue, resourceID) + } else { + log.LogInfo("Remove resource tag %s:%s from %s successfully", tagKey, tagValue, resourceID) + } + return output, err +} diff --git a/pkg/aws/aws_client/volume.go b/pkg/aws/aws_client/volume.go new file mode 100644 index 0000000..f45ab73 --- /dev/null +++ b/pkg/aws/aws_client/volume.go @@ -0,0 +1,20 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) DescribeVolumeByID(volumeID string) (*ec2.DescribeVolumesOutput, error) { + + output, err := client.Ec2Client.DescribeVolumes(context.TODO(), &ec2.DescribeVolumesInput{ + VolumeIds: []string{volumeID}, + }) + + if err != nil { + log.LogError("Got error describe volume: %s", err) + } + return output, err +} diff --git a/pkg/aws/aws_client/vpc.go b/pkg/aws/aws_client/vpc.go new file mode 100644 index 0000000..98b1cb4 --- /dev/null +++ b/pkg/aws/aws_client/vpc.go @@ -0,0 +1,167 @@ +package aws_client + +import ( + "context" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) ListVPCByName(vpcName string) ([]types.Vpc, error) { + vpcs := []types.Vpc{} + filterKey := "tag:Name" + filter := []types.Filter{ + types.Filter{ + Name: &filterKey, + Values: []string{vpcName}, + }, + } + input := &ec2.DescribeVpcsInput{ + Filters: filter, + } + resp, err := client.Ec2Client.DescribeVpcs(context.TODO(), input) + if err != nil { + return vpcs, err + } + vpcs = resp.Vpcs + return vpcs, nil +} + +func (client *AWSClient) CreateVpc(cidr string, name ...string) (*ec2.CreateVpcOutput, error) { + vpcName := CON.VpcDefaultName + if len(name) == 1 { + vpcName = name[0] + } + tags := map[string]string{ + "Name": vpcName, + CON.QEFlagKey: CON.QEFLAG, + } + input := &ec2.CreateVpcInput{ + CidrBlock: aws.String(cidr), + DryRun: nil, + InstanceTenancy: "", + Ipv4IpamPoolId: nil, + Ipv4NetmaskLength: nil, + TagSpecifications: nil, + } + + resp, err := client.Ec2Client.CreateVpc(context.TODO(), input) + if err != nil { + log.LogError("Create vpc error " + err.Error()) + return nil, err + } + log.LogInfo("Create vpc success " + *resp.Vpc.VpcId) + err = client.WaitForResourceExisting(*resp.Vpc.VpcId, 10) + if err != nil { + return resp, err + } + + _, err = client.TagResource(*resp.Vpc.VpcId, tags) + if err != nil { + return resp, err + } + + log.LogInfo("Created vpc with ID " + *resp.Vpc.VpcId) + return resp, err +} + +// ModifyVpcDnsAttribute will modify the vpc attibutes +// dnsAttribute should be the value of "DnsHostnames" and "DnsSupport" +func (client *AWSClient) ModifyVpcDnsAttribute(vpcID string, dnsAttribute string, status bool) (*ec2.ModifyVpcAttributeOutput, error) { + inputModifyVpc := &ec2.ModifyVpcAttributeInput{} + + if dnsAttribute == CON.VpcDnsHostnamesAttribute { + inputModifyVpc = &ec2.ModifyVpcAttributeInput{ + VpcId: aws.String(vpcID), + EnableDnsHostnames: &types.AttributeBooleanValue{Value: aws.Bool(status)}, + } + } else if dnsAttribute == CON.VpcDnsSupportAttribute { + inputModifyVpc = &ec2.ModifyVpcAttributeInput{ + VpcId: aws.String(vpcID), + EnableDnsSupport: &types.AttributeBooleanValue{Value: aws.Bool(status)}, + } + } + + resp, err := client.Ec2Client.ModifyVpcAttribute(context.TODO(), inputModifyVpc) + if err != nil { + log.LogError("Modify vpc dns attribute failed " + err.Error()) + return nil, err + } + log.LogInfo("Modify vpc dns attribute success" + vpcID + dnsAttribute) + return resp, err +} + +func (client *AWSClient) DeleteVpc(vpcID string) (*ec2.DeleteVpcOutput, error) { + input := &ec2.DeleteVpcInput{ + VpcId: aws.String(vpcID), + DryRun: nil, + } + + resp, err := client.Ec2Client.DeleteVpc(context.TODO(), input) + if err != nil { + log.LogError("Delete vpc %s failed "+err.Error(), vpcID) + return nil, err + } + log.LogInfo("Delete vpc success " + vpcID) + return resp, err + +} +func (client *AWSClient) DescribeVPC(vpcID string) (types.Vpc, error) { + var vpc types.Vpc + input := &ec2.DescribeVpcsInput{ + VpcIds: []string{vpcID}, + } + + resp, err := client.Ec2Client.DescribeVpcs(context.TODO(), input) + if err != nil { + return vpc, err + } + vpc = resp.Vpcs[0] + return vpc, err +} + +func (client *AWSClient) ListEndpointAssociation(vpcID string) ([]types.VpcEndpoint, error) { + vpcFilterKey := "vpc-id" + filters := []types.Filter{ + types.Filter{ + Name: &vpcFilterKey, + Values: []string{vpcID}, + }, + } + + input := ec2.DescribeVpcEndpointsInput{ + Filters: filters, + } + resp, err := client.Ec2Client.DescribeVpcEndpoints(context.TODO(), &input) + if err != nil { + return nil, err + } + return resp.VpcEndpoints, err +} + +func (client *AWSClient) DeleteVPCEndpoints(vpcID string) error { + vpcEndpoints, err := client.ListEndpointAssociation(vpcID) + if err != nil { + return err + } + var endpoints = []string{} + for _, ve := range vpcEndpoints { + endpoints = append(endpoints, *ve.VpcEndpointId) + } + if len(endpoints) != 0 { + input := &ec2.DeleteVpcEndpointsInput{ + VpcEndpointIds: endpoints, + } + _, err = client.Ec2Client.DeleteVpcEndpoints(context.TODO(), input) + if err != nil { + log.LogError("Delete vpc endpoints %s failed: %s", strings.Join(endpoints, ","), err.Error()) + } else { + log.LogInfo("Delete vpc endpoints %s successfully", strings.Join(endpoints, ",")) + } + } + return err +} diff --git a/pkg/aws/consts/consts.go b/pkg/aws/consts/consts.go index a968ed4..10235bc 100644 --- a/pkg/aws/consts/consts.go +++ b/pkg/aws/consts/consts.go @@ -1,5 +1,80 @@ package consts +import ( + "os" +) + const ( MaxAwsRoleLength = 64 ) + +var HomeDir, _ = os.UserHomeDir() +var QEFLAG string = os.Getenv("QE_FLAG") + +const ( + HTTPConflict = 409 + AWSCredentialsFileRelativePath = "/.aws/credentials" + AdminPolicyArn = "arn:aws:iam::aws:policy/AdministratorAccess" + DefaultAWSCredentialUser = "default" + DefaultAWSRegion = "us-east-2" + DefaultAWSZone = "a" +) + +var AWSCredentialsFilePath = HomeDir + AWSCredentialsFileRelativePath + +const ( + DefaultVPCCIDR = "10.0.0.0/16" + DefaultCIDRPrefix = 24 + RouteDestinationCidrBlock = "0.0.0.0/0" + + VpcDefaultName = "ocm-ci-vpc" + + CreationPrivateSelector = "private" + CreationPublicSelector = "public" + CreationPairSelector = "pair" + CreationMultiSelector = "multi" + + VpcDnsHostnamesAttribute = "DnsHostnames" + VpcDnsSupportAttribute = "DnsSupport" + NetworkResourceFileName = "resource.json" + + TCPProtocol = "tcp" + UDPProtocol = "udp" + + ProxySecurityGroupName = "proxy-sg" + AdditionalSecurityGroupName = "ocm-additional-sg" + ProxySecurityGroupDescription = "security group for proxy" + + QEFlagKey = "ocm_ci_flag" + + // Proxy related + ProxyName = "ocm-proxy" + InstanceKeyName = "openshift-qe" + AWSInstanceUser = "ec2-user" + BastionName = "ocm-bastion" +) + +var ProxyImageMap = map[string]string{ + "us-west-2": "ami-03b82d95dbe67072d", + "ap-northeast-1": "ami-0517f6ca1da98f337", +} +var BastionImageMap = map[string]string{ + "us-east-1": "ami-01c647eace872fc02", + "us-east-2": "ami-00a9282ce3b5ddfb1", + "us-west-1": "ami-0f1ee917b10382dea", + "ap-southeast-1": "ami-0db1894e055420bc0", + "us-west-2": "ami-0b2b4f610e654d9ac", + "ap-northeast-1": "ami-0a21e01face015dd9", +} + +const ( + PublicSubNetTagKey = "PublicSubnet" + PublicSubNetTagValue = "true" +) + +const ( + PrivateLBTag = "kubernetes.io/role/internal-elb" + PublicLBTag = "kubernetes.io/role/elb" + // valid LB Tag is empty or 1 + LBTagValue = "" +) diff --git a/pkg/log/logger.go b/pkg/log/logger.go new file mode 100644 index 0000000..0add513 --- /dev/null +++ b/pkg/log/logger.go @@ -0,0 +1,31 @@ +package log + +import ( + logger "github.com/sirupsen/logrus" +) + +func Initlogger() { + customFormatter := new(logger.TextFormatter) + customFormatter.TimestampFormat = "2006-01-02 15:04:05" + logger.SetFormatter(customFormatter) + customFormatter.FullTimestamp = true +} +func LogInfo(format string, args ...interface{}) { + Initlogger() + logger.Infof(format, args...) +} + +func LogError(format string, args ...interface{}) { + Initlogger() + logger.Errorf(format, args...) +} + +func LogDebug(format string, args ...interface{}) { + Initlogger() + logger.Debugf(format, args...) +} + +func LogWarning(format string, args ...interface{}) { + Initlogger() + logger.Warnf(format, args...) +} diff --git a/pkg/test/vpc_client/bastion.go b/pkg/test/vpc_client/bastion.go new file mode 100644 index 0000000..7dc98eb --- /dev/null +++ b/pkg/test/vpc_client/bastion.go @@ -0,0 +1,89 @@ +package vpc_client + +import ( + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +// LaunchBastion will launch a bastion instance on the indicated zone. +// If set imageID to empty, it will find the bastion image in the bastionImageMap map +func (vpc *VPC) LaunchBastion(imageID string, zone string) (*types.Instance, error) { + var inst *types.Instance + if imageID == "" { + var ok bool + imageID, ok = CON.BastionImageMap[vpc.Region] + if !ok { + log.LogError("Cannot find bastion image of region %s in map bastionImageMap, please indicate it as parameter", vpc.Region) + return nil, fmt.Errorf("cannot find bastion image of region %s in map bastionImageMap, please indicate it as parameter", vpc.Region) + } + } + pubSubnet, err := vpc.PreparePublicSubnet(zone) + if err != nil { + log.LogInfo("Error preparing a subnet in current zone %s with image ID %s: %s", zone, imageID, err) + return nil, err + } + SGID, err := vpc.CreateAndAuthorizeDefaultSecurityGroupForProxy() + if err != nil { + log.LogError("Prepare SG failed for the bastion preparation %s", err) + return inst, err + } + + instOut, err := vpc.AWSClient.LaunchInstance(pubSubnet.ID, imageID, 1, "t3.medium", CON.InstanceKeyName, []string{SGID}, true) + if err != nil { + log.LogError("Launch bastion instance failed %s", err) + return inst, err + } else { + log.LogInfo("Launch bastion instance %s succeed", *instOut.Instances[0].InstanceId) + } + tags := map[string]string{ + "Name": CON.BastionName, + } + instID := *instOut.Instances[0].InstanceId + _, err = vpc.AWSClient.TagResource(instID, tags) + if err != nil { + return inst, fmt.Errorf("tag instance %s failed:%s", instID, err) + } + + publicIP, err := vpc.AWSClient.AllocateEIPAndAssociateInstance(instID) + if err != nil { + log.LogError("Prepare EIP failed for the bastion preparation %s", err) + return inst, err + } + log.LogInfo("Prepare EIP successfully for the bastion preparation. Launch with IP: %s", publicIP) + inst = &instOut.Instances[0] + inst.PublicIpAddress = &publicIP + time.Sleep(2 * time.Minute) + return inst, nil +} + +func (vpc *VPC) PrepareBastion(zone string) (*types.Instance, error) { + filters := []map[string][]string{ + { + "vpc-id": { + vpc.VpcID, + }, + }, + { + "tag:Name": { + CON.BastionName, + }, + }, + } + + insts, err := vpc.AWSClient.ListInstances([]string{}, filters...) + if err != nil { + return nil, err + } + if len(insts) == 0 { + log.LogInfo("Didn't found an existing bastion, going to launch one") + return vpc.LaunchBastion("", zone) + + } + log.LogInfo("Found existing bastion: %s", *insts[0].InstanceId) + return &insts[0], nil +} diff --git a/pkg/test/vpc_client/cidr.go b/pkg/test/vpc_client/cidr.go new file mode 100644 index 0000000..9cfa4a5 --- /dev/null +++ b/pkg/test/vpc_client/cidr.go @@ -0,0 +1,71 @@ +package vpc_client + +import ( + "fmt" + "net" + + "github.com/apparentlymart/go-cidr/cidr" + + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" +) + +func NewCIDRPool(vpcCIDR string) *VPCCIDRPool { + v := &VPCCIDRPool{ + CIDR: vpcCIDR, + } + prefix := CON.DefaultCIDRPrefix + if v.Prefix != 0 { + prefix = v.Prefix + } + v.GenerateSubnetPool(prefix) + return v +} + +func (v *VPCCIDRPool) GenerateSubnetPool(prefix int) { + subnetcidrs := []*SubnetCIDR{} + _, vpcSubnet, _ := net.ParseCIDR(v.CIDR) + currentSubnet, _ := cidr.PreviousSubnet(vpcSubnet, prefix) + for { + currentSubnet, finished := cidr.NextSubnet(currentSubnet, prefix) + if !finished && vpcSubnet.Contains(currentSubnet.IP) { + subnetcidr := SubnetCIDR{ + IPNet: currentSubnet, + CIDR: currentSubnet.String(), + } + subnetcidrs = append(subnetcidrs, &subnetcidr) + } else { + break + } + } + v.SubNetPool = subnetcidrs +} + +func (v *VPCCIDRPool) Allocate() *SubnetCIDR { + for _, subnetCIDR := range v.SubNetPool { + if !subnetCIDR.Reserved { + subnetCIDR.Reserved = true + return subnetCIDR + } + } + return nil +} + +// Reserve will reserve the ones you passed as parameter so you won't allocate them again from the pool +func (v *VPCCIDRPool) Reserve(reservedCIDRs ...string) error { + for _, reservedCIDR := range reservedCIDRs { + _, ipnet, err := net.ParseCIDR(reservedCIDR) + if err != nil { + return fmt.Errorf("you passed a wrong CIDR:%s for reserve. %s", reservedCIDR, err) + } + for _, freeCidr := range v.SubNetPool { + if intersect(freeCidr.IPNet, ipnet) { + freeCidr.Reserved = true + } + } + } + return nil +} + +func intersect(n1, n2 *net.IPNet) bool { + return n2.Contains(n1.IP) || n1.Contains(n2.IP) +} diff --git a/pkg/test/vpc_client/elb.go b/pkg/test/vpc_client/elb.go new file mode 100644 index 0000000..1d01732 --- /dev/null +++ b/pkg/test/vpc_client/elb.go @@ -0,0 +1,16 @@ +package vpc_client + +func (vpc *VPC) DeleteVPCELBs() error { + elbs, err := vpc.AWSClient.DescribeLoadBalancers(vpc.VpcID) + if err != nil { + return err + } + + for _, elb := range elbs { + err = vpc.AWSClient.DeleteELB(elb) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/test/vpc_client/helper.go b/pkg/test/vpc_client/helper.go new file mode 100644 index 0000000..e2f8910 --- /dev/null +++ b/pkg/test/vpc_client/helper.go @@ -0,0 +1,45 @@ +package vpc_client + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" +) + +func GetSubnetsRouteTables(routeTables []types.RouteTable, subnetIDs ...string) map[string]*types.RouteTable { + rtMap := map[string]*types.RouteTable{} + + for _, subnetID := range subnetIDs { + rtMap[subnetID] = getSubnetRouteTable(subnetID, routeTables) + } + + return rtMap + +} + +func getSubnetRouteTable( + subnetID string, routeTables []types.RouteTable) *types.RouteTable { + var mainTable *types.RouteTable + for i := range routeTables { + for _, assoc := range routeTables[i].Associations { + if aws.ToString(assoc.SubnetId) == subnetID { + return &routeTables[i] + } + if aws.ToBool(assoc.Main) { + mainTable = &routeTables[i] + } + } + } + // If there is no explicit association, the subnet will be implicitly + // associated with the VPC's main routing table. + return mainTable +} + +func getTagName(tags []types.Tag) string { + name := "" + for _, tag := range tags { + if *tag.Key == "Name" { + name = *tag.Value + } + } + return name +} diff --git a/pkg/test/vpc_client/instance.go b/pkg/test/vpc_client/instance.go new file mode 100644 index 0000000..c816dd7 --- /dev/null +++ b/pkg/test/vpc_client/instance.go @@ -0,0 +1,44 @@ +package vpc_client + +import ( + "strings" + + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (vpc *VPC) TerminateVPCInstances(nonClusterOnly bool) error { + filters := []map[string][]string{ + { + "vpc-id": []string{ + vpc.VpcID, + }, + }, + } + if nonClusterOnly { + filters = append(filters, map[string][]string{ + "tag:Name": { + CON.ProxyName, + CON.BastionName, + }, + }) + } + insts, err := vpc.AWSClient.ListInstances([]string{}, filters...) + + if err != nil { + log.LogError("Error happened when list instances for vpc %s: %s", vpc.VpcID, err) + return err + } + needTermination := []string{} + for _, inst := range insts { + needTermination = append(needTermination, *inst.InstanceId) + } + err = vpc.AWSClient.TerminateInstances(needTermination, true, 20) + if err != nil { + log.LogError("Terminating instances %s meet error: %s", strings.Join(needTermination, ","), err) + } else { + log.LogInfo("Terminating instances %s successfully", strings.Join(needTermination, ",")) + } + return err + +} diff --git a/pkg/test/vpc_client/internet_gateway.go b/pkg/test/vpc_client/internet_gateway.go new file mode 100644 index 0000000..2a2aca2 --- /dev/null +++ b/pkg/test/vpc_client/internet_gateway.go @@ -0,0 +1,40 @@ +package vpc_client + +// PrepareInternetGateway will return the existing internet gateway if there is one attached to the vpc +// Otherwise, it will create a new one and attach to the VPC +func (vpc *VPC) PrepareInternetGateway() (igwID string, err error) { + igws, err := vpc.AWSClient.ListInternetGateWay(vpc.VpcID) + if err != nil { + return "", err + } + if len(igws) != 0 { + return *igws[0].InternetGatewayId, nil + } + igw, err := vpc.AWSClient.CreateInternetGateway() + if err != nil { + return "", err + } + _, err = vpc.AWSClient.AttachInternetGateway(*igw.InternetGateway.InternetGatewayId, vpc.VpcID) + if err != nil { + return "", err + } + return *igw.InternetGateway.InternetGatewayId, nil +} + +func (vpc *VPC) DeleteVPCInternetGateWays() error { + igws, err := vpc.AWSClient.ListInternetGateWay(vpc.VpcID) + if err != nil { + return err + } + for _, igw := range igws { + _, err = vpc.AWSClient.DetachInternetGateway(*igw.InternetGatewayId, vpc.VpcID) + if err != nil { + return err + } + _, err = vpc.AWSClient.DeleteInternetGateway(*igw.InternetGatewayId) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/test/vpc_client/key_pair.go b/pkg/test/vpc_client/key_pair.go new file mode 100644 index 0000000..d06efda --- /dev/null +++ b/pkg/test/vpc_client/key_pair.go @@ -0,0 +1,25 @@ +package vpc_client + +import ( + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (vpc *VPC) CreateKeyPair(keyName string) (*string, error) { + output, err := vpc.AWSClient.CreateKeyPair(keyName) + if err != nil { + return nil, err + } + log.LogInfo("create key pair: %v successfully\n", *output.KeyPairId) + content := output.KeyMaterial + + return content, nil +} + +func (vpc *VPC) DeleteKeyPair(keyName string) error { + _, err := vpc.AWSClient.DeleteKeyPair(keyName) + if err != nil { + return err + } + log.LogInfo("delete key pair successfully\n") + return nil +} diff --git a/pkg/test/vpc_client/nat_gateway.go b/pkg/test/vpc_client/nat_gateway.go new file mode 100644 index 0000000..9994074 --- /dev/null +++ b/pkg/test/vpc_client/nat_gateway.go @@ -0,0 +1,26 @@ +package vpc_client + +import ( + "sync" +) + +func (vpc *VPC) DeleteVPCNatGateways(vpcID string) error { + var delERR error + natGateways, err := vpc.AWSClient.ListNatGateWays(vpcID) + if err != nil { + return err + } + var wg sync.WaitGroup + for _, ngw := range natGateways { + wg.Add(1) + go func(gateWayID string) { + defer wg.Done() + _, err = vpc.AWSClient.DeleteNatGateway(gateWayID, 120) + if err != nil { + delERR = err + } + }(*ngw.NatGatewayId) + } + wg.Wait() + return delERR +} diff --git a/pkg/test/vpc_client/network_acl.go b/pkg/test/vpc_client/network_acl.go new file mode 100644 index 0000000..251a5be --- /dev/null +++ b/pkg/test/vpc_client/network_acl.go @@ -0,0 +1,31 @@ +package vpc_client + +import "github.com/openshift-online/ocm-common/pkg/log" + +func (vpc *VPC) AddSimplyDenyRuleToNetworkACL(port int32, ruleNumber int32) error { + err := vpc.AddNetworkACLRules(true, "6", "deny", ruleNumber, port, port, "0.0.0.0/0") + return err +} + +func (vpc *VPC) AddNetworkACLRules(egress bool, protocol string, ruleAction string, ruleNumber int32, fromPort int32, toPort int32, cidrBlock string) error { + acls, err := vpc.AWSClient.ListNetWorkAcls(vpc.VpcID) + if err != nil { + return err + } + networkAclId := *acls[0].NetworkAclId + log.LogInfo("Find Network ACL" + networkAclId) + _, err = vpc.AWSClient.AddNetworkAclEntry(networkAclId, egress, protocol, ruleAction, ruleNumber, fromPort, toPort, cidrBlock) + return err +} + +func (vpc *VPC) DeleteNetworkACLRules(egress bool, ruleNumber int32) error { + acls, err := vpc.AWSClient.ListNetWorkAcls(vpc.VpcID) + if err != nil { + return err + } + networkAclId := *acls[0].NetworkAclId + log.LogInfo("Find Network ACL" + networkAclId) + + _, err = vpc.AWSClient.DeleteNetworkAclEntry(networkAclId, egress, ruleNumber) + return err +} diff --git a/pkg/test/vpc_client/network_interface.go b/pkg/test/vpc_client/network_interface.go new file mode 100644 index 0000000..0c578df --- /dev/null +++ b/pkg/test/vpc_client/network_interface.go @@ -0,0 +1,16 @@ +package vpc_client + +func (vpc *VPC) DeleteVPCNetworkInterfaces() error { + networkInterfaces, err := vpc.AWSClient.DescribeNetWorkInterface(vpc.VpcID) + if err != nil { + return err + } + + for _, networkInterface := range networkInterfaces { + err = vpc.AWSClient.DeleteNetworkInterface(networkInterface) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/test/vpc_client/proxy.go b/pkg/test/vpc_client/proxy.go new file mode 100644 index 0000000..bc736b6 --- /dev/null +++ b/pkg/test/vpc_client/proxy.go @@ -0,0 +1,124 @@ +package vpc_client + +import ( + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +// LaunchProxyInstance will launch a proxy instance on the indicated zone. +// If set imageID to empty, it will find the proxy image in the ProxyImageMap map +func (vpc *VPC) LaunchProxyInstance(imageID string, zone string, sshKey string) (types.Instance, string, string, error) { + var inst types.Instance + if imageID == "" { + var ok bool + imageID, ok = CON.ProxyImageMap[vpc.Region] + if !ok { + log.LogInfo("Cannot find proxy image of region %s in map ProxyImageMap, will copy from existing region", vpc.Region) + var err error + imageID, err = vpc.CopyImageToProxy(CON.ProxyName) + if err != nil { + log.LogError("Error to copy image ID %s: %s", imageID, err) + return inst, "", "", err + } + //Wait 30 minutes for image to active + result, err := vpc.WaitImageToActive(imageID, 30) + if err != nil || !result { + log.LogError("Error wait image %s to active %s", imageID, err) + return inst, "", "", err + } + } + } + + pubSubnet, err := vpc.PreparePublicSubnet(zone) + if err != nil { + log.LogInfo("Error preparing a subnet in current zone %s with image ID %s: %s", zone, imageID, err) + return inst, "", "", err + } + SGID, err := vpc.CreateAndAuthorizeDefaultSecurityGroupForProxy() + if err != nil { + log.LogError("Prepare SG failed for the proxy preparation %s", err) + return inst, "", "", err + } + + instOut, err := vpc.AWSClient.LaunchInstance(pubSubnet.ID, imageID, 1, "t3.medium", CON.InstanceKeyName, []string{SGID}, true) + if err != nil { + log.LogError("Launch proxy instance failed %s", err) + return inst, "", "", err + } else { + log.LogInfo("Launch proxy instance %s succeed", *instOut.Instances[0].InstanceId) + } + tags := map[string]string{ + "Name": CON.ProxyName, + } + instID := *instOut.Instances[0].InstanceId + _, err = vpc.AWSClient.TagResource(instID, tags) + if err != nil { + return inst, "", "", fmt.Errorf("tag instance %s failed:%s", instID, err) + } + + publicIP, err := vpc.AWSClient.AllocateEIPAndAssociateInstance(instID) + if err != nil { + log.LogError("Prepare EIP failed for the proxy preparation %s", err) + return inst, "", "", err + } + log.LogInfo("Prepare EIP successfully for the proxy preparation. Launch with IP: %s", publicIP) + + time.Sleep(2 * time.Minute) + cmd1 := "http_proxy=127.0.0.1:8080 curl http://mitm.it/cert/pem -s > mitm-ca.pem" + cmd2 := "cat mitm-ca.pem" + hostname := fmt.Sprintf("%s:22", publicIP) + _, err = Exec_CMD(CON.AWSInstanceUser, sshKey, hostname, cmd1) + if err != nil { + log.LogError("login instance to run cmd %s failed %s", cmd1, err) + return inst, "", "", err + } + caContent, err := Exec_CMD(CON.AWSInstanceUser, sshKey, hostname, cmd2) + if err != nil { + log.LogError("login instance to run cmd %s failed %s", cmd2, err) + return inst, "", "", err + } + + return instOut.Instances[0], *instOut.Instances[0].PrivateIpAddress, caContent, err +} + +func (vpc *VPC) CopyImageToProxy(name string) (destinationImageID string, err error) { + sourceRegion := "us-west-2" + sourceImageID, ok := CON.ProxyImageMap[sourceRegion] + if !ok { + log.LogError("Can't find image from region %s :%s", sourceRegion, err) + return "", err + } + destinationImageID, err = vpc.AWSClient.CopyImage(sourceImageID, sourceRegion, name) + if err != nil { + log.LogError("Copy image %s meet error %s", sourceImageID, err) + return "", err + } + return destinationImageID, nil +} + +func (vpc *VPC) WaitImageToActive(imageID string, timeout time.Duration) (imageAvailable bool, err error) { + + startTime := time.Now() + imageAvailable = false + for time.Now().Before(startTime.Add(timeout * time.Minute)) { + output, err := vpc.AWSClient.DescribeImage(imageID) + if err != nil { + log.LogError("Error happened when describe image status: %s", imageID) + return imageAvailable, err + } + if string(output.Images[0].State) == "available" { + imageAvailable = true + return imageAvailable, nil + } + log.LogInfo("Wait for image %s status to active", imageID) + time.Sleep(time.Minute) + } + err = fmt.Errorf("timeout for waiting image active") + return imageAvailable, err + +} diff --git a/pkg/test/vpc_client/route_table.go b/pkg/test/vpc_client/route_table.go new file mode 100644 index 0000000..c9c9228 --- /dev/null +++ b/pkg/test/vpc_client/route_table.go @@ -0,0 +1,44 @@ +package vpc_client + +func (vpc *VPC) DescribeSubnetRTMappings(subnets ...*Subnet) error { + rts, err := vpc.AWSClient.ListCustomerRouteTables(vpc.VpcID) + if err != nil { + return err + } + for _, subnet := range subnets { + subnet.RTable = getSubnetRouteTable(subnet.ID, rts) + } + + return nil +} + +// DeleteVPCRouteTables will delete all of route table resources including associations and routes +func (vpc *VPC) DeleteVPCRouteTables(vpcID string) error { + rts, err := vpc.AWSClient.ListCustomerRouteTables(vpcID) + if err != nil { + return err + } + for _, rt := range rts { + for _, asso := range rt.Associations { + _, err = vpc.AWSClient.DisassociateRouteTableAssociation(*asso.RouteTableAssociationId) + if err != nil { + return err + } + } + err = vpc.AWSClient.DeleteRouteTable(*rt.RouteTableId) + if err != nil { + return err + } + } + return nil +} + +func (vpc *VPC) DeleteRouteTableChains(routeTables ...string) error { + for _, routeTable := range routeTables { + err := vpc.AWSClient.DeleteRouteTableChain(routeTable) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/test/vpc_client/security_group.go b/pkg/test/vpc_client/security_group.go new file mode 100644 index 0000000..3339aa8 --- /dev/null +++ b/pkg/test/vpc_client/security_group.go @@ -0,0 +1,61 @@ +package vpc_client + +import ( + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (vpc *VPC) DeleteVPCSecurityGroups(customizedOnly bool) error { + needCleanGroups := []types.SecurityGroup{} + securityGroups, err := vpc.AWSClient.ListSecurityGroups(vpc.VpcID) + if customizedOnly { + for _, sg := range securityGroups { + for _, tag := range sg.Tags { + if *tag.Key == "Name" && (*tag.Value == CON.ProxySecurityGroupName || *tag.Value == CON.AdditionalSecurityGroupName) { + needCleanGroups = append(needCleanGroups, sg) + } + } + } + } else { + needCleanGroups = securityGroups + } + if err != nil { + return err + } + for _, sg := range needCleanGroups { + _, err = vpc.AWSClient.DeleteSecurityGroup(*sg.GroupId) + if err != nil { + return err + } + } + return nil +} + +func (vpc *VPC) CreateAndAuthorizeDefaultSecurityGroupForProxy() (string, error) { + var groupID string + var err error + protocol := CON.TCPProtocol + resp, err := vpc.AWSClient.CreateSecurityGroup(vpc.VpcID, CON.ProxySecurityGroupName, CON.ProxySecurityGroupDescription) + if err != nil { + log.LogError("Create proxy security group failed for vpc %s: %s", vpc.VpcID, err) + return "", err + } + groupID = *resp.GroupId + log.LogInfo("SG %s created for vpc %s", groupID, vpc.VpcID) + cidrPortsMap := map[string]int32{ + vpc.CIDRValue: 8080, + "0.0.0.0/0": 22, + } + for cidr, port := range cidrPortsMap { + _, err = vpc.AWSClient.AuthorizeSecurityGroupIngress(groupID, cidr, protocol, port, port) + if err != nil { + log.LogError("Authorize CIDR %s with port %v failed to SG %s of vpc %s: %s", + cidr, port, groupID, vpc.VpcID, err) + return groupID, err + } + } + log.LogInfo("Authorize SG %s successfully for proxy.", groupID) + + return groupID, err +} diff --git a/pkg/test/vpc_client/ssh.go b/pkg/test/vpc_client/ssh.go new file mode 100644 index 0000000..6409a4f --- /dev/null +++ b/pkg/test/vpc_client/ssh.go @@ -0,0 +1,59 @@ +package vpc_client + +import ( + "bytes" + "fmt" + "os" + + sshclient "golang.org/x/crypto/ssh" +) + +func Exec_CMD(userName, keyPath string, addr string, cmd string) (result string, err error) { + authMethod, err := publicKeyAuthFunc(keyPath) + if err != nil { + return "", err + } + config := &sshclient.ClientConfig{ + User: userName, + Auth: []sshclient.AuthMethod{ + authMethod, + }, + HostKeyCallback: sshclient.InsecureIgnoreHostKey(), + } + + client, err := sshclient.Dial("tcp", addr, config) + if err != nil { + err = fmt.Errorf("failed to dail %s", err) + return "", err + } + defer client.Close() + session, err := client.NewSession() + if err != nil { + err = fmt.Errorf("failed to create session %s", err) + return "", err + } + defer session.Close() + + var b bytes.Buffer + session.Stdout = &b + + if err := session.Run(cmd); err != nil { + err = fmt.Errorf("failed to run command %s", err) + return "", err + } + return b.String(), nil +} + +func publicKeyAuthFunc(keyPath string) (sshclient.AuthMethod, error) { + key, err := os.ReadFile(keyPath) + if err != nil { + err = fmt.Errorf("ssh key file read failed %s", err) + return nil, err + } + signer, err := sshclient.ParsePrivateKey(key) + if err != nil { + err = fmt.Errorf("ssh key sinher failed %s", err) + return nil, err + } + return sshclient.PublicKeys(signer), nil +} diff --git a/pkg/test/vpc_client/subnet.go b/pkg/test/vpc_client/subnet.go new file mode 100644 index 0000000..9289e95 --- /dev/null +++ b/pkg/test/vpc_client/subnet.go @@ -0,0 +1,466 @@ +package vpc_client + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (subnet *Subnet) IsPublic() bool { + if subnet.RTable == nil { + return false + } + for _, route := range subnet.RTable.Routes { + if strings.HasPrefix(aws.ToString(route.GatewayId), "igw") { + // There is no direct way in the AWS API to determine if a subnet is public or private. + // A public subnet is one which has an internet gateway route + // we look for the gatewayId and make sure it has the prefix of igw to differentiate + // from the default in-subnet route which is called "local" + // or other virtual gateway (starting with vgv) + // or vpc peering connections (starting with pcx). + return true + } + } + return false +} + +func (subnet *Subnet) IsNatgatwatEnabled() bool { + if subnet.RTable == nil { + return false + } + for _, route := range subnet.RTable.Routes { + if route.NatGatewayId != nil { + return true + } + } + return false +} + +// PrepareNatGatway will return a NAT gateway if existing no matter which zone set +// zone only work when create public subnet once no NAT gate way existing +// Will implement zone supporting for nat gateway in future. But for now, there is no requirement +func (vpc *VPC) PrepareNatGatway(zone string) (types.NatGateway, error) { + var gateWay types.NatGateway + natGatways, err := vpc.AWSClient.ListNatGateWays(vpc.VpcID) + if err != nil { + return gateWay, err + } + if len(natGatways) != 0 { + gateWay = natGatways[0] + log.LogInfo("Found existing nat gateway: %s", *gateWay.NatGatewayId) + err = vpc.AWSClient.WaitForResourceExisting(*gateWay.NatGatewayId, 10*60) + + } else { + allocation, err := vpc.AWSClient.AllocateEIPAddress() + if err != nil { + return gateWay, fmt.Errorf("error happened when allocate EIP Address for NAT gateway: %s", err) + } + publicSubnet, err := vpc.PreparePublicSubnet(zone) + if err != nil { + return gateWay, fmt.Errorf("error happened when prepare public subnet for NAT gateway: %s", err) + } + natGatway, err := vpc.AWSClient.CreateNatGateway(publicSubnet.ID, *allocation.AllocationId, vpc.VpcID) + if err != nil { + return gateWay, fmt.Errorf("error happened when prepare NAT gateway: %s", err) + } + gateWay = *natGatway.NatGateway + } + + return gateWay, err +} +func (vpc *VPC) PreparePublicSubnet(zone string) (*Subnet, error) { + if vpc.SubnetList != nil { + for _, subnet := range vpc.SubnetList { + if !subnet.Private { + return subnet, nil + } + } + } + subnets, err := vpc.ListSubnets() + if err != nil { + return nil, fmt.Errorf("error happened when list subnet of VPC: %s. %s", vpc.VpcID, err.Error()) + } + for _, subnet := range subnets { + if !subnet.Private && subnet.Zone == zone { + return subnet, nil + } + } + subnet, err := vpc.CreatePublicSubnet(zone) + if err != nil { + return nil, fmt.Errorf("error happened when create public subnet of VPC: %s. %s", vpc.VpcID, err.Error()) + } + return subnet, nil +} + +// CreatePrivateSubnet will create a private subnet +// if natEnabled then , it will prepare a public subnet and create a NATgatway to the public subnet +func (vpc *VPC) CreatePrivateSubnet(zone string, natEnabled bool) (*Subnet, error) { + subNetName := strings.Join([]string{ + vpc.VPCName, + "private", + zone, + }, "-") + tags := map[string]string{ + "Name": subNetName, + CON.PrivateLBTag: CON.LBTagValue, + } + respRouteTable, err := vpc.AWSClient.CreateRouteTable(vpc.VpcID) + if err != nil { + return nil, err + } + + subnet, err := vpc.CreateSubnet(zone) + if err != nil { + return nil, err + } + + _, err = vpc.AWSClient.AssociateRouteTable(*respRouteTable.RouteTable.RouteTableId, subnet.ID, vpc.VpcID) + if err != nil { + return nil, err + } + + subnet.RTable = respRouteTable.RouteTable + + if natEnabled { + natGateway, err := vpc.PrepareNatGatway(zone) + if err != nil { + return nil, fmt.Errorf("prepare nat gateway for private cluster failed. %s", err.Error()) + } + route, err := vpc.AWSClient.CreateRoute(*respRouteTable.RouteTable.RouteTableId, *natGateway.NatGatewayId) + if err != nil { + return subnet, fmt.Errorf("error happens when create route NAT gateway route to subnet: %s, %s", subnet.ID, err.Error()) + } + subnet.RTable.Routes = append(subnet.RTable.Routes, *route) + } + _, err = vpc.AWSClient.TagResource(subnet.ID, tags) + if err != nil { + return subnet, fmt.Errorf("tag subnet %s failed:%s", subnet.ID, err) + } + return subnet, err +} + +// CreatePublicSubnet create one public subnet, and related route table, internet gateway, routes. DO NOT include vpc creation. +// Inputs: +// +// vpcID should be provided, e.g. "vpc-0287d4a924e9f35d9". +// allocationID is an eip allocate ID, e.g. "eipalloc-0efc1c0ceff5339a2". +// region is a string of the AWS region. If this value is empty, the default region is "us-east-2". +// zone is a string. If this value is empty, the default zone is "a". +// data a VPC struct containing the ids for recording create resources ids and needed while deleting. +// subnetCidr is a string, e.g. "10.190.1.0/24". +// +// Output: +// +// If success, a Subnet struct containing the subnetID, private=false, subnetCidr, region, zone and VpcID. +// Otherwise, nil and an error from the call. +func (vpc *VPC) CreatePublicSubnet(zone string) (*Subnet, error) { + subNetName := strings.Join([]string{ + vpc.VPCName, + "public", + zone, + }, "-") + tags := map[string]string{ + "Name": subNetName, + CON.PublicSubNetTagKey: CON.PublicSubNetTagValue, + CON.PublicLBTag: CON.LBTagValue, + } + subnet, err := vpc.CreateSubnet(zone) + if err != nil { + return nil, fmt.Errorf("create subnet meets error:%s", err) + } + + respRouteTable, err := vpc.AWSClient.CreateRouteTable(vpc.VpcID) + if err != nil { + return nil, fmt.Errorf("create RouteTable failed %s", err.Error()) + } + _, err = vpc.AWSClient.AssociateRouteTable(*respRouteTable.RouteTable.RouteTableId, subnet.ID, vpc.VpcID) + if err != nil { + return nil, fmt.Errorf("associate route table failed %s", err.Error()) + } + subnet.RTable = respRouteTable.RouteTable + //data.AssociationRouteTableSubnetIDs.AssociationID = append(data.AssociationRouteTableSubnetIDs.AssociationID, *respAssociateRT.AssociationId) + igwid, err := vpc.PrepareInternetGateway() + if err != nil { + return nil, fmt.Errorf("prepare internet gatway failed for vpc: %s", err) + } + + route, err := vpc.AWSClient.CreateRoute(*respRouteTable.RouteTable.RouteTableId, igwid) + if err != nil { + return nil, fmt.Errorf("create route failed for rt %s: %s", *respRouteTable.RouteTable.RouteTableId, err) + } + subnet.RTable.Routes = append(subnet.RTable.Routes, *route) + subnet.Private = false + _, err = vpc.AWSClient.TagResource(subnet.ID, tags) + if err != nil { + return subnet, fmt.Errorf("tag subnet %s failed:%s", subnet.ID, err) + } + return subnet, err +} + +// CreatePairSubnet create one public subnet one private subnet, and related route table, internet gateway, nat gateway, routes. DO NOT include vpc creation. +// Inputs: +// +// zone: which zone you prefer to create the subnets +// +// Output: +// +// If success, a VPC struct containing the ids of the created resources and nil. +// Otherwise, nil and an error from the call. +func (vpc *VPC) CreatePairSubnet(zone string) (*VPC, []*Subnet, error) { + publicSubnet, err := vpc.CreatePublicSubnet(zone) + if err != nil { + log.LogError("Create public subnet failed" + err.Error()) + return vpc, nil, err + } + privateSubnet, err := vpc.CreatePrivateSubnet(zone, true) + if err != nil { + log.LogError("Create private subnet failed" + err.Error()) + return vpc, nil, err + } + return vpc, []*Subnet{publicSubnet, privateSubnet}, err +} + +// PreparePairSubnetByZone will return current pair subents once existing, +// Otherwise it will create a pair. +// If single one missing, it will create another one based on the zone +func (vpc *VPC) PreparePairSubnetByZone(zone string) (map[string]*Subnet, error) { + log.LogInfo("Going to prepare") + result := map[string]*Subnet{} + for _, subnet := range vpc.SubnetList { + trimedSubnetZone := strings.Split(subnet.Zone, vpc.Region)[1] // Cannot use Trim because it will remove the whole string if the zone is ap-northeast-1a and region is ap-northeast-1 + if trimedSubnetZone == "" { + log.LogError("Got empty trimed zone. But the subnet zone is: %s, vpc region is: %s", subnet.Zone, vpc.Region) + } + log.LogInfo("Subnet %s in zone: %s, and trimed zone %s and region %s", subnet.Name, subnet.Zone, trimedSubnetZone, vpc.Region) + if trimedSubnetZone == zone { + if subnet.Private { + if _, ok := result["private"]; !ok { + log.LogInfo("Got private subnet %s and set it to the result", subnet.ID) + if subnet.IsNatgatwatEnabled() { + result["private"] = subnet + } + } + } else { + if _, ok := result["public"]; !ok { + log.LogInfo("Got public subnet %s and set it to the result", subnet.ID) + result["public"] = subnet + } + } + } + } + + if _, ok := result["public"]; !ok { + log.LogInfo("Got no public subnet for current zone %s, going to create one", zone) + subnet, err := vpc.CreatePublicSubnet(zone) + if err != nil { + log.LogError("Prepare public subnet failed for zone %s: %s", zone, err) + return nil, fmt.Errorf("prepare public subnet failed for zone %s: %s", zone, err) + } + result["public"] = subnet + } + if _, ok := result["private"]; !ok { + log.LogInfo("Got no proper private subnet for current zone %s, going to create one", zone) + subnet, err := vpc.CreatePrivateSubnet(zone, true) + if err != nil { + log.LogError("Prepare private subnet failed for zone %s: %s", zone, err) + return nil, fmt.Errorf("prepare private subnet failed for zone %s: %s", zone, err) + } + result["private"] = subnet + } + + return result, nil +} + +// CreateMultiZoneSubnet create private and public subnet in multi zones, and related route table, internet gateway, nat gateway, routes. DO NOT include vpc creation. +// Inputs: +// +// vpcID should be provided, e.g. "vpc-0287d4a924e9f35d9". +// allocationID is an eip allocate ID, e.g. "eipalloc-0efc1c0ceff5339a2". +// region is a string of the AWS region. If this value is empty, the default region is "us-east-2". +// zone is a slice. Need provide more than 1 zones. +// data a VPC struct containing the ids for recording create resources ids and needed while deleting. +// subnetCidr is a slice, 2 times the number of subnetCidrs compared to the number of zones should be provided. +// +// Output: +// +// If success, a VPC struct containing the ids of the created resources and nil. +// Otherwise, nil and an error from the call. +func (vpc *VPC) CreateMultiZoneSubnet(zones ...string) error { + var wg sync.WaitGroup + var err error + for index, zone := range zones { + wg.Add(1) + go func(targetzone string, sleeping int) { + defer wg.Done() + time.Sleep(time.Duration(sleeping) * 2 * time.Second) + _, _, innererr := vpc.CreatePairSubnet(targetzone) + if innererr != nil { + err = innererr + log.LogError("Create subnets meets error %s", err.Error()) + } + }(zone, index) + } + wg.Wait() + return err +} + +func (vpc *VPC) CreateSubnet(zone string) (*Subnet, error) { + if zone == "" { + zone = CON.DefaultAWSZone + } + + subnetcidr := vpc.CIDRPool.Allocate().CIDR + respCreateSubnet, err := vpc.AWSClient.CreateSubnet(vpc.VpcID, vpc.Region, zone, subnetcidr) + if err != nil { + log.LogError("create subnet error " + err.Error()) + return nil, err + } + err = vpc.AWSClient.WaitForResourceExisting(*respCreateSubnet.SubnetId, 4) + + if err != nil { + + return nil, err + } + + log.LogInfo("Created subnet with ID " + *respCreateSubnet.SubnetId) + subnet := &Subnet{ + ID: *respCreateSubnet.SubnetId, + Private: true, + Zone: fmt.Sprintf(vpc.Region + zone), + Cidr: subnetcidr, + Region: vpc.Region, + VpcID: vpc.VpcID, + } + vpc.SubnetList = append(vpc.SubnetList, subnet) + return subnet, err +} + +// ListIndicatedSubnetsByVPC will returns the indicated type of subnets like public, private +func (vpc *VPC) ListIndicatedSubnetsByVPC(private bool) ([]string, error) { + results := []string{} + subnetDetails, err := vpc.ListSubnets() + if err != nil { + return nil, err + } + for _, subnet := range subnetDetails { + if subnet.Private == private { + results = append(results, subnet.ID) + } + + } + return results, nil +} + +// ListIndicatedSubnetsByVPC will returns the indicated type of subnets like public, private +func (vpc *VPC) FindIndicatedSubnetsBysubnets(private bool, subnetIDs ...string) ([]string, error) { + subnets, err := vpc.ListSubnets() + if err != nil { + return nil, err + } + results := []string{} + for _, requiredSubnet := range subnetIDs { + for _, subnet := range subnets { + if subnet.ID == requiredSubnet && subnet.Private == private { + results = append(results, subnet.ID) + } + } + } + + return results, nil +} + +func (vpc *VPC) DeleteVPCSubnets() error { + subnets, err := vpc.AWSClient.ListSubnetByVpcID(vpc.VpcID) + if err != nil { + return err + } + for _, subnet := range subnets { + _, err = vpc.AWSClient.DeleteSubnet(*subnet.SubnetId) + if err != nil { + return err + } + } + return nil +} + +func (vpc *VPC) ListSubnets() ([]*Subnet, error) { + log.LogInfo("Trying to list subnets of the vpc") + subnets := []*Subnet{} + awsSubnets, err := vpc.AWSClient.ListSubnetByVpcID(vpc.VpcID) + if err != nil { + log.LogError(err.Error()) + return subnets, err + } + log.LogInfo("Got %d subnets", len(awsSubnets)) + for _, sub := range awsSubnets { + subnetName := getTagName(sub.Tags) + + subnet := NewSubnet(). + SetID(*sub.SubnetId). + SetZone(*sub.AvailabilityZone). + SetCidr(*sub.CidrBlock). + SetVpcID(*sub.VpcId). + SetName(subnetName). + SetRegion(vpc.Region) + + subnets = append(subnets, subnet) + log.LogInfo(*sub.SubnetId + "\t" + *sub.CidrBlock + "\t" + *sub.AvailabilityZone + "\t") + + } + vpc.SubnetList = subnets + err = vpc.DescribeSubnetRTMappings(vpc.SubnetList...) + for _, subnet := range vpc.SubnetList { + subnet.Private = !subnet.IsPublic() + } + return subnets, err +} + +// UniqueSubnet will return a unique subnet by the subnetID +// It contains more values including the CIDR values +func (vpc *VPC) UniqueSubnet(subnetID string) *Subnet { + var subnet *Subnet + for _, sub := range vpc.SubnetList { + if sub.ID == subnetID { + subnet = sub + break + } + } + return subnet +} + +// AllSubnetIDs will return all if the subnet IDs of the vpc instance +func (vpc *VPC) AllSubnetIDs() []string { + subnetIDs := []string{} + for _, subnet := range vpc.SubnetList { + subnetIDs = append(subnetIDs, subnet.ID) + } + return subnetIDs +} + +// AllPublicSubnetIDs will return all of the subnet IDs in vpc instance +func (vpc *VPC) AllPublicSubnetIDs() []string { + subnetIDs := []string{} + for _, subnet := range vpc.SubnetList { + if !subnet.Private { + subnetIDs = append(subnetIDs, subnet.ID) + } + } + return subnetIDs +} + +// AllPublicSubnetIDs will return all of the subnet IDs in vpc instance +func (vpc *VPC) AllPrivateSubnetIDs() []string { + subnetIDs := []string{} + for _, subnet := range vpc.SubnetList { + if subnet.Private { + subnetIDs = append(subnetIDs, subnet.ID) + } + } + return subnetIDs +} diff --git a/pkg/test/vpc_client/types.go b/pkg/test/vpc_client/types.go new file mode 100644 index 0000000..f72d736 --- /dev/null +++ b/pkg/test/vpc_client/types.go @@ -0,0 +1,124 @@ +package vpc_client + +import ( + "net" + + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/openshift-online/ocm-common/pkg/aws/aws_client" +) + +// ************************* CIDR Pool ************************* +type SubnetCIDR struct { + Reserved bool + CIDR string + IPNet *net.IPNet +} + +type VPCCIDRPool struct { + CIDR string + Prefix int + SubNetPool []*SubnetCIDR +} + +// ************************** Subnet **************************** +type Subnet struct { + ID string + Private bool + Zone string + Cidr string + Region string + VpcID string + Name string + RTable *types.RouteTable +} + +func NewSubnet() *Subnet { + return &(Subnet{}) +} +func (subnet *Subnet) SetID(id string) *Subnet { + subnet.ID = id + return subnet +} + +func (subnet *Subnet) SetPrivate(private bool) *Subnet { + subnet.Private = private + return subnet +} + +func (subnet *Subnet) SetZone(zone string) *Subnet { + subnet.Zone = zone + return subnet +} + +func (subnet *Subnet) SetVpcID(vpcID string) *Subnet { + subnet.VpcID = vpcID + return subnet +} + +func (subnet *Subnet) SetRegion(region string) *Subnet { + subnet.Region = region + return subnet +} + +func (subnet *Subnet) SetCidr(cidr string) *Subnet { + subnet.Cidr = cidr + return subnet +} + +func (subnet *Subnet) SetName(name string) *Subnet { + subnet.Name = name + return subnet +} + +// **************************** VPC ****************************** +type VPC struct { + AWSClient *aws_client.AWSClient + VpcID string + VPCName string + CIDRValue string + CIDRPool *VPCCIDRPool + SubnetList []*Subnet + Region string +} + +func NewVPC() *VPC { + vpc := &VPC{} + return vpc +} + +func (vpc *VPC) Name(name string) *VPC { + vpc.VPCName = name + return vpc +} +func (vpc *VPC) ID(id string) *VPC { + vpc.VpcID = id + return vpc +} + +func (vpc *VPC) AWSclient(awsClient *aws_client.AWSClient) *VPC { + vpc.AWSClient = awsClient + return vpc +} + +func (vpc *VPC) CIDR(cidr string) *VPC { + vpc.CIDRValue = cidr + return vpc +} + +func (vpc *VPC) CIDRpool(cidrPool *VPCCIDRPool) *VPC { + vpc.CIDRPool = cidrPool + return vpc +} +func (vpc *VPC) NewCIDRPool() *VPC { + vpc.CIDRPool = NewCIDRPool(vpc.CIDRValue) + return vpc +} + +func (vpc *VPC) Subnets(subnet ...*Subnet) *VPC { + vpc.SubnetList = subnet + return vpc +} +func (vpc *VPC) SetRegion(region string) *VPC { + vpc.Region = region + return vpc +} diff --git a/pkg/test/vpc_client/vpc.go b/pkg/test/vpc_client/vpc.go new file mode 100644 index 0000000..cbd9bb1 --- /dev/null +++ b/pkg/test/vpc_client/vpc.go @@ -0,0 +1,251 @@ +package vpc_client + +import ( + "fmt" + "strings" + + "github.com/openshift-online/ocm-common/pkg/aws/aws_client" + + CON "github.com/openshift-online/ocm-common/pkg/aws/consts" + "github.com/openshift-online/ocm-common/pkg/log" +) + +// GenerateVPCBySubnet will return a VPC with CIDRpool and subnets based on one of the subnet ID +func (vpc *VPC) GenerateVPCBySubnet(subnetID string) (*VPC, error) { + log.LogInfo("Trying to load vpc from AWS by subnet: %s", subnetID) + subnetDetail, err := vpc.AWSClient.ListSubnetDetail(subnetID) + if err != nil { + log.LogError("List subnet detail meets error: %s", err) + return nil, err + } + log.LogInfo("Subnet info loaded from AWS by subnet: %s", subnetID) + vpc, err = GenerateVPCByID(vpc.AWSClient, *subnetDetail[0].VpcId) + log.LogInfo("VPC info loaded from AWS by subnet: %s", subnetID) + return vpc, err +} + +// CreateVPCChain create a complete set of web resources, including eip, vpc, subnet, route table, internet gateway, nat gateway, routes +// Inputs: +// +// vpcCidr is a string of the vpc's cidr, e.g. "10.190.0.0/16". +// region is a string of the AWS region. If this value is empty, the default region is "us-east-2". +// zone is a slice. If only one subnet should be created, the first zone should be selected. If this value is empty, the default zone is "a". +// If success, a VPC struct containing the ids of the created resources and nil. +// Otherwise, nil and an error from the call. +func (vpc *VPC) CreateVPCChain(zones ...string) (*VPC, error) { + log.LogInfo("Going to create vpc and the follow resources on zones: %s", strings.Join(zones, ",")) + respVpc, err := vpc.AWSClient.CreateVpc(vpc.CIDRValue, vpc.VPCName) + if err != nil { + log.LogError("Create vpc meets error: %s ", err.Error()) + return nil, err + } + log.LogInfo("VPC created on AWS with id: %s", *respVpc.Vpc.VpcId) + _, err = vpc.AWSClient.ModifyVpcDnsAttribute(*respVpc.Vpc.VpcId, CON.VpcDnsHostnamesAttribute, true) + if err != nil { + log.LogError("Modify Vpc failed: %s ", err.Error()) + return nil, err + } + log.LogInfo("VPC DNS Updated on AWS with id: %s", *respVpc.Vpc.VpcId) + vpc = vpc.ID(*respVpc.Vpc.VpcId) + _, err = vpc.PrepareInternetGateway() + if err != nil { + log.LogError("Prepare Vpc internet gateway failed: %s ", err.Error()) + return vpc, err + } + log.LogInfo("Prepare vpc internetgateway for vpc %s", *respVpc.Vpc.VpcId) + err = vpc.CreateMultiZoneSubnet(zones...) + if err != nil { + log.LogError("Create subnets meets error: %s", err.Error()) + } else { + log.LogInfo("Create subnets successfully") + } + return vpc, err +} + +func (vpc *VPC) DeleteVPCChain(totalClean ...bool) error { + vpcID := vpc.VpcID + if vpcID == "" { + return fmt.Errorf("got empty vpc ID to clean. Make sure you loaded it from AWS") + } + log.LogInfo("Going to delete the vpc and follow resources by ID: %s", vpcID) + log.LogInfo("Going to terminate proxy instances if existing") + err := vpc.TerminateVPCInstances(true) + if err != nil { + log.LogError("Delete vpc instances meets error: %s", err.Error()) + return err + } + log.LogInfo("Delete vpc instances successfully") + + log.LogInfo("Going to delete proxy security group") + err = vpc.DeleteVPCSecurityGroups(true) + if err != nil { + log.LogError("Delete vpc proxy security group meets error: %s", err.Error()) + return err + } + log.LogInfo("Delete vpc vpc proxy security group successfully") + + err = vpc.DeleteVPCRouteTables(vpcID) + if err != nil { + log.LogError("Delete vpc route tables meets error: %s", err.Error()) + return err + } + log.LogInfo("Delete vpc route tables successfully") + + err = vpc.DeleteVPCNatGateways(vpcID) + if err != nil { + log.LogError("Delete vpc nat gatways meets error: %s", err.Error()) + return err + } + + log.LogInfo("Delete vpc nat gateways successfully") + err = vpc.AWSClient.DeleteVPCEndpoints(vpc.VpcID) + if err != nil { + log.LogError("Delete vpc endpoints meets error: %s", err.Error()) + return err + } + if len(totalClean) == 1 && totalClean[0] { + log.LogInfo("Got total clean set, going to delete other possible resource leak") + err = vpc.DeleteVPCELBs() + if err != nil { + log.LogError("Delete vpc load balancers meets error: %s", err.Error()) + return err + } + err = vpc.DeleteVPCSecurityGroups(false) + if err != nil { + log.LogError("Delete vpc security groups meets error: %s", err.Error()) + return err + } + } + err = vpc.DeleteVPCNetworkInterfaces() + if err != nil { + log.LogError("Delete vpc network interfaces meets error: %s", err.Error()) + return err + } + + err = vpc.DeleteVPCInternetGateWays() + if err != nil { + log.LogError("Delete vpc internet gatways meets error: %s", err.Error()) + return err + } + + err = vpc.DeleteVPCSubnets() + if err != nil { + log.LogError("Delete vpc subnets meets error: %s", err.Error()) + return err + } + + _, err = vpc.AWSClient.DeleteVpc(vpc.VpcID) + if err != nil { + log.LogError("Delete vpc meets error: %s", err.Error()) + return err + } + return nil +} + +// PrepareVPC will find a vpc named +// If there is no vpc in the name +// It will Create vpc with the name in the region +// checkExisting means if you want to check current existing vpc to re-use. +// Just be careful once you use checkExisting, the vpc may have subnets not existing in your zones. And maybe multi subnets in the zones +// Try vpc.PreparePairSubnets by zone for further implementation to get a pair of +// Zones will be customized if you want. Otherwise, it will use the default zone "a" +func PrepareVPC(vpcName string, region string, vpcCIDR string, checkExisting bool, zones ...string) (*VPC, error) { + if vpcCIDR == "" { + vpcCIDR = CON.DefaultVPCCIDR + } + log.LogInfo("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, ",")) + awsclient, err := aws_client.CreateAWSClient("", region) + 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(awsclient, vpcID) + 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 + +// GenerateVPCByID will return a VPC with CIDRpool and subnets +// If you know the vpc ID on AWS, then try to generate it +func GenerateVPCByID(awsClient *aws_client.AWSClient, vpcID string) (*VPC, error) { + 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(awsClient *aws_client.AWSClient, subnetID string) (*VPC, error) { + subnetDetail, err := awsClient.ListSubnetDetail(subnetID) + if err != nil { + return nil, err + } + vpc, err := GenerateVPCByID(awsClient, *subnetDetail[0].VpcId) + return vpc, err +}