Repository: kubernetes-sigs/aws-alb-ingress-controller
Pull request #1109 (2019-12-19 15:30:18)

Rewrite GetClusterSubnets() using EC2 specific API
This PR moves `rgt.GetClusterSubnets()` to `ec2.GetClusterSubnets()` and updates the `GetClusterSubnets()` function to use `EC2 specific API` instead of `Resource Groups Tagging API`.

Since there is no VPC endpoint for  `Resource Groups Tagging API`, this change will make the controller be able to run inside an internal VPC subnet without `internet gateway` or `nat gateway`.
Please let me know if I need to update anything.

Thanks,

k8s-ci-robot: Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please follow instructions at <https://git.k8s.io/community/CLA.md#the-contributor-license-agreement> to sign the CLA.**

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check your existing CLA data and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If you signed the CLA as a corporation, please sign in with your organization's credentials at <https://identity.linuxfoundation.org/projects/cncf> to be authorized.
- If you have done the above and are still having issues with the CLA being reported as unsigned, please log a ticket with the Linux Foundation Helpdesk: <https://support.linuxfoundation.org/>
- Should you encounter any issues with the Linux Foundation Helpdesk, send a message to the backup e-mail support address at: login-issues@jira.linuxfoundation.org

<!-- need_sender_cla -->

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
	
k8s-ci-robot: Welcome @lanSs! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes-sigs/aws-alb-ingress-controller'>kubernetes-sigs/aws-alb-ingress-controller</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes-sigs/aws-alb-ingress-controller has [its own contribution guidelines](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: Hi @lanSs. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/pull/1109#" title="Author self-approved">lanSs</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **bigkraig**
You can assign the PR to them by writing `/assign @bigkraig` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["bigkraig"]} -->
lanSs: /assign M00nF1sh
M00nF1sh: /ok-to-test
k8s-ci-robot: @lanSs: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-aws-alb-ingress-controller-lint | b7e9a6a90268f4c5a48ef6298c11539dcf4fc6b7 | [link](https://prow.k8s.io/view/gcs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_aws-alb-ingress-controller/1109/pull-aws-alb-ingress-controller-lint/1210290948408348672) | `/test pull-aws-alb-ingress-controller-lint`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=aws-alb-ingress-controller&pr=1109). [Your PR dashboard](https://gubernator.k8s.io/pr/lanSs). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @lanSs: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-aws-alb-ingress-controller-lint | b7e9a6a90268f4c5a48ef6298c11539dcf4fc6b7 | [link](https://prow.k8s.io/view/gcs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_aws-alb-ingress-controller/1109/pull-aws-alb-ingress-controller-lint/1210290948408348674) | `/test pull-aws-alb-ingress-controller-lint`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=aws-alb-ingress-controller&pr=1109). [Your PR dashboard](https://gubernator.k8s.io/pr/lanSs). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/pull/1109#" title="Author self-approved">lanSs</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **m00nf1sh**
You can assign the PR to them by writing `/assign @m00nf1sh` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["m00nf1sh"]} -->
lanSs: /retest
M00nF1sh: @lanSs 
This overall looks good to me and should works.
Just one nit before merge, would you help test change to 
```
in := &ec2.DescribeSubnetsInput{Filters: []*ec2.Filter{
		{
			Name:   aws.String("tag:kubernetes.io/cluster/" + c.clusterName),
			Values: aws.StringSlice([]string{"owned", "shared"}),
		},
                {
			Name:   aws.String("tag: + tagSubnetType),
			Values: aws.StringSlice([]string{"", "1"}),
		},
	}}
```
I believe it should works and more efficient if you have a lot subnets in your VPC. (we had customers with 100+ subnets) :D 

Patch:
diff --git a/docs/guide/external-dns/setup.md b/docs/guide/external-dns/setup.md
index c8f7a043f..76413e7a1 100644
--- a/docs/guide/external-dns/setup.md
+++ b/docs/guide/external-dns/setup.md
@@ -6,15 +6,15 @@
 Adequate roles and policies must be configured in AWS and available to the node(s) running the external-dns. See https://github.com/kubernetes-incubator/external-dns/blob/master/docs/tutorials/aws.md#iam-permissions.
 
 ## Installation
-1. Download sample `external-dns` manifest
-
-    ```bash
+1. Download sample external-dns manifest
+   
+    ``` bash
     wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/external-dns.yaml
     ```
 
 2. Edit the `--domain-filter` flag to include your hosted zone(s)
 
-    The following example is for a hosted zone `test-dns.com`:
+    The following example is for a hosted zone test-dns.com
 
     ```yaml
     args:
@@ -23,45 +23,32 @@ Adequate roles and policies must be configured in AWS and available to the node(
     - --domain-filter=test-dns.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
     - --provider=aws
     - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
-    - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
-    - --registry=txt
-    - --txt-owner-id=my-identifier
     ```
 
 3. Deploy external-dns
 
-    ```bash
+    ``` bash
     kubectl apply -f external-dns.yaml
     ```
 
 4. Verify it deployed successfully.
 
-    ```bash
-    kubectl logs -f $(kubectl get po | egrep -o 'external-dns[A-Za-z0-9-]+')
+    ``` bash
+    kubectl logs -f -n kube-system $(kubectl get po -n kube-system | egrep -o 'external-dns[A-Za-z0-9-]+')
     ```
 
-    Should display output similar to the following:
+    Should display output similar to the following.
     ```
-    time="2019-12-11T10:26:05Z" level=info msg="config: {Master: KubeConfig: RequestTimeout:30s IstioIngressGateway:istio-system/istio-ingressgateway Sources:[service ingress] Namespace: AnnotationFilter: FQDNTemplate: CombineFQDNAndAnnotation:false Compatibility: PublishInternal:false PublishHostIP:false ConnectorSourceServer:localhost:8080 Provider:aws GoogleProject: DomainFilter:[test-dns.com] ZoneIDFilter:[] AlibabaCloudConfigFile:/etc/kubernetes/alibaba-cloud.json AlibabaCloudZoneType: AWSZoneType:public AWSAssumeRole: AWSBatchChangeSize:4000 AWSBatchChangeInterval:1s AWSEvaluateTargetHealth:true AzureConfigFile:/etc/kubernetes/azure.json AzureResourceGroup: CloudflareProxied:false InfobloxGridHost: InfobloxWapiPort:443 InfobloxWapiUsername:admin InfobloxWapiPassword: InfobloxWapiVersion:2.3.1 InfobloxSSLVerify:true DynCustomerName: DynUsername: DynPassword: DynMinTTLSeconds:0 OCIConfigFile:/etc/kubernetes/oci.yaml InMemoryZones:[] PDNSServer:http://localhost:8081 PDNSAPIKey: PDNSTLSEnabled:false TLSCA: TLSClientCert: TLSClientCertKey: Policy:upsert-only Registry:txt TXTOwnerID:my-identifier TXTPrefix: Interval:1m0s Once:false DryRun:false LogFormat:text MetricsAddress::7979 LogLevel:info TXTCacheInterval:0s ExoscaleEndpoint:https://api.exoscale.ch/dns ExoscaleAPIKey: ExoscaleAPISecret: CRDSourceAPIVersion:externaldns.k8s.io/v1alpha CRDSourceKind:DNSEndpoint ServiceTypeFilter:[] RFC2136Host: RFC2136Port:0 RFC2136Zone: RFC2136Insecure:false RFC2136TSIGKeyName: RFC2136TSIGSecret: RFC2136TSIGSecretAlg: RFC2136TAXFR:false}"
-    time="2019-12-11T10:26:05Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
+    time="2017-09-19T02:51:54Z" level=info msg="config: &{Master: KubeConfig: Sources:[service ingress] Namespace: FQDNTemplate: Compatibility: Provider:aws GoogleProject: DomainFilter:[] AzureConfigFile:/etc/kuberne tes/azure.json AzureResourceGroup: Policy:upsert-only Registry:txt TXTOwnerID:my-identifier TXTPrefix: Interval:1m0s Once:false DryRun:false LogFormat:text MetricsAddress::7979 Debug:false}"
+    time="2017-09-19T02:51:54Z" level=info msg="Connected to cluster at https://10.3.0.1:443"
     ```
 
-## Usage
-1. To create a record set in the subdomain, from your ingress which has been created by the ingress-controller, simply add the following annotation in the ingress object specification and apply the manifest:
+## Usage:
+To create a record set in the subdomain, from your ingress which has been created by the ingress-controller, simply add the following annotation in the ingress object specification,
 
-    ```yaml
     annotations:
       kubernetes.io/ingress.class: alb
       alb.ingress.kubernetes.io/scheme: internet-facing
 
       # for creating record-set
       external-dns.alpha.kubernetes.io/hostname: my-app.test-dns.com # give your domain name here
-    ```
-
-2. Similar entries should appear in the ExternalDNS pod log:
-
-    ```
-    time="2019-12-11T10:26:08Z" level=info msg="Desired change: CREATE my-app.test-dns.com A"
-    time="2019-12-11T10:26:08Z" level=info msg="Desired change: CREATE my-app.test-dns.com TXT"
-    time="2019-12-11T10:26:08Z" level=info msg="2 record(s) in zone my-app.test-dns.com. were successfully updated"
-    ```
diff --git a/docs/guide/walkthrough/echoserver.md b/docs/guide/walkthrough/echoserver.md
index 060f31b59..1c3b07e11 100644
--- a/docs/guide/walkthrough/echoserver.md
+++ b/docs/guide/walkthrough/echoserver.md
@@ -303,13 +303,13 @@ In this walkthrough, you'll
     ```
 
 ## Kube2iam setup
-follow below steps if you want to use kube2iam to provide the AWS credentials
+follow below steps If you want to use kube2iam to provide the AWS credentials
 
 1. configure the proper policy
     The policy to be used can be fetched from https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/iam-policy.json
 
 1. configure the proper role and create the trust relationship
-    You have to find which role is associated with your K8S nodes. Once you found take note of the full arn:
+    You have to find which role is associated woth your K8S nodes. Once you found take note of the full arn:
 
     ```
     arn:aws:iam::XXXXXXXXXXXX:role/k8scluster-node
@@ -341,7 +341,7 @@ follow below steps if you want to use kube2iam to provide the AWS credentials
     }
     ```
 
-    The new role will have a similar arn:
+    The new role will have the arn:
 
     ```
     arn:aws:iam:::XXXXXXXXXXXX:role/k8s-alb-controller
@@ -349,7 +349,7 @@ follow below steps if you want to use kube2iam to provide the AWS credentials
 
 1.  update the alb-ingress-controller.yaml
 
-    Add the annotations in the template's metadata point
+    Add the annotations in the template's metadata poin
 
     ```yaml
     spec:
diff --git a/internal/alb/lb/loadbalancer.go b/internal/alb/lb/loadbalancer.go
index 7b467b96b..717cd1c34 100644
--- a/internal/alb/lb/loadbalancer.go
+++ b/internal/alb/lb/loadbalancer.go
@@ -380,7 +380,6 @@ func (controller *defaultController) resolveSubnets(ctx context.Context, scheme
 }
 
 func (controller *defaultController) clusterSubnets(ctx context.Context, scheme string) ([]string, error) {
-	var subnetIds []string
 	var useableSubnets []*ec2.Subnet
 	var out []string
 	var key string
@@ -393,27 +392,12 @@ func (controller *defaultController) clusterSubnets(ctx context.Context, scheme
 		return nil, fmt.Errorf("invalid scheme [%s]", scheme)
 	}
 
-	clusterSubnets, err := controller.cloud.GetClusterSubnets()
+	clusterSubnets, err := controller.cloud.GetClusterSubnets(key)
 	if err != nil {
-		return nil, fmt.Errorf("failed to get AWS tags. Error: %s", err.Error())
+		return nil, fmt.Errorf("unable to fetch subnets. Error: %s", err.Error())
 	}
 
-	for arn, subnetTags := range clusterSubnets {
-		for _, tag := range subnetTags {
-			if aws.StringValue(tag.Key) == key {
-				p := strings.Split(arn, "/")
-				subnetID := p[len(p)-1]
-				subnetIds = append(subnetIds, subnetID)
-			}
-		}
-	}
-
-	o, err := controller.cloud.GetSubnetsByNameOrID(ctx, subnetIds)
-	if err != nil {
-		return nil, fmt.Errorf("unable to fetch subnets due to %v", err)
-	}
-
-	for _, subnet := range o {
+	for _, subnet := range clusterSubnets {
 		if subnetIsUsable(subnet, useableSubnets) {
 			useableSubnets = append(useableSubnets, subnet)
 			out = append(out, aws.StringValue(subnet.SubnetId))
diff --git a/internal/aws/ec2.go b/internal/aws/ec2.go
index 28f40ecce..ae69dd8b0 100644
--- a/internal/aws/ec2.go
+++ b/internal/aws/ec2.go
@@ -44,6 +44,9 @@ type EC2API interface {
 	// GetSecurityGroupsByName retrieves securityGroups by securityGroupName(SecurityGroup names within vpc are unique)
 	GetSecurityGroupsByName(context.Context, []string) ([]*ec2.SecurityGroup, error)
 
+	// GetClusterSubnets retrieves the subnets associated with the cluster, by matching tags
+	GetClusterSubnets(string) ([]*ec2.Subnet, error)
+
 	// DeleteSecurityGroupByID delete securityGroup by securityGroupID
 	DeleteSecurityGroupByID(context.Context, string) error
 
@@ -164,6 +167,26 @@ func (c *Cloud) GetSubnetsByNameOrID(ctx context.Context, nameOrIDs []string) (s
 	return
 }
 
+func (c *Cloud) GetClusterSubnets(tagSubnetType string) ([]*ec2.Subnet, error) {
+	in := &ec2.DescribeSubnetsInput{Filters: []*ec2.Filter{
+		{
+			Name:   aws.String("tag:kubernetes.io/cluster/" + c.clusterName),
+			Values: aws.StringSlice([]string{"owned", "shared"}),
+		},
+		{
+			Name:   aws.String("tag:" + tagSubnetType),
+			Values: aws.StringSlice([]string{"", "1"}),
+		},
+	}}
+
+	result, err := c.describeSubnetsHelper(in)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
 func (c *Cloud) GetSecurityGroupsByName(ctx context.Context, names []string) (groups []*ec2.SecurityGroup, err error) {
 	in := &ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{
 		{
@@ -272,6 +295,16 @@ func (c *Cloud) describeSecurityGroupsHelper(params *ec2.DescribeSecurityGroupsI
 	return results, err
 }
 
+// describeSubnetsHelper is a helper to handle pagination for DescribeSubnets API call
+func (c *Cloud) describeSubnetsHelper(params *ec2.DescribeSubnetsInput) (result []*ec2.Subnet, err error) {
+	err = c.ec2.DescribeSubnetsPages(params, func(output *ec2.DescribeSubnetsOutput, _ bool) bool {
+		result = append(result, output.Subnets...)
+		return true
+	})
+
+	return result, err
+}
+
 func (c *Cloud) describeInstancesHelper(params *ec2.DescribeInstancesInput) (result []*ec2.Reservation, err error) {
 	err = c.ec2.DescribeInstancesPages(params, func(output *ec2.DescribeInstancesOutput, _ bool) bool {
 		result = append(result, output.Reservations...)
diff --git a/internal/aws/rgt.go b/internal/aws/rgt.go
index ff0d78492..4ac2b10ce 100644
--- a/internal/aws/rgt.go
+++ b/internal/aws/rgt.go
@@ -2,15 +2,9 @@ package aws
 
 import (
 	"context"
-	"strings"
-
-	"github.com/aws/aws-sdk-go/service/ec2"
 
 	"github.com/aws/aws-sdk-go/aws"
-
 	"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
-
-	util "github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/types"
 )
 
 const (
@@ -20,8 +14,6 @@ const (
 )
 
 type ResourceGroupsTaggingAPIAPI interface {
-	GetClusterSubnets() (map[string]util.EC2Tags, error)
-
 	// GetResourcesByFilters fetches resources ARNs by tagFilters and 0 or more resourceTypesFilters
 	GetResourcesByFilters(tagFilters map[string][]string, resourceTypeFilters ...string) ([]string, error)
 
@@ -36,76 +28,6 @@ func (c *Cloud) UntagResourcesWithContext(ctx context.Context, i *resourcegroups
 	return c.rgt.UntagResourcesWithContext(ctx, i)
 }
 
-// GetClusterSubnets looks up all subnets in AWS that are tagged for the cluster.
-func (c *Cloud) GetClusterSubnets() (map[string]util.EC2Tags, error) {
-	subnets := make(map[string]util.EC2Tags)
-
-	paramSets := []*resourcegroupstaggingapi.GetResourcesInput{
-		{
-			ResourcesPerPage: aws.Int64(50),
-			ResourceTypeFilters: []*string{
-				aws.String("ec2"),
-			},
-			TagFilters: []*resourcegroupstaggingapi.TagFilter{
-				{
-					Key:    aws.String("kubernetes.io/role/internal-elb"),
-					Values: []*string{aws.String(""), aws.String("1")},
-				},
-				{
-					Key:    aws.String("kubernetes.io/cluster/" + c.clusterName),
-					Values: []*string{aws.String("owned"), aws.String("shared")},
-				},
-			},
-		},
-		{
-			ResourcesPerPage: aws.Int64(50),
-			ResourceTypeFilters: []*string{
-				aws.String("ec2"),
-			},
-			TagFilters: []*resourcegroupstaggingapi.TagFilter{
-				{
-					Key:    aws.String("kubernetes.io/role/elb"),
-					Values: []*string{aws.String(""), aws.String("1")},
-				},
-				{
-					Key:    aws.String("kubernetes.io/cluster/" + c.clusterName),
-					Values: []*string{aws.String("owned"), aws.String("shared")},
-				},
-			},
-		},
-	}
-
-	for _, paramSet := range paramSets {
-		err := c.rgt.GetResourcesPages(paramSet, func(page *resourcegroupstaggingapi.GetResourcesOutput, lastPage bool) bool {
-			if page == nil {
-				return false
-			}
-			for _, rtm := range page.ResourceTagMappingList {
-				switch {
-				case strings.Contains(*rtm.ResourceARN, ":subnet/"):
-					subnets[*rtm.ResourceARN] = rgtTagAsEC2Tag(rtm.Tags)
-				}
-			}
-			return true
-		})
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return subnets, nil
-}
-
-func rgtTagAsEC2Tag(in []*resourcegroupstaggingapi.Tag) (tags util.EC2Tags) {
-	for _, t := range in {
-		tags = append(tags, &ec2.Tag{
-			Key:   t.Key,
-			Value: t.Value,
-		})
-	}
-	return tags
-}
-
 func (c *Cloud) GetResourcesByFilters(tagFilters map[string][]string, resourceTypeFilters ...string) ([]string, error) {
 	var awsTagFilters []*resourcegroupstaggingapi.TagFilter
 	for k, v := range tagFilters {
diff --git a/mocks/CloudAPI.go b/mocks/CloudAPI.go
index 9d0d0f491..f1640e03b 100644
--- a/mocks/CloudAPI.go
+++ b/mocks/CloudAPI.go
@@ -15,8 +15,6 @@ import (
 
 	resourcegroupstaggingapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
 
-	types "github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/types"
-
 	waf "github.com/aws/aws-sdk-go/service/waf"
 
 	wafregional "github.com/aws/aws-sdk-go/service/wafregional"
@@ -618,21 +616,21 @@ func (_m *CloudAPI) GetClusterName() string {
 }
 
 // GetClusterSubnets provides a mock function with given fields:
-func (_m *CloudAPI) GetClusterSubnets() (map[string]types.EC2Tags, error) {
+func (_m *CloudAPI) GetClusterSubnets(_a0 string) ([]*ec2.Subnet, error) {
 	ret := _m.Called()
 
-	var r0 map[string]types.EC2Tags
-	if rf, ok := ret.Get(0).(func() map[string]types.EC2Tags); ok {
-		r0 = rf()
+	var r0 []*ec2.Subnet
+	if rf, ok := ret.Get(0).(func(string) []*ec2.Subnet); ok {
+		r0 = rf(_a0)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(map[string]types.EC2Tags)
+			r0 = ret.Get(0).([]*ec2.Subnet)
 		}
 	}
 
 	var r1 error
-	if rf, ok := ret.Get(1).(func() error); ok {
-		r1 = rf()
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(_a0)
 	} else {
 		r1 = ret.Error(1)
 	}


Test patch:
diff --git a/internal/aws/ec2_test.go b/internal/aws/ec2_test.go
index 56bcf17e4..0af3d7ebe 100644
--- a/internal/aws/ec2_test.go
+++ b/internal/aws/ec2_test.go
@@ -5,6 +5,8 @@ import (
 	"errors"
 	"testing"
 
+	"github.com/stretchr/testify/mock"
+
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ec2"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/mocks"
@@ -147,3 +149,122 @@ func TestCloud_RevokeSecurityGroupIngressWithContext(t *testing.T) {
 		svc.AssertExpectations(t)
 	})
 }
+
+func TestCloud_GetClusterSubnets(t *testing.T) {
+	clusterName := "clusterName"
+	internalSubnet1 := &ec2.Subnet{
+		SubnetId: aws.String("arn:aws:ec2:region:account-id:subnet/subnet-id1"),
+		Tags: []*ec2.Tag{
+			{
+				Key:   aws.String("kubernetes.io/cluster/" + clusterName),
+				Value: aws.String("owned"),
+			},
+			{
+				Key:   aws.String("kubernetes.io/role/internal-elb"),
+				Value: aws.String("1"),
+			},
+		},
+	}
+	internalSubnet2 := &ec2.Subnet{
+		SubnetId: aws.String("arn:aws:ec2:region:account-id:subnet/subnet-id2"),
+		Tags: []*ec2.Tag{
+			{
+				Key:   aws.String("kubernetes.io/cluster/" + clusterName),
+				Value: aws.String("owned"),
+			},
+			{
+				Key:   aws.String("kubernetes.io/role/internal-elb"),
+				Value: aws.String(""),
+			},
+		},
+	}
+	publicSubnet := &ec2.Subnet{
+		SubnetId: aws.String("arn:aws:ec2:region:account-id:subnet/subnet-id3"),
+		Tags: []*ec2.Tag{
+			{
+				Key:   aws.String("kubernetes.io/cluster/" + clusterName),
+				Value: aws.String("shared"),
+			},
+			{
+				Key:   aws.String("kubernetes.io/role/elb"),
+				Value: aws.String("1"),
+			},
+		},
+	}
+
+	for _, tc := range []struct {
+		Name                  string
+		DescribeSubnetsOutput *ec2.DescribeSubnetsOutput
+		DescribeSubnetsError  error
+		TagSubnetType         string
+		ExpectedResult        []*ec2.Subnet
+		ExpectedError         error
+	}{
+		{
+			Name:          "No subnets returned",
+			TagSubnetType: TagNameSubnetInternalELB,
+			DescribeSubnetsOutput: &ec2.DescribeSubnetsOutput{
+				NextToken: nil,
+				Subnets:   []*ec2.Subnet{},
+			},
+		},
+		{
+			Name:          "Two internal subnets returned",
+			TagSubnetType: TagNameSubnetInternalELB,
+			DescribeSubnetsOutput: &ec2.DescribeSubnetsOutput{
+				NextToken: nil,
+				Subnets:   []*ec2.Subnet{internalSubnet1, internalSubnet2},
+			},
+			ExpectedResult: []*ec2.Subnet{internalSubnet1, internalSubnet2},
+		},
+		{
+			Name:          "One public subnet returned",
+			TagSubnetType: TagNameSubnetPublicELB,
+			DescribeSubnetsOutput: &ec2.DescribeSubnetsOutput{
+				NextToken: nil,
+				Subnets:   []*ec2.Subnet{publicSubnet},
+			},
+			ExpectedResult: []*ec2.Subnet{publicSubnet},
+		},
+		{
+			Name:          "Error from API call",
+			TagSubnetType: TagNameSubnetPublicELB,
+			DescribeSubnetsOutput: &ec2.DescribeSubnetsOutput{
+				NextToken: nil,
+				Subnets:   []*ec2.Subnet{},
+			},
+			DescribeSubnetsError: errors.New("Some API error"),
+			ExpectedError:        errors.New("Some API error"),
+		},
+	} {
+		t.Run(tc.Name, func(t *testing.T) {
+			svc := &mocks.EC2API{}
+
+			svc.On("DescribeSubnetsPages",
+				&ec2.DescribeSubnetsInput{Filters: []*ec2.Filter{
+					{
+						Name:   aws.String("tag:kubernetes.io/cluster/" + clusterName),
+						Values: aws.StringSlice([]string{"owned", "shared"}),
+					},
+					{
+						Name:   aws.String("tag:" + tc.TagSubnetType),
+						Values: aws.StringSlice([]string{"", "1"}),
+					}},
+				},
+				mock.AnythingOfType("func(*ec2.DescribeSubnetsOutput, bool) bool"),
+			).Return(tc.DescribeSubnetsError).Run(func(args mock.Arguments) {
+				arg := args.Get(1).(func(*ec2.DescribeSubnetsOutput, bool) bool)
+				arg(tc.DescribeSubnetsOutput, false)
+			})
+
+			cloud := &Cloud{
+				clusterName: clusterName,
+				ec2:         svc,
+			}
+			subnets, err := cloud.GetClusterSubnets(tc.TagSubnetType)
+			assert.Equal(t, tc.ExpectedResult, subnets)
+			assert.Equal(t, tc.ExpectedError, err)
+			svc.AssertExpectations(t)
+		})
+	}
+}
diff --git a/internal/aws/rgt_test.go b/internal/aws/rgt_test.go
index 815dc76ae..6ac6293f2 100644
--- a/internal/aws/rgt_test.go
+++ b/internal/aws/rgt_test.go
@@ -7,10 +7,8 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/request"
-	"github.com/aws/aws-sdk-go/service/ec2"
 	"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/mocks"
-	util "github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/types"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -57,132 +55,6 @@ func TestCloud_UntagResourcesWithContext(t *testing.T) {
 	})
 }
 
-func tag(k, v string) *resourcegroupstaggingapi.Tag {
-	return &resourcegroupstaggingapi.Tag{Key: String(k), Value: String(v)}
-}
-
-func tags(t ...*resourcegroupstaggingapi.Tag) []*resourcegroupstaggingapi.Tag {
-	return t
-}
-
-func ec2Tag(k, v string) *ec2.Tag {
-	return &ec2.Tag{Key: aws.String(k), Value: aws.String(v)}
-}
-
-func TestCloud_GetClusterSubnets(t *testing.T) {
-	clusterName := "clusterName"
-	paramSets := []*resourcegroupstaggingapi.GetResourcesInput{
-		{
-			ResourcesPerPage: aws.Int64(50),
-			ResourceTypeFilters: []*string{
-				aws.String("ec2"),
-			},
-			TagFilters: []*resourcegroupstaggingapi.TagFilter{
-				{
-					Key:    aws.String("kubernetes.io/role/internal-elb"),
-					Values: []*string{aws.String(""), aws.String("1")},
-				},
-				{
-					Key:    aws.String("kubernetes.io/cluster/" + clusterName),
-					Values: []*string{aws.String("owned"), aws.String("shared")},
-				},
-			},
-		},
-		{
-			ResourcesPerPage: aws.Int64(50),
-			ResourceTypeFilters: []*string{
-				aws.String("ec2"),
-			},
-			TagFilters: []*resourcegroupstaggingapi.TagFilter{
-				{
-					Key:    aws.String("kubernetes.io/role/elb"),
-					Values: []*string{aws.String(""), aws.String("1")},
-				},
-				{
-					Key:    aws.String("kubernetes.io/cluster/" + clusterName),
-					Values: []*string{aws.String("owned"), aws.String("shared")},
-				},
-			},
-		},
-	}
-	for _, tc := range []struct {
-		Name               string
-		GetResourcesOutput *resourcegroupstaggingapi.GetResourcesOutput
-		GetResourcesError  awserr.Error
-		Params             int
-		ExpectedResult     map[string]util.EC2Tags
-		ExpectedError      error
-	}{
-		{
-			Name:   "No subnets returned",
-			Params: 2,
-			GetResourcesOutput: &resourcegroupstaggingapi.GetResourcesOutput{
-				ResourceTagMappingList: []*resourcegroupstaggingapi.ResourceTagMapping{
-					{
-						ResourceARN: aws.String("arn1"),
-						Tags:        tags(tag("tag1", "val1")),
-					},
-				},
-			},
-			ExpectedResult: map[string]util.EC2Tags{},
-		},
-		{
-			Name:   "Three subnets returned",
-			Params: 2,
-			GetResourcesOutput: &resourcegroupstaggingapi.GetResourcesOutput{
-				ResourceTagMappingList: []*resourcegroupstaggingapi.ResourceTagMapping{
-					{
-						ResourceARN: aws.String("arn:aws:ec2:region:account-id:subnet/subnet-id1"),
-						Tags:        tags(tag("tag1", "val1")),
-					},
-					{
-						ResourceARN: aws.String("arn:aws:ec2:region:account-id:subnet/subnet-id2"),
-						Tags:        tags(tag("tag2", "val2")),
-					},
-					{
-						ResourceARN: aws.String("arn:aws:ec2:region:account-id:subnet/subnet-id3"),
-						Tags:        tags(tag("tag3", "val3")),
-					},
-				},
-			},
-			ExpectedResult: map[string]util.EC2Tags{
-				"arn:aws:ec2:region:account-id:subnet/subnet-id1": {ec2Tag("tag1", "val1")},
-				"arn:aws:ec2:region:account-id:subnet/subnet-id2": {ec2Tag("tag2", "val2")},
-				"arn:aws:ec2:region:account-id:subnet/subnet-id3": {ec2Tag("tag3", "val3")},
-			},
-		},
-		{
-			Name:              "API throws an error",
-			Params:            1,
-			GetResourcesError: awserr.New(request.ErrCodeResponseTimeout, "timeout", nil),
-			ExpectedError:     awserr.New(request.ErrCodeResponseTimeout, "timeout", nil),
-		},
-	} {
-		t.Run(tc.Name, func(t *testing.T) {
-			rgtsvc := &mocks.ResourceGroupsTaggingAPIAPI{}
-
-			for paramSet := 0; paramSet < tc.Params; paramSet++ {
-				rgtsvc.On("GetResourcesPages",
-					paramSets[paramSet],
-					mock.AnythingOfType("func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool"),
-				).Return(tc.GetResourcesError).Run(func(args mock.Arguments) {
-					arg := args.Get(1).(func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool)
-					arg(tc.GetResourcesOutput, false)
-				})
-			}
-
-			cloud := &Cloud{
-				clusterName: clusterName,
-				rgt:         rgtsvc,
-			}
-			subnets, err := cloud.GetClusterSubnets()
-			assert.Equal(t, tc.ExpectedResult, subnets)
-			assert.Equal(t, tc.ExpectedError, err)
-			rgtsvc.AssertExpectations(t)
-		})
-	}
-}
-
 func TestCloud_GetResourcesByFilters(t *testing.T) {
 	for _, tc := range []struct {
 		Name                string