From 747aaa383b323fa371f9c0df557a96755b2b0192 Mon Sep 17 00:00:00 2001
From: yingzhan <yingzhan@redhat.com>
Date: Fri, 12 Apr 2024 19:29:07 +0800
Subject: [PATCH] OCM-6311 | feat: Migrate common code to ocm-common repo

---
 go.mod                                   |  23 +-
 go.sum                                   |  54 ++-
 pkg/aws/aws_client/client.go             | 106 ++++++
 pkg/aws/aws_client/eip.go                |  89 +++++
 pkg/aws/aws_client/elb.go                |  42 ++
 pkg/aws/aws_client/env.go                |  13 +
 pkg/aws/aws_client/image.go              |  35 ++
 pkg/aws/aws_client/instance.go           | 256 +++++++++++++
 pkg/aws/aws_client/internet_gateway.go   |  87 +++++
 pkg/aws/aws_client/key_pair.go           |  39 ++
 pkg/aws/aws_client/kms.go                | 103 +++++
 pkg/aws/aws_client/nat_gateway.go        |  72 ++++
 pkg/aws/aws_client/network_acl.go        |  73 ++++
 pkg/aws/aws_client/network_interface.go  |  54 +++
 pkg/aws/aws_client/oidc.go               |  15 +
 pkg/aws/aws_client/policy.go             | 163 ++++++++
 pkg/aws/aws_client/resource.go           | 334 ++++++++++++++++
 pkg/aws/aws_client/role.go               |  91 +++++
 pkg/aws/aws_client/route53.go            |  49 +++
 pkg/aws/aws_client/route_table.go        | 198 ++++++++++
 pkg/aws/aws_client/security_group.go     | 180 +++++++++
 pkg/aws/aws_client/subnet.go             | 114 ++++++
 pkg/aws/aws_client/tag.go                |  54 +++
 pkg/aws/aws_client/volume.go             |  20 +
 pkg/aws/aws_client/vpc.go                | 167 ++++++++
 pkg/aws/consts/consts.go                 |  75 ++++
 pkg/log/logger.go                        |  31 ++
 pkg/test/vpc_client/bastion.go           |  89 +++++
 pkg/test/vpc_client/cidr.go              |  71 ++++
 pkg/test/vpc_client/elb.go               |  16 +
 pkg/test/vpc_client/helper.go            |  45 +++
 pkg/test/vpc_client/instance.go          |  44 +++
 pkg/test/vpc_client/internet_gateway.go  |  40 ++
 pkg/test/vpc_client/key_pair.go          |  25 ++
 pkg/test/vpc_client/nat_gateway.go       |  26 ++
 pkg/test/vpc_client/network_acl.go       |  31 ++
 pkg/test/vpc_client/network_interface.go |  16 +
 pkg/test/vpc_client/proxy.go             | 124 ++++++
 pkg/test/vpc_client/route_table.go       |  44 +++
 pkg/test/vpc_client/security_group.go    |  61 +++
 pkg/test/vpc_client/ssh.go               |  59 +++
 pkg/test/vpc_client/subnet.go            | 466 +++++++++++++++++++++++
 pkg/test/vpc_client/types.go             | 124 ++++++
 pkg/test/vpc_client/vpc.go               | 251 ++++++++++++
 44 files changed, 4063 insertions(+), 6 deletions(-)
 create mode 100644 pkg/aws/aws_client/client.go
 create mode 100644 pkg/aws/aws_client/eip.go
 create mode 100644 pkg/aws/aws_client/elb.go
 create mode 100644 pkg/aws/aws_client/env.go
 create mode 100644 pkg/aws/aws_client/image.go
 create mode 100644 pkg/aws/aws_client/instance.go
 create mode 100644 pkg/aws/aws_client/internet_gateway.go
 create mode 100644 pkg/aws/aws_client/key_pair.go
 create mode 100644 pkg/aws/aws_client/kms.go
 create mode 100644 pkg/aws/aws_client/nat_gateway.go
 create mode 100644 pkg/aws/aws_client/network_acl.go
 create mode 100644 pkg/aws/aws_client/network_interface.go
 create mode 100644 pkg/aws/aws_client/oidc.go
 create mode 100644 pkg/aws/aws_client/policy.go
 create mode 100644 pkg/aws/aws_client/resource.go
 create mode 100644 pkg/aws/aws_client/role.go
 create mode 100644 pkg/aws/aws_client/route53.go
 create mode 100644 pkg/aws/aws_client/route_table.go
 create mode 100644 pkg/aws/aws_client/security_group.go
 create mode 100644 pkg/aws/aws_client/subnet.go
 create mode 100644 pkg/aws/aws_client/tag.go
 create mode 100644 pkg/aws/aws_client/volume.go
 create mode 100644 pkg/aws/aws_client/vpc.go
 create mode 100644 pkg/log/logger.go
 create mode 100644 pkg/test/vpc_client/bastion.go
 create mode 100644 pkg/test/vpc_client/cidr.go
 create mode 100644 pkg/test/vpc_client/elb.go
 create mode 100644 pkg/test/vpc_client/helper.go
 create mode 100644 pkg/test/vpc_client/instance.go
 create mode 100644 pkg/test/vpc_client/internet_gateway.go
 create mode 100644 pkg/test/vpc_client/key_pair.go
 create mode 100644 pkg/test/vpc_client/nat_gateway.go
 create mode 100644 pkg/test/vpc_client/network_acl.go
 create mode 100644 pkg/test/vpc_client/network_interface.go
 create mode 100644 pkg/test/vpc_client/proxy.go
 create mode 100644 pkg/test/vpc_client/route_table.go
 create mode 100644 pkg/test/vpc_client/security_group.go
 create mode 100644 pkg/test/vpc_client/ssh.go
 create mode 100644 pkg/test/vpc_client/subnet.go
 create mode 100644 pkg/test/vpc_client/types.go
 create mode 100644 pkg/test/vpc_client/vpc.go

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{"<id>" }}, map[string][]string{"tag:Name":[]string{"<value>" }}
+// 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 <timeout> 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-<rolename>" to pass
+	case "role":
+		role, _ := client.GetRole(splitedResource[1])
+		return role != nil
+	// policy should use "policy-<policy arn>" 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 <timeout> 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 <timeout> 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 <vpcName>
+// 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
+}