Repository: keikoproj/instance-manager
Pull request #71 (2019-12-19 19:12:40)

Eks fargate
Initial implementation of #70 

@eytan-avisror Love to have your thoughts and comments.

codecov[bot]: # [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/71?src=pr&el=h1) Report
> Merging [#71](https://codecov.io/gh/keikoproj/instance-manager/pull/71?src=pr&el=desc) into [master](https://codecov.io/gh/keikoproj/instance-manager/commit/9a21c95d1383b310820a834e2986701d039719cb?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/keikoproj/instance-manager/pull/71/graphs/tree.svg?width=650&token=IJbjmSBliL&height=150&src=pr)](https://codecov.io/gh/keikoproj/instance-manager/pull/71?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master      #71   +/-   ##
=======================================
  Coverage   73.09%   73.09%           
=======================================
  Files           6        6           
  Lines         825      825           
=======================================
  Hits          603      603           
  Misses        162      162           
  Partials       60       60
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/71?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/71?src=pr&el=footer). Last update [9a21c95...a7721d0](https://codecov.io/gh/keikoproj/instance-manager/pull/71?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

claassistantio: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/keikoproj/instance-manager?pullRequest=71) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/keikoproj/instance-manager?pullRequest=71) before we can accept your contribution.<br/><hr/>**twhitcomb** seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/keikoproj/instance-manager?pullRequest=71) it.</sub>
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/keikoproj/instance-manager?pullRequest=71) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/keikoproj/instance-manager?pullRequest=71) before we can accept your contribution.<br/><hr/>**twhitcomb** seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/keikoproj/instance-manager?pullRequest=71) it.</sub>
edlee2121: @mukulikak 
eytan-avisror: Some comments:
- You are not using the shared interface `HandleReconcileRequest` and instead implemented your own `HandleRequest` - you should use the common provisioner interface `CloudDeployer`

- AWS clients are created in the controller and not in the method call, could you refactor to use same pattern? example:
https://github.com/keikoproj/instance-manager/blob/master/controllers/instancegroup_controller.go#L303-L309


thomaswhitcomb: @eytan-avisror Thanks for those comments.  I'll first resolve the conflicts then I'll fix up the first two points.
thomaswhitcomb: @eytan-avisror Working on the first 2 comments above... Are we striving to encapsulate all AWS references to the controllers/providers/aws/aws.go? 
eytan-avisror: > @eytan-avisror Working on the first 2 comments above... Are we striving to encapsulate all AWS references to the controllers/providers/aws/aws.go?

All the AWS specific calls should be methods under object `AwsWorker`, which contains all required clients - this makes unit testing easier because we can pass in fake clients when we instantiate AwsWorker.

As far as order/organization, perhaps down the road (not in this PR), we can refactor to have files according to client (e.g. eks.go, cloudformation.go, etc).
thomaswhitcomb: Using ```HandleReconcileRequest``` and have refactored to the pattern shown here: https://github.com/keikoproj/instance-manager/blob/master/controllers/instancegroup_controller.go#L303-L309
thomaswhitcomb: @eytan-avisror I pushed my support for immutable profiles.  All dependent AWS calls are handled through reconciliation. An `apply` ends up with an Error. I am going to start looking into the validating webhooks.
eytan-avisror: @thomaswhitcomb 
Let's get this PR merged in soon (right after #83)
We can keep iterating on this before we release.
I will be making a lot of changes to shared code in the coming weeks and I don't want this PR to get too many conflicts
eytan-avisror: I just merged #83 - there are some conflicts as a result but they are simple ones (mostly 'keep both').
Once you can resolve - we can get this merged. - validating webhooks can be a new PR
thomaswhitcomb: @eytan-avisror Just added very basic create and delete for the profiles.  Approach, layout,... look ok?

Patch:
diff --git a/Makefile b/Makefile
index 66bbe82..8c226ab 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,17 @@
+export GO111MODULE=on
+
+GO_MIN_VERSION := 11100 # go1.11
+GO_VERSION_CHECK := \
+  $(shell expr \
+    $(shell go version | \
+      awk '{print $$3}' | \
+      cut -do -f2 | \
+      sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/' \
+    ) \>= $(GO_MIN_VERSION) \
+  )
+
+# Default Go linker flags.
+GO_LDFLAGS ?= -ldflags="-s -w"
 
 # Image URL to use all building/pushing image targets
 IMG ?= instance-manager:latest
@@ -6,28 +20,39 @@ INSTANCEMGR_TAG ?= latest
 # Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
 CRD_OPTIONS ?= "crd:trivialVersions=true"
 
-all: manager
+.PHONY: all
+all: check-go test clean manager
 
 # Run tests
+.PHONY: test
 test: generate fmt vet manifests
-	go test -v ./controllers/... -coverprofile coverage.txt
+	go test -v ./controllers/... ./api/... -coverprofile coverage.txt
 
+.PHONY: bdd
 bdd:
-	go test -timeout 60m -v ./test-bdd/ --godog.stop-on-failure
+	go test -timeout 60m -v ./test-bdd/ --godog.stop-on-failure 
+
+.PHONY: wip
+wip:
+	go test -timeout 60m -v ./test-bdd/ --godog.tags "@wip"
 
+.PHONY: coverage
 coverage:
 	go test -coverprofile coverage.txt -v ./controllers/...
 	go tool cover -html=coverage.txt -o coverage.html
 
 # Build manager binary
+.PHONY: manager
 manager: generate fmt vet
 	go build -o bin/manager main.go
 
 # Run against the configured Kubernetes cluster in ~/.kube/config
+.PHONY: run
 run: generate fmt vet
 	go run ./main.go
 
 # Install CRDs into a cluster
+.PHONY: install
 install: manifests
 	kubectl apply -f config/rbac/service_account.yaml
 	kubectl auth reconcile -f config/rbac/role.yaml
@@ -35,38 +60,46 @@ install: manifests
 	kubectl apply -f config/crd/bases
 
 # Deploy controller in the configured Kubernetes cluster in ~/.kube/config
+.PHONY: deploy
 deploy: manifests
 	kubectl apply -f config/crd/bases
 	kustomize build config/default | kubectl apply -f -
 
 # Generate manifests e.g. CRD, RBAC etc.
+.PHONY: manifests
 manifests: controller-gen
 	$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=instance-manager webhook paths="./api/...;./controllers/..." output:crd:artifacts:config=config/crd/bases
 
 # Run go fmt against code
+.PHONY: fmt
 fmt:
 	go fmt ./...
 
 # Run go vet against code
+.PHONY: vet
 vet:
 	go vet ./...
 
 # Generate code
+.PHONY: generate
 generate: controller-gen
 	$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths=./api/...
 
 # Build the docker image
+.PHONY: docker-build
 docker-build:
 	docker build . -t ${IMG}
 	@echo "updating kustomize image patch file for manager resource"
 	sed -i'' -e 's@image: .*@image: '"${IMG}"'@' ./config/default/manager_image_patch.yaml
 
 # Push the docker image
+.PHONY: docker-push
 docker-push:
 	docker push ${IMG}
 
 # find or download controller-gen
 # download controller-gen if necessary
+.PHONY: controller-gen
 controller-gen:
 ifeq (, $(shell which controller-gen))
 	go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.9
@@ -74,3 +107,25 @@ CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen
 else
 CONTROLLER_GEN=$(shell which controller-gen)
 endif
+
+.PHONY: check-go
+check-go:
+ifeq ($(GO_VERSION_CHECK),0)
+        $(error go1.11 or higher is required)
+endif
+
+.PHONY: lint
+lint: check-go
+	@echo "golint $(LINTARGS)"
+	@for pkg in $(shell go list ./...) ; do \
+		golint $(LINTARGS) $$pkg ; \
+	done
+
+.PHONY: vendor
+vendor:
+	go mod tidy
+	go mod vendor
+
+.PHONY: clean
+clean:
+	@rm -rf ./bin	
diff --git a/README.md b/README.md
index 505f4c5..1515b0a 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,21 @@
 ![version](https://img.shields.io/badge/version-0.7.0-blue.svg?cacheSeconds=2592000)
 > Create and manage instance groups with Kubernetes.
 
-instance-manager simplifies the creation of worker nodes from within a Kubernetes cluster, create `InstanceGroup` objects in your cluster and instance-manager will provision the actual machines and bootstrap them to the cluster.
+**instance-manager** simplifies the creation of worker nodes from within a Kubernetes cluster and creates `InstanceGroup` objects in your cluster. Additionally, **instance-manager** will provision the actual machines and bootstrap them to the cluster.
 
 ![instance-manager](hack/instance-manager.png)
 
+- [Installation](#installation)  
+- [Usage Example](#usage-example)  
+    * [Currently supported provisioners](#currently-supported-provisioners)
+    * [EKS sample spec](#EKS-sample-spec)
+    * [Submit and Verify](#submit-and-verify)
+    * [Features](#features)
+    * [Alpha-2 Version](#alpha-2-version)
+- [Contributing](#contributing)  
+- [Developer Guide](#developer-guide)  
+
+
 Worker nodes in Kubernetes clusters work best if provisioned and managed using a logical grouping. Kops introduced the term “InstanceGroup” for this logical grouping. In AWS, an InstanceGroup maps to an AutoScalingGroup.
 
 Given a particular cluster, there should be a way to create, read, upgrade and delete worker nodes from within the cluster itself. This enables use-cases where worker nodes can be created in response to Kubernetes events, InstanceGroups can be automatically assigned to namespaces for multi-tenancy, etc.
@@ -26,14 +37,16 @@ _For installation instructions and more examples of usage, please refer to the [
 
 ### Currently supported provisioners
 
-| Provisioner | Description |
-| :--- | :--- |
-| eks | provision nodes on EKS |
-| eks-managed | provision managed node groups on EKS |
+| Provisioner | Description | 
+| :---------- | :---------- | 
+| eks         | provision nodes on EKS |
+| eks-managed | provision managed node groups on EKS|
+| eks-fargate | provision a cluster to run pods on EKS Fargate|
 
 To create an instance group, submit an InstanceGroup custom resource in your cluster, and the controller will provision and bootstrap it to your cluster, and allow you to modify it from within the cluster.
 
-### EKS sample spec (alpha-2)
+
+### EKS sample spec
 
 ```yaml
 apiVersion: instancemgr.keikoproj.io/v1alpha1
@@ -77,7 +90,8 @@ spec:
         value: some-value
 ```
 
-### Submit & Verify
+
+### Submit and Verify
 
 ```bash
 $ kubectl create -f instance_group.yaml
@@ -107,9 +121,9 @@ ip-10-10-10-30.us-west-2.compute.internal   Ready    hello-world   32s    v1.14.
 ip-10-10-10-40.us-west-2.compute.internal   Ready    hello-world   32s    v1.14.6-eks-5047ed
 ```
 
-#### Features
+### Features
 
-##### Spot instance support
+#### Spot instance support
 
 You can manually specify a spot price directly in the spec of an instance group
 
@@ -131,7 +145,7 @@ instance-manager will switch the instances to spot for you if that price is avai
 
 You can also use [minion-manager](https://github.com/keikoproj/minion-manager/issues/50) in `--events-only` mode to provide spot recommendations for instance-manager.
 
-##### Upgrade Strategies
+#### Upgrade Strategies
 
 instance-manager supports multiple upgrade strategies, a basic one called `rollingUpdate` which rotates instances according to `maxUnavailable`.
 
@@ -190,29 +204,7 @@ In this strategy you can create resources as a response for a pending upgrade, y
 
 _For more examples and usage, please refer to the [Installation Reference Walkthrough][install]._
 
-##### Bring your own role
-
-You can choose to provide an IAM role that is managed externally to instance-manager by providing the name of the instance profile and the role name.
-
-```yaml
-apiVersion: instancemgr.keikoproj.io/v1alpha1
-kind: InstanceGroup
-metadata:
-  name: hello-world
-  namespace: instance-manager
-spec:
-  strategy: <...>
-  provisioner: eks
-  eks:
-    configuration:
-      roleName: my-eks-role-name
-      instanceProfileName: my-eks-instance-profile-name
-```
-
-if you do not provide these fields, a role will be created for your instance-group by the controller (will require IAM access).
-
-
-##### EKS Managed Node Group (alpha-1)
+#### EKS Managed Node Group (alpha-1)
 
 You can also provision EKS managed node groups by submitting a spec with a different provisioner.
 
@@ -249,6 +241,127 @@ spec:
         value: some-value
 ```
 
+#### EKS Fargate
+The purpose of the fargate provisioner is to enable the management of Fargate profiles.
+
+By associating EKS clusters with a Fargate Profile, pods can be identified for execution through profile selectors. If a to-be-scheduled pod matches any of the selectors in the Fargate Profile, then that pod is scheduled on Fargate. 
+
+An EKS cluster can have multiple Fargate Profiles. If a pod matches multiple Fargate Profiles, Amazon EKS picks one of the matches at random.  
+
+EKS supports clusters with both local worker nodes and Fargate management.  If a pod is scheduled and matches a Fargate selector then Fargate manages the pod.  Otherwise the pod is scheduled on a worker node.  Clusters can be defined without any worker nodes (0) and completely rely upon Fargate for scheduling and running pods. 
+
+More on [Fargate](https://docs.aws.amazon.com/eks/latest/userguide/fargate.html).
+
+The provisioner will manage (create and delete) Fargate Profiles on any EKS cluster (within the account) regardless of whether the cluster was created via CloudFormation, the AWS CLI or the AWS API. 
+
+Below is an example specification for the **eks-fargate** provisioner
+
+```yaml
+apiVersion: instancemgr.keikoproj.io/v1alpha1
+kind: InstanceGroup
+metadata:
+  name: hello-world
+spec:
+  # provision for EKS using Fargate
+  provisioner: eks-fargate
+  strategy:
+    type: managed
+  # provisioner configuration
+  eks-fargate:
+    clusterName: "the-cluster-for-my-pods"
+    podExecutionRoleArn: "arn:aws:iam::123456789012:role/MyPodRole"
+    subnets:
+    - subnet-1a2b3c4d
+    - subnet-4d3c2b1a
+    - subnet-0w9x8y7z
+    selectors:
+    - namespace1:
+      labels:
+        key1: "value1"
+        key2: "value2"
+    - namespace2:
+      labels:
+        key1: "value1"
+        key2: "value2"
+    tags:
+      key1: "value1"
+      key2: "value2"
+
+```
+Read more about the [Fargate Profile](https://docs.aws.amazon.com/eks/latest/userguide/fargate-profile.html).
+
+Note that the eks-fargate provisioner does not accept a Fargate profile name.  Instead, the provisioner creates a unique profile name based upon the cluster name, instance group name and namespace.
+
+If the above *podExecutionRoleArn* parameter is not specified, the provisioner will create a simple, limited role and policy that enables the pod to start but not access any AWS resources.  The role's name will be prefixed by the generated Fargate profile name from above.  That role and policy are shown below. 
+
+```yaml
+Type: 'AWS::IAM::Role'
+Properties:
+  AssumeRolePolicyDocument:
+    Version: 2012-10-17
+    Statement:
+      - Effect: "Allow"
+        Principal:
+          Service: "eks-fargate-pods.amazonaws.com"
+        Action: "sts:AssumeRole"
+  ManagedPolicyArns:
+  - "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
+  Path: /
+  
+```
+Most likely an execution role with access to addtional AWS resources will be required.  In this case, the above IAM role can be used as the basis to create a new, custom role with the IAM policies specific to your pods. Create your new role and your pod specific policies and use the new role's ARN as the *podExecutionRoleArn* parameter value in eks-fargate spec. 
+
+Here is an example of a role with an additional policy for S3 access.
+
+```yaml
+Type: 'AWS::IAM::Role'
+Properties:
+  AssumeRolePolicyDocument:
+    Version: 2012-10-17
+    Statement:
+      - Effect: "Allow"
+        Principal:
+          Service: "eks-fargate-pods.amazonaws.com"
+        Action: "sts:AssumeRole"
+  ManagedPolicyArns:
+  - "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
+  Path: /
+  Policies:
+    - PolicyName: "your chosen name"
+      PolicyDocument:
+        Version: 2012-10-17
+        Statement:
+          - Effect: Allow
+            Action: 
+              - 's3:*'
+            Resource: '*'
+```
+
+AWS's Fargate Profiles are immutable.  Once one is created, it cannot be directly modified.  It first has to be deleted and then re-created with the desired change.
+
+The **eks-fargate** provisioner is built on top of that immutability.  Therefore, if an attempt is made to modify an existing profile, the provisioner will return an error.  You first have to `delete` the profile and follow that with a `create`.
+
+#### Bring your own role
+
+You can choose to provide an IAM role that is managed externally to instance-manager by providing the name of the instance profile and the role name.
+
+```yaml
+apiVersion: instancemgr.keikoproj.io/v1alpha1
+kind: InstanceGroup
+metadata:
+  name: hello-world
+  namespace: instance-manager
+spec:
+  strategy: <...>
+  provisioner: eks
+  eks:
+    configuration:
+      roleName: my-eks-role-name
+      instanceProfileName: my-eks-instance-profile-name
+```
+
+if you do not provide these fields, a role will be created for your instance-group by the controller (will require IAM access).
+
 ### Alpha-2 Version
 
 Please consider that this project is in alpha stages and breaking API changes may happen, we will do our best to not break backwards compatiblity without a deprecation period going further.
@@ -267,4 +380,4 @@ Please see [DEVELOPER.md](.github/DEVELOPER.md).
 
 <!-- Markdown link -->
 [install]: https://github.com/keikoproj/instance-manager/blob/master/docs/README.md
-[SlackUrl]: https://keikoproj.slack.com/messages/instance-manager
\ No newline at end of file
+[SlackUrl]: https://keikoproj.slack.com/messages/instance-manager
diff --git a/api/v1alpha1/instancegroup_types.go b/api/v1alpha1/instancegroup_types.go
index 5406e20..a393939 100644
--- a/api/v1alpha1/instancegroup_types.go
+++ b/api/v1alpha1/instancegroup_types.go
@@ -50,15 +50,21 @@ const (
 	LifecycleStateSpot        = "spot"
 	CRDStrategyName           = "crd"
 	RollingUpdateStrategyName = "rollingupdate"
+	ManagedStrategyName       = "managed"
 	EKSProvisionerName        = "eks"
 	EKSManagedProvisionerName = "eks-managed"
+	EKSFargateProvisionerName = "eks-fargate"
 
 	NodesReady InstanceGroupConditionType = "NodesReady"
 )
 
 var (
-	Strategies   = []string{"crd", "rollingupdate", "managed"}
-	Provisioners = []string{"eks", "eks-managed"}
+	Strategies   = []string{CRDStrategyName, RollingUpdateStrategyName, ManagedStrategyName}
+	Provisioners = []string{
+		EKSProvisionerName,
+		EKSManagedProvisionerName,
+		EKSFargateProvisionerName,
+	}
 
 	DefaultRollingUpdateStrategy = &RollingUpdateStrategy{
 		MaxUnavailable: &intstr.IntOrString{
@@ -129,6 +135,7 @@ type CRDUpdateStrategy struct {
 type InstanceGroupSpec struct {
 	Provisioner        string             `json:"provisioner"`
 	EKSManagedSpec     *EKSManagedSpec    `json:"eks-managed,omitempty"`
+	EKSFargateSpec     *EKSFargateSpec    `json:"eks-fargate,omitempty"`
 	EKSSpec            *EKSSpec           `json:"eks,omitempty"`
 	AwsUpgradeStrategy AwsUpgradeStrategy `json:"strategy"`
 }
@@ -170,6 +177,13 @@ type NodeVolume struct {
 	Type string `json:"type,omitempty"`
 	Size int64  `json:"size,omitempty"`
 }
+type EKSFargateSpec struct {
+	ClusterName         string                `json:"clusterName"`
+	PodExecutionRoleArn string                `json:"podExecutionRoleArn,omitempty"`
+	Subnets             []string              `json:"subnets,omitempty"`
+	Selectors           []EKSFargateSelectors `json:"selectors"`
+	Tags                []map[string]string   `json:"tags,omitempty"`
+}
 
 type EKSManagedConfiguration struct {
 	EksClusterName     string              `json:"clusterName,omitempty"`
@@ -186,6 +200,11 @@ type EKSManagedConfiguration struct {
 	Version            string              `json:"version,omitempty"`
 }
 
+type EKSFargateSelectors struct {
+	Namespace string            `json:"namespace"`
+	Labels    map[string]string `json:"labels,omitempty"`
+}
+
 // InstanceGroupStatus defines the schema of resource Status
 type InstanceGroupStatus struct {
 	CurrentState                  string                   `json:"currentState,omitempty"`
@@ -280,6 +299,14 @@ func (s *InstanceGroupSpec) Validate() error {
 			return err
 		}
 	}
+	if strings.EqualFold(s.Provisioner, EKSFargateProvisionerName) {
+		if err := s.EKSFargateSpec.Validate(); err != nil {
+			return err
+		}
+		if !strings.EqualFold(s.AwsUpgradeStrategy.Type, ManagedStrategyName) {
+			return errors.Errorf("validation failed, strategy '%v' is invalid for the eks-fargate provisioner", s.AwsUpgradeStrategy.Type)
+		}
+	}
 
 	// TODO: Add validation for EKSManagedProvisioner
 
@@ -626,3 +653,51 @@ func (ig *InstanceGroup) SetState(s ReconcileState) {
 func init() {
 	SchemeBuilder.Register(&InstanceGroup{}, &InstanceGroupList{})
 }
+
+func (ig *InstanceGroup) GetEKSFargateSpec() *EKSFargateSpec {
+	return ig.Spec.EKSFargateSpec
+}
+
+func (spec *EKSFargateSpec) Validate() error {
+	return nil
+}
+
+func (spec *EKSFargateSpec) GetClusterName() string {
+	return spec.ClusterName
+}
+
+func (spec *EKSFargateSpec) SetClusterName(name string) {
+	spec.ClusterName = name
+}
+
+func (spec *EKSFargateSpec) GetPodExecutionRoleArn() string {
+	return spec.PodExecutionRoleArn
+}
+
+func (spec *EKSFargateSpec) SetPodExecutionRoleArn(arn string) {
+	spec.PodExecutionRoleArn = arn
+}
+
+func (spec *EKSFargateSpec) GetSubnets() []string {
+	return spec.Subnets
+}
+
+func (spec *EKSFargateSpec) SetSubnets(subnets []string) {
+	spec.Subnets = subnets
+}
+
+func (spec *EKSFargateSpec) GetSelectors() []EKSFargateSelectors {
+	return spec.Selectors
+}
+
+func (spec *EKSFargateSpec) SetSelectors(selectors []EKSFargateSelectors) {
+	spec.Selectors = selectors
+}
+
+func (spec *EKSFargateSpec) GetTags() []map[string]string {
+	return spec.Tags
+}
+
+func (spec *EKSFargateSpec) SetTags(tags []map[string]string) {
+	spec.Tags = tags
+}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 4aac31c..020ecd0 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -137,6 +137,68 @@ func (in *EKSConfiguration) DeepCopy() *EKSConfiguration {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *EKSFargateSelectors) DeepCopyInto(out *EKSFargateSelectors) {
+	*out = *in
+	if in.Labels != nil {
+		in, out := &in.Labels, &out.Labels
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSFargateSelectors.
+func (in *EKSFargateSelectors) DeepCopy() *EKSFargateSelectors {
+	if in == nil {
+		return nil
+	}
+	out := new(EKSFargateSelectors)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *EKSFargateSpec) DeepCopyInto(out *EKSFargateSpec) {
+	*out = *in
+	if in.Subnets != nil {
+		in, out := &in.Subnets, &out.Subnets
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.Selectors != nil {
+		in, out := &in.Selectors, &out.Selectors
+		*out = make([]EKSFargateSelectors, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.Tags != nil {
+		in, out := &in.Tags, &out.Tags
+		*out = make([]map[string]string, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = make(map[string]string, len(*in))
+				for key, val := range *in {
+					(*out)[key] = val
+				}
+			}
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSFargateSpec.
+func (in *EKSFargateSpec) DeepCopy() *EKSFargateSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(EKSFargateSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *EKSManagedConfiguration) DeepCopyInto(out *EKSManagedConfiguration) {
 	*out = *in
@@ -304,6 +366,11 @@ func (in *InstanceGroupSpec) DeepCopyInto(out *InstanceGroupSpec) {
 		*out = new(EKSManagedSpec)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.EKSFargateSpec != nil {
+		in, out := &in.EKSFargateSpec, &out.EKSFargateSpec
+		*out = new(EKSFargateSpec)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.EKSSpec != nil {
 		in, out := &in.EKSSpec, &out.EKSSpec
 		*out = new(EKSSpec)
diff --git a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
index 54bb3f7..497cf42 100644
--- a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
+++ b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.2.9
+    controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: instancegroups.instancemgr.keikoproj.io
 spec:
@@ -178,6 +178,39 @@ spec:
               - maxSize
               - minSize
               type: object
+            eks-fargate:
+              properties:
+                clusterName:
+                  type: string
+                podExecutionRoleArn:
+                  type: string
+                selectors:
+                  items:
+                    properties:
+                      labels:
+                        additionalProperties:
+                          type: string
+                        type: object
+                      namespace:
+                        type: string
+                    required:
+                    - namespace
+                    type: object
+                  type: array
+                subnets:
+                  items:
+                    type: string
+                  type: array
+                tags:
+                  items:
+                    additionalProperties:
+                      type: string
+                    type: object
+                  type: array
+              required:
+              - clusterName
+              - selectors
+              type: object
             eks-managed:
               properties:
                 configuration:
diff --git a/controllers/instancegroup_controller.go b/controllers/instancegroup_controller.go
index 307cdff..fcd8226 100644
--- a/controllers/instancegroup_controller.go
+++ b/controllers/instancegroup_controller.go
@@ -31,6 +31,7 @@ import (
 	kubeprovider "github.com/keikoproj/instance-manager/controllers/providers/kubernetes"
 	"github.com/keikoproj/instance-manager/controllers/provisioners"
 	"github.com/keikoproj/instance-manager/controllers/provisioners/eks"
+	"github.com/keikoproj/instance-manager/controllers/provisioners/eksfargate"
 	"github.com/keikoproj/instance-manager/controllers/provisioners/eksmanaged"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
@@ -204,6 +205,31 @@ func (r *InstanceGroupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
 			)
 			return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
 		}
+
+	case eksfargate.ProvisionerName:
+
+		ctx := eksfargate.New(input)
+		defer r.Update(context.Background(), ctx.GetInstanceGroup())
+		err = HandleReconcileRequest(ctx)
+		if err != nil {
+			r.Log.Error(err,
+				"reconcile failed",
+				"instancegroup", instanceGroup.GetName(),
+				"provisioner", provisionerKind,
+			)
+			ctx.SetState(v1alpha1.ReconcileErr)
+		}
+		if eksfargate.IsRetryable(instanceGroup) {
+			r.Log.Info(
+				"reconcile event ended with requeue",
+				"instancegroup", req.Name,
+				"namespace", req.Namespace,
+				"provisioner", provisionerKind,
+				"resourceVersion", instanceGroup.GetResourceVersion(),
+			)
+			return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
+		}
+
 	default:
 		return ctrl.Result{}, errors.Errorf("provisioner '%v' does not exist", provisionerKind)
 	}
diff --git a/controllers/providers/aws/aws.go b/controllers/providers/aws/aws.go
index d98152f..df67d12 100644
--- a/controllers/providers/aws/aws.go
+++ b/controllers/providers/aws/aws.go
@@ -18,10 +18,6 @@ package aws
 import (
 	"encoding/base64"
 	"fmt"
-	"os"
-	"strings"
-	"time"
-
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
@@ -36,7 +32,10 @@ import (
 	"github.com/keikoproj/aws-sdk-go-cache/cache"
 	"github.com/keikoproj/instance-manager/controllers/common"
 	"github.com/pkg/errors"
+	"os"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"strings"
+	"time"
 )
 
 var (
@@ -96,15 +95,12 @@ set -o xtrace
 }
 
 func (w *AwsWorker) RoleExist(name string) (*iam.Role, bool) {
-	var role *iam.Role
-	input := &iam.GetRoleInput{
-		RoleName: aws.String(name),
-	}
-	out, err := w.IamClient.GetRole(input)
+	out, err := w.GetRole(name)
 	if err != nil {
+		var role *iam.Role
 		return role, false
 	}
-	return out.Role, true
+	return out, true
 }
 
 func (w *AwsWorker) InstanceProfileExist(name string) (*iam.InstanceProfile, bool) {
@@ -726,6 +722,30 @@ func GetAwsEksClient(region string, cacheCfg *cache.Config) eksiface.EKSAPI {
 	return eks.New(sess, config)
 }
 
+func (w *AwsWorker) DeriveEksVpcID(clusterName string) (string, error) {
+	out, err := w.EksClient.DescribeCluster(&eks.DescribeClusterInput{Name: aws.String(clusterName)})
+	if err != nil {
+		return "", err
+	}
+	return aws.StringValue(out.Cluster.ResourcesVpcConfig.VpcId), nil
+}
+
+type CloudResourceReconcileState struct {
+	OngoingState             bool
+	FiniteState              bool
+	FiniteDeleted            bool
+	UpdateRecoverableError   bool
+	UnrecoverableError       bool
+	UnrecoverableDeleteError bool
+}
+
+var OngoingState = CloudResourceReconcileState{OngoingState: true}
+var FiniteState = CloudResourceReconcileState{FiniteState: true}
+var FiniteDeleted = CloudResourceReconcileState{FiniteDeleted: true}
+var UpdateRecoverableError = CloudResourceReconcileState{UpdateRecoverableError: true}
+var UnrecoverableError = CloudResourceReconcileState{UnrecoverableError: true}
+var UnrecoverableDeleteError = CloudResourceReconcileState{UnrecoverableDeleteError: true}
+
 // GetAwsIAMClient returns an IAM client
 func GetAwsIamClient(region string, cacheCfg *cache.Config) iamiface.IAMAPI {
 	config := aws.NewConfig().WithRegion(region).WithCredentialsChainVerboseErrors(true)
@@ -786,3 +806,134 @@ func IsNodeGroupInConditionState(key string, condition string) bool {
 		return false
 	}
 }
+
+func IsProfileInConditionState(key string, condition string) bool {
+
+	conditionStates := map[string]CloudResourceReconcileState{
+		aws.StringValue(nil):                 FiniteDeleted,
+		eks.FargateProfileStatusCreating:     OngoingState,
+		eks.FargateProfileStatusActive:       FiniteState,
+		eks.FargateProfileStatusDeleting:     OngoingState,
+		eks.FargateProfileStatusCreateFailed: UpdateRecoverableError,
+		eks.FargateProfileStatusDeleteFailed: UnrecoverableDeleteError,
+	}
+	state := conditionStates[key]
+	switch condition {
+	case "OngoingState":
+		return state.OngoingState
+	case "FiniteState":
+		return state.FiniteState
+	case "FiniteDeleted":
+		return state.FiniteDeleted
+	case "UpdateRecoverableError":
+		return state.UpdateRecoverableError
+	case "UnrecoverableError":
+		return state.UnrecoverableError
+	case "UnrecoverableDeleteError":
+		return state.UnrecoverableDeleteError
+	default:
+		return false
+	}
+}
+
+const defaultPolicyArn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
+
+func (w *AwsWorker) DetachDefaultPolicyFromDefaultRole() error {
+	var roleName = w.Parameters["DefaultRoleName"].(string)
+	rolePolicy := &iam.DetachRolePolicyInput{
+		PolicyArn: aws.String(defaultPolicyArn),
+		RoleName:  aws.String(roleName),
+	}
+	_, err := w.IamClient.DetachRolePolicy(rolePolicy)
+	return err
+}
+
+func (w *AwsWorker) DeleteDefaultFargateRole() error {
+	var roleName = w.Parameters["DefaultRoleName"].(string)
+	role := &iam.DeleteRoleInput{
+		RoleName: aws.String(roleName),
+	}
+	_, err := w.IamClient.DeleteRole(role)
+	return err
+}
+
+func (w *AwsWorker) GetDefaultFargateRole() (*iam.Role, error) {
+	var roleName = w.Parameters["DefaultRoleName"].(string)
+	return w.GetRole(roleName)
+}
+func (w *AwsWorker) GetRole(roleName string) (*iam.Role, error) {
+	role := &iam.GetRoleInput{
+		RoleName: aws.String(roleName),
+	}
+	resp, err := w.IamClient.GetRole(role)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Role, nil
+}
+func (w *AwsWorker) CreateDefaultFargateRole() error {
+	var roleName = w.Parameters["DefaultRoleName"].(string)
+	var template = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"eks-fargate-pods.amazonaws.com"},"Action":"sts:AssumeRole"}]}`
+	role := &iam.CreateRoleInput{
+		AssumeRolePolicyDocument: &template,
+		Path:                     aws.String("/"),
+		RoleName:                 aws.String(roleName),
+	}
+	_, err := w.IamClient.CreateRole(role)
+	return err
+}
+
+func (w *AwsWorker) AttachDefaultPolicyToDefaultRole() error {
+	var roleName = w.Parameters["DefaultRoleName"].(string)
+	rolePolicy := &iam.AttachRolePolicyInput{
+		PolicyArn: aws.String(defaultPolicyArn),
+		RoleName:  aws.String(roleName),
+	}
+	_, err := w.IamClient.AttachRolePolicy(rolePolicy)
+	return err
+}
+
+func (w *AwsWorker) CreateFargateProfile(arn string) error {
+	tags := w.Parameters["Tags"].(map[string]*string)
+	if len(tags) == 0 {
+		tags = nil
+	}
+	selectors := w.Parameters["Selectors"].([]*eks.FargateProfileSelector)
+	if len(selectors) == 0 {
+		selectors = nil
+	}
+
+	fargateInput := &eks.CreateFargateProfileInput{
+		ClusterName:         aws.String(w.Parameters["ClusterName"].(string)),
+		FargateProfileName:  aws.String(w.Parameters["ProfileName"].(string)),
+		PodExecutionRoleArn: aws.String(arn),
+		Selectors:           selectors,
+		Subnets:             aws.StringSlice(w.Parameters["Subnets"].([]string)),
+		Tags:                tags,
+	}
+
+	_, err := w.EksClient.CreateFargateProfile(fargateInput)
+	return err
+}
+
+func (w *AwsWorker) DeleteFargateProfile() error {
+	deleteInput := &eks.DeleteFargateProfileInput{
+		ClusterName:        aws.String(w.Parameters["ClusterName"].(string)),
+		FargateProfileName: aws.String(w.Parameters["ProfileName"].(string)),
+	}
+	_, err := w.EksClient.DeleteFargateProfile(deleteInput)
+	return err
+}
+
+func (w *AwsWorker) DescribeFargateProfile() (*eks.FargateProfile, error) {
+	describeInput := &eks.DescribeFargateProfileInput{
+		ClusterName:        aws.String(w.Parameters["ClusterName"].(string)),
+		FargateProfileName: aws.String(w.Parameters["ProfileName"].(string)),
+	}
+	output, err := w.EksClient.DescribeFargateProfile(describeInput)
+	if err != nil {
+		return nil, err
+	}
+	return output.FargateProfile, nil
+}
diff --git a/controllers/provisioners/eksfargate/eksfargate.go b/controllers/provisioners/eksfargate/eksfargate.go
new file mode 100644
index 0000000..d46d252
--- /dev/null
+++ b/controllers/provisioners/eksfargate/eksfargate.go
@@ -0,0 +1,384 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package eksfargate
+
+import (
+	"errors"
+	"fmt"
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
+	"github.com/aws/aws-sdk-go/service/eks"
+	"github.com/aws/aws-sdk-go/service/iam"
+	v1alpha1 "github.com/keikoproj/instance-manager/api/v1alpha1"
+	awsprovider "github.com/keikoproj/instance-manager/controllers/providers/aws"
+	"github.com/keikoproj/instance-manager/controllers/provisioners"
+	"hash/fnv"
+	"strconv"
+)
+
+const ProvisionerName = "eks-fargate"
+
+const LastAppliedConfigurationKey = "kubectl.kubernetes.io/last-applied-configuration"
+
+const (
+	PendingRolePolicyAttach = "pendingPolicyCreation"
+	PendingRoleCreation     = "pendingRoleCreation"
+)
+
+var (
+	NonRetryableStates = []v1alpha1.ReconcileState{v1alpha1.ReconcileErr, v1alpha1.ReconcileReady, v1alpha1.ReconcileDeleted}
+)
+
+const (
+	OngoingStateString             = "OngoingState"
+	FiniteStateString              = "FiniteState"
+	FiniteDeletedString            = "FiniteDeleted"
+	UpdateRecoverableErrorString   = "UpdateRecoverableError"
+	UnrecoverableErrorString       = "UnrecoverableError"
+	UnrecoverableDeleteErrorString = "UnrecoverableDeleteError"
+)
+
+func hash(s string) string {
+	h := fnv.New64a()
+	h.Write([]byte(s))
+	i := h.Sum64()
+	return strconv.FormatUint(i, 10)
+}
+
+func becauseErrorContains(err error, code string) bool {
+	if aerr, ok := err.(awserr.Error); ok {
+		if aerr.Code() == code {
+			return true
+		}
+	}
+	return false
+}
+
+func New(p provisioners.ProvisionerInput) *FargateInstanceGroupContext {
+	ctx := &FargateInstanceGroupContext{
+		InstanceGroup: p.InstanceGroup,
+		AwsWorker:     p.AwsWorker,
+		Log:           p.Log.WithName("eks-fargate"),
+	}
+
+	instanceGroup := ctx.GetInstanceGroup()
+
+	instanceGroup.SetState(v1alpha1.ReconcileInit)
+	ctx.processParameters()
+
+	return ctx
+}
+func (ctx *FargateInstanceGroupContext) generateUniqueName() string {
+	instanceGroup := ctx.GetInstanceGroup()
+	spec := instanceGroup.GetEKSFargateSpec()
+	name := fmt.Sprintf("%v-%v-%v", spec.GetClusterName(), instanceGroup.GetNamespace(), instanceGroup.GetName())
+	return fmt.Sprintf("%v-%v", spec.GetClusterName(), hash(name))
+
+}
+
+func (ctx *FargateInstanceGroupContext) processParameters() {
+	var (
+		instanceGroup = ctx.GetInstanceGroup()
+		spec          = instanceGroup.GetEKSFargateSpec()
+		params        = make(map[string]interface{})
+	)
+
+	params["ClusterName"] = spec.GetClusterName()
+	params["ProfileName"] = ctx.generateUniqueName()
+	params["Selectors"] = CreateFargateSelectors(spec.GetSelectors())
+	params["Subnets"] = spec.GetSubnets()
+	params["Tags"] = CreateFargateTags(spec.GetTags())
+	params["DefaultRoleName"] = fmt.Sprintf("%v-role", ctx.generateUniqueName())
+	ctx.AwsWorker.Parameters = params
+}
+
+func CreateFargateSelectors(selectors []v1alpha1.EKSFargateSelectors) []*eks.FargateProfileSelector {
+	var eksSelectors []*eks.FargateProfileSelector
+	for _, selector := range selectors {
+		m := make(map[string]*string)
+		for k, v := range selector.Labels {
+			vv := new(string)
+			*vv = v
+			m[k] = vv
+		}
+		eksSelectors = append(eksSelectors,
+			&eks.FargateProfileSelector{
+				Namespace: &selector.Namespace,
+				Labels:    m})
+	}
+	return eksSelectors
+}
+func CreateFargateTags(tagArray []map[string]string) map[string]*string {
+	tags := make(map[string]*string)
+	for _, t := range tagArray {
+		for k, v := range t {
+			vv := new(string)
+			*vv = v
+			tags[k] = vv
+		}
+	}
+	return tags
+}
+func IsRetryable(instanceGroup *v1alpha1.InstanceGroup) bool {
+	for _, state := range NonRetryableStates {
+		if state == instanceGroup.GetState() {
+			return false
+		}
+	}
+	return true
+}
+func (ctx *FargateInstanceGroupContext) Create() error {
+	var arn string
+	instanceGroup := ctx.GetInstanceGroup()
+	spec := instanceGroup.GetEKSFargateSpec()
+	if instanceGroup.GetEKSFargateSpec().GetPodExecutionRoleArn() == "" {
+		err := ctx.AwsWorker.CreateDefaultFargateRole()
+		if err == nil {
+			ctx.Log.Info("Created default role",
+				"instancegroup",
+				instanceGroup.GetName())
+			return nil
+		}
+		if !becauseErrorContains(err, iam.ErrCodeEntityAlreadyExistsException) {
+			ctx.Log.Error(err,
+				"Creation of the default role failed.",
+				"instancegroup",
+				instanceGroup.GetName())
+			return err
+		}
+
+		role, err := ctx.AwsWorker.GetDefaultFargateRole()
+		if err != nil {
+			ctx.Log.Error(err,
+				"Failed to find the default role",
+				"instancegroup",
+				instanceGroup.GetName())
+			return err
+		}
+		arn = *role.Arn
+
+		err = ctx.AwsWorker.AttachDefaultPolicyToDefaultRole()
+		if err != nil {
+			ctx.Log.Error(err,
+				"Failed to attach the default policy to role",
+				"instancegroup",
+				instanceGroup.GetName())
+			return err
+		}
+		ctx.Log.Info("Attached default policy to role",
+			"instancegroup",
+			instanceGroup.GetName())
+
+	} else {
+		arn = spec.GetPodExecutionRoleArn()
+	}
+
+	err := ctx.AwsWorker.CreateFargateProfile(arn)
+	if err != nil {
+
+		if becauseErrorContains(err, eks.ErrCodeResourceInUseException) {
+			ctx.Log.Info("Creation of the fargate profile delayed.",
+				"instancegroup",
+				instanceGroup.GetName(),
+				"cluster",
+				spec.GetClusterName(),
+				"profile",
+				ctx.generateUniqueName(),
+				"error", err)
+			return nil
+		}
+
+		ctx.Log.Error(err, "Creation of the fargate profile failed",
+			"instancegroup",
+			instanceGroup.GetName(),
+			"cluster",
+			spec.GetClusterName(),
+			"profile",
+			ctx.generateUniqueName())
+		return err
+	}
+
+	ctx.Log.Info("Fargate profile creation started.",
+		"instancegroup",
+		instanceGroup.GetName(),
+		"cluster",
+		spec.GetClusterName(),
+		"profile",
+		ctx.generateUniqueName())
+
+	instanceGroup.SetState(v1alpha1.ReconcileModifying)
+
+	return nil
+}
+func (ctx *FargateInstanceGroupContext) CloudDiscovery() error {
+	profile, err := ctx.AwsWorker.DescribeFargateProfile()
+	if err != nil {
+		profile = &eks.FargateProfile{
+			Status: nil,
+		}
+	}
+	if profile.Status == nil {
+		ctx.DiscoveredState.ProfileStatus = aws.StringValue(nil)
+	} else {
+		ctx.DiscoveredState.ProfileStatus = *profile.Status
+	}
+	return nil
+}
+func (ctx *FargateInstanceGroupContext) Delete() error {
+	instanceGroup := ctx.GetInstanceGroup()
+	spec := instanceGroup.GetEKSFargateSpec()
+
+	worker := ctx.AwsWorker
+	if spec.GetPodExecutionRoleArn() == "" {
+		err := worker.DetachDefaultPolicyFromDefaultRole()
+		// Policy was detached
+		if err == nil {
+			// Role was detached, return and get requeued.
+			ctx.Log.Info("Detached default policy.",
+				"instancegroup",
+				instanceGroup.GetName())
+			return nil
+		}
+		if !becauseErrorContains(err, iam.ErrCodeNoSuchEntityException) {
+			ctx.Log.Error(err,
+				"Detaching the default policy failed.",
+				"instancegroup",
+				instanceGroup.GetName())
+			return err
+		}
+
+		err = ctx.AwsWorker.DeleteDefaultFargateRole()
+		if err == nil {
+			ctx.Log.Info("Deleted the default role.",
+				"instancegroup",
+				instanceGroup.GetName())
+			return nil
+		}
+		if !becauseErrorContains(err, iam.ErrCodeNoSuchEntityException) {
+			ctx.Log.Error(err,
+				"Deleting the default role failed.",
+				"instancegroup",
+				instanceGroup.GetName())
+			return err
+		}
+	}
+
+	err := worker.DeleteFargateProfile()
+	if err != nil {
+
+		if becauseErrorContains(err, eks.ErrCodeResourceInUseException) {
+			ctx.Log.Info("Deletion of the fargate profile delayed",
+				"instancegroup",
+				instanceGroup.GetName(),
+				"cluster",
+				spec.GetClusterName(),
+				"profile",
+				ctx.generateUniqueName(),
+				"error", err)
+			return nil
+		}
+
+		ctx.Log.Error(err, "Deletion of the fargate profile failed.",
+			"instancegroup",
+			instanceGroup.GetName(),
+			"cluster",
+			spec.GetClusterName(),
+			"profile",
+			ctx.generateUniqueName())
+		return err
+	}
+
+	ctx.Log.Info("Deletion of the fargate profile started",
+		"instancegroup",
+		instanceGroup.GetName(),
+		"cluster",
+		spec.GetClusterName(),
+		"profile",
+		ctx.generateUniqueName())
+
+	instanceGroup.SetState(v1alpha1.ReconcileDeleting)
+
+	return nil
+}
+
+func (ctx *FargateInstanceGroupContext) Update() error {
+	instanceGroup := ctx.GetInstanceGroup()
+	annos := instanceGroup.GetObjectMeta().GetAnnotations()
+	// If there is a last-applied-configuration then assume
+	// this is an update and throw an exception
+	if _, ok := annos[LastAppliedConfigurationKey]; ok {
+		return errors.New("update not supported")
+	}
+	instanceGroup.SetState(v1alpha1.ReconcileModified)
+	return nil
+}
+func (ctx *FargateInstanceGroupContext) UpgradeNodes() error {
+	return nil
+}
+func (ctx *FargateInstanceGroupContext) BootstrapNodes() error {
+	return nil
+}
+func (ctx *FargateInstanceGroupContext) IsReady() bool {
+	instanceGroup := ctx.GetInstanceGroup()
+	if instanceGroup.GetState() == v1alpha1.ReconcileModified || instanceGroup.GetState() == v1alpha1.ReconcileDeleted {
+		return true
+	}
+	return false
+}
+func (ctx *FargateInstanceGroupContext) StateDiscovery() {
+	instanceGroup := ctx.GetInstanceGroup()
+	if instanceGroup.GetState() == v1alpha1.ReconcileInit {
+
+		if instanceGroup.ObjectMeta.DeletionTimestamp.IsZero() {
+			if ctx.GetDiscoveredState().IsProvisioned() {
+				// Role exists and the Profile exists in some form (creating)
+				if awsprovider.IsProfileInConditionState(ctx.GetDiscoveredState().GetProfileStatus(), OngoingStateString) {
+					instanceGroup.SetState(v1alpha1.ReconcileModifying)
+					// Role exists and the Profile exists (active)
+				} else if awsprovider.IsProfileInConditionState(ctx.GetDiscoveredState().GetProfileStatus(), FiniteStateString) {
+					instanceGroup.SetState(v1alpha1.ReconcileInitUpdate)
+				} else if awsprovider.IsProfileInConditionState(ctx.GetDiscoveredState().GetProfileStatus(), UpdateRecoverableErrorString) {
+					instanceGroup.SetState(v1alpha1.ReconcileInitDelete)
+				} else {
+					// Profile already exists so return an error
+					instanceGroup.SetState(v1alpha1.ReconcileErr)
+				}
+			} else {
+				instanceGroup.SetState(v1alpha1.ReconcileInitCreate)
+			}
+		} else {
+			if ctx.GetDiscoveredState().IsProvisioned() {
+				if awsprovider.IsProfileInConditionState(ctx.GetDiscoveredState().GetProfileStatus(), OngoingStateString) {
+					// deleting stack is in an ongoing state
+					instanceGroup.SetState(v1alpha1.ReconcileDeleting)
+				} else if awsprovider.IsProfileInConditionState(ctx.GetDiscoveredState().GetProfileStatus(), FiniteStateString) {
+					instanceGroup.SetState(v1alpha1.ReconcileInitDelete)
+				} else {
+					instanceGroup.SetState(v1alpha1.ReconcileErr)
+				}
+			} else {
+				instanceGroup.SetState(v1alpha1.ReconcileDeleted)
+			}
+		}
+	}
+}
+
+func (ctx *FargateInstanceGroupContext) SetState(state v1alpha1.ReconcileState) {
+	ctx.GetInstanceGroup().SetState(state)
+}
+func (ctx *FargateInstanceGroupContext) GetState() v1alpha1.ReconcileState {
+	return ctx.GetInstanceGroup().GetState()
+}
diff --git a/controllers/provisioners/eksfargate/types.go b/controllers/provisioners/eksfargate/types.go
new file mode 100644
index 0000000..3ce95c4
--- /dev/null
+++ b/controllers/provisioners/eksfargate/types.go
@@ -0,0 +1,51 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package eksfargate
+
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/go-logr/logr"
+	"github.com/keikoproj/instance-manager/api/v1alpha1"
+	awsprovider "github.com/keikoproj/instance-manager/controllers/providers/aws"
+)
+
+type DiscoveredState struct {
+	ProfileStatus string
+}
+
+func (ds *DiscoveredState) GetProfileStatus() string {
+	return ds.ProfileStatus
+}
+func (ds *DiscoveredState) IsProvisioned() bool {
+	return ds.GetProfileStatus() != aws.StringValue(nil)
+}
+
+type FargateInstanceGroupContext struct {
+	InstanceGroup   *v1alpha1.InstanceGroup
+	AwsWorker       awsprovider.AwsWorker
+	DiscoveredState DiscoveredState
+	Log             logr.Logger
+}
+
+func (ctx *FargateInstanceGroupContext) GetDiscoveredState() *DiscoveredState {
+	return &ctx.DiscoveredState
+}
+func (ctx *FargateInstanceGroupContext) GetInstanceGroup() *v1alpha1.InstanceGroup {
+	if ctx != nil {
+		return ctx.InstanceGroup
+	}
+	return &v1alpha1.InstanceGroup{}
+}
diff --git a/go.mod b/go.mod
index 9ccd1cc..9527309 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
 	github.com/keikoproj/aws-auth v0.0.0-20200529211237-b481594963fa
 	github.com/keikoproj/aws-sdk-go-cache v0.0.0-20200124200058-ab3c8c94044a
 	github.com/kr/pretty v0.2.0 // indirect
+	github.com/onsi/ginkgo v1.11.0 // indirect
 	github.com/onsi/gomega v1.9.0
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.4.2
@@ -26,9 +27,9 @@ require (
 	golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
 	google.golang.org/appengine v1.6.6 // indirect
 	k8s.io/api v0.17.2
+	k8s.io/apiextensions-apiserver v0.17.0 // indirect
 	k8s.io/apimachinery v0.17.2
 	k8s.io/client-go v0.17.2
 	k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 // indirect
 	sigs.k8s.io/controller-runtime v0.4.0
-	sigs.k8s.io/controller-tools v0.2.9 // indirect
 )
diff --git a/go.sum b/go.sum
index 7e92247..d7a21e7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,4 @@
-cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -34,9 +32,7 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
 github.com/aws/aws-sdk-go v1.25.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.30.24 h1:y3JPD51VuEmVqN3BEDVm4amGpDma2cKJcDPuAU1OR58=
 github.com/aws/aws-sdk-go v1.30.24/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -77,7 +73,6 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -139,8 +134,6 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM=
-github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
@@ -148,7 +141,6 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
 github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -156,10 +148,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -171,7 +161,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
@@ -205,13 +194,10 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
 github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
 github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
@@ -221,9 +207,7 @@ github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2E
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -243,7 +227,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -259,11 +242,7 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
-github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -273,7 +252,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -284,27 +262,22 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.6.0 h1:8XTW0fcJZEq9q+Upcyws4JSGua2MFysCL5xkaSgHc+M=
 github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
 github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -313,23 +286,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
 github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
 github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
 github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
 github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
@@ -347,12 +315,10 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -362,7 +328,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
@@ -384,7 +349,6 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
 go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -392,21 +356,17 @@ go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8
 go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
 golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -423,7 +383,6 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -442,14 +401,11 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -457,26 +413,22 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -484,7 +436,6 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI=
 golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -492,7 +443,6 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -510,11 +460,8 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws=
 golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -524,10 +471,8 @@ gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@@ -547,7 +492,6 @@ google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2M
 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -566,31 +510,23 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0=
-gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
 k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
 k8s.io/api v0.16.9/go.mod h1:Y7dZNHs1Xy0mSwSlzL9QShi6qkljnN41yR8oWCRTDe8=
 k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
 k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
 k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
-k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
 k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
 k8s.io/apiextensions-apiserver v0.17.0 h1:+XgcGxqaMztkbbvsORgCmHIb4uImHKvTjNyu7b8gRnA=
 k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=
-k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
 k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
 k8s.io/apimachinery v0.16.9/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE=
 k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
@@ -598,7 +534,6 @@ k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
 k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
 k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
 k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
-k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
 k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
 k8s.io/client-go v0.16.9/go.mod h1:ThjPlh7Kx+XoBFOCt775vx5J7atwY7F/zaFzTco5gL0=
 k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
@@ -611,22 +546,17 @@ k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
-k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
-k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
 k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ=
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
-k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a h1:uy5HAgt4Ha5rEMbhZA+aM1j2cq5LmR6LQ71EYC2sVH4=
 k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
-k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 h1:Ly1Oxdu5p5ZFmiVT71LFgeZETvMfZ1iBIGeOenT2JeM=
 k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
@@ -637,10 +567,7 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
 modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
 sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
 sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
-sigs.k8s.io/controller-tools v0.2.9 h1:DEZuCFWANX2zlZVMlf/XmhSq0HzmGCZ/GTdPJig62ig=
-sigs.k8s.io/controller-tools v0.2.9/go.mod h1:ArP7w60JQKkZf7UU2oWTVnEhoNGA+sOMyuSuS+JFNDQ=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
-sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
 sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
 sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=


Test patch:
diff --git a/api/v1alpha1/instancegroup_types_test.go b/api/v1alpha1/instancegroup_types_test.go
new file mode 100644
index 0000000..2fa54f4
--- /dev/null
+++ b/api/v1alpha1/instancegroup_types_test.go
@@ -0,0 +1,102 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package v1alpha1
+
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"testing"
+)
+
+type EksUnitTest struct {
+	Spec InstanceGroupSpec
+}
+
+func (u *EksUnitTest) Run(t *testing.T) string {
+	err := u.Spec.Validate()
+	if err == nil {
+		return aws.StringValue(nil)
+	} else {
+		return err.Error()
+	}
+}
+
+func TestInstanceGroupSpecValidate(t *testing.T) {
+	type args struct {
+		spec InstanceGroupSpec
+	}
+	testFunction := func(t *testing.T, args args) string {
+		testCase := EksUnitTest{
+			Spec: args.spec,
+		}
+		return testCase.Run(t)
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "eks-fargate with managed strategy",
+			args: args{
+				spec: buildEKSFargateInstanceGroupSpec("eks-fargate", "managed"),
+			},
+			want: "",
+		},
+		{
+			name: "eks-bogus provisioner",
+			args: args{
+				spec: buildEKSFargateInstanceGroupSpec("eks-bogus", "managed"),
+			},
+			want: "validation failed, provisioner 'eks-bogus' is invalid",
+		},
+		{
+			name: "eks-fargate with bad strategy",
+			args: args{
+				spec: buildEKSFargateInstanceGroupSpec("eks-fargate", "rollingUpdate"),
+			},
+			want: "validation failed, strategy 'rollingUpdate' is invalid for the eks-fargate provisioner",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := testFunction(t, tt.args)
+			if got != tt.want {
+				t.Errorf("%v: got %v, want %v", tt.name, got, tt.want)
+			}
+		})
+
+	}
+}
+
+func buildEKSFargateInstanceGroupSpec(provisioner, strategy string) InstanceGroupSpec {
+	igs := InstanceGroupSpec{
+		Provisioner: provisioner,
+		AwsUpgradeStrategy: AwsUpgradeStrategy{
+			Type: strategy,
+		},
+		EKSFargateSpec: &EKSFargateSpec{
+			ClusterName:         "",
+			PodExecutionRoleArn: "",
+			Subnets:             []string{"subnet-1111111", "subnet-222222"},
+			Tags: []map[string]string{
+				{
+					"key":   "a-key",
+					"value": "a-value",
+				},
+			},
+		}}
+
+	return igs
+}
diff --git a/controllers/provisioners/eksfargate/eksfargate_test.go b/controllers/provisioners/eksfargate/eksfargate_test.go
new file mode 100644
index 0000000..c0fc878
--- /dev/null
+++ b/controllers/provisioners/eksfargate/eksfargate_test.go
@@ -0,0 +1,941 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package eksfargate
+
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
+	"github.com/aws/aws-sdk-go/service/eks"
+	"github.com/aws/aws-sdk-go/service/eks/eksiface"
+	"github.com/aws/aws-sdk-go/service/iam"
+	"github.com/aws/aws-sdk-go/service/iam/iamiface"
+	"github.com/keikoproj/instance-manager/api/v1alpha1"
+	awsprovider "github.com/keikoproj/instance-manager/controllers/providers/aws"
+	"github.com/keikoproj/instance-manager/controllers/provisioners"
+	"github.com/pkg/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+	"testing"
+	"time"
+)
+
+type EksFargateUnitTest struct {
+	Description                           string
+	Provisioner                           *FargateInstanceGroupContext
+	InstanceGroup                         *v1alpha1.InstanceGroup
+	ProfileBasic                          *eks.FargateProfile
+	ProfileFromDescribe                   *eks.FargateProfile
+	ProfileFromCreate                     *eks.FargateProfile
+	ProfileExists                         bool
+	MakeCreateProfileFail                 bool
+	MakeCreateProfileRetry                bool
+	MakeDeleteProfileFail                 bool
+	MakeDeleteProfileRetry                bool
+	MakeDescribeProfileFail               bool
+	CheckArnFor                           string
+	MakeCreateRoleFail                    bool
+	MakeGetRoleFail                       bool
+	CreateRoleDupFound                    bool
+	DetachRolePolicyNoSuchEntityException bool
+	DetachRolePolicyFail                  bool
+	MakeAttachRolePolicyFail              bool
+	MakeDeleteRoleFail                    bool
+	DeleteRoleNoSuchEntityException       bool
+}
+
+type FakeIG struct {
+	Name         string
+	Namespace    string
+	ClusterName  string
+	CurrentState string
+	IsDeleting   bool
+}
+
+type stubEKS struct {
+	eksiface.EKSAPI
+	ProfileFromDescribe     *eks.FargateProfile
+	ProfileFromCreate       *eks.FargateProfile
+	ProfileBasic            *eks.FargateProfile
+	ProfileExists           bool
+	MakeCreateProfileFail   bool
+	MakeCreateProfileRetry  bool
+	MakeDeleteProfileFail   bool
+	MakeDeleteProfileRetry  bool
+	MakeDescribeProfileFail bool
+	CheckArnFor             string
+}
+type stubIAM struct {
+	iamiface.IAMAPI
+	Profile                               *eks.FargateProfile
+	MakeCreateRoleFail                    bool
+	MakeGetRoleFail                       bool
+	CreateRoleDupFound                    bool
+	MakeAttachRolePolicyFail              bool
+	DetachRolePolicyNoSuchEntityException bool
+	DetachRolePolicyFail                  bool
+	MakeDeleteRoleFail                    bool
+	DeleteRoleNoSuchEntityException       bool
+}
+
+func (s *stubIAM) DetachRolePolicy(input *iam.DetachRolePolicyInput) (*iam.DetachRolePolicyOutput, error) {
+	if s.DetachRolePolicyFail == false {
+		output := &iam.DetachRolePolicyOutput{}
+		return output, nil
+	} else {
+		if s.DetachRolePolicyNoSuchEntityException {
+			return nil, awserr.New(iam.ErrCodeNoSuchEntityException, "not found on detach", errors.New(""))
+		}
+		return nil, errors.New("detach role policy failed")
+	}
+}
+func (s *stubIAM) DeleteRole(input *iam.DeleteRoleInput) (*iam.DeleteRoleOutput, error) {
+	if s.MakeDeleteRoleFail == false {
+		output := &iam.DeleteRoleOutput{}
+		return output, nil
+	} else {
+		if s.DeleteRoleNoSuchEntityException {
+			return nil, awserr.New(iam.ErrCodeNoSuchEntityException, "not found on delete role", errors.New(""))
+		}
+		return nil, errors.New("delete role failed")
+	}
+}
+func (s *stubIAM) CreateRole(input *iam.CreateRoleInput) (*iam.CreateRoleOutput, error) {
+	if s.MakeCreateRoleFail == false {
+		if s.CreateRoleDupFound {
+			return nil, awserr.New(iam.ErrCodeEntityAlreadyExistsException, "duplicate found", errors.New(""))
+		}
+		output := &iam.CreateRoleOutput{
+			Role: &iam.Role{
+				Arn: aws.String("the profile execution arn"),
+			},
+		}
+		return output, nil
+	} else {
+		return nil, errors.New("create role failed")
+	}
+}
+func (s *stubIAM) GetRole(input *iam.GetRoleInput) (*iam.GetRoleOutput, error) {
+	if s.MakeGetRoleFail == false {
+		output := &iam.GetRoleOutput{
+			Role: &iam.Role{
+				Arn: aws.String("eksfargate::dummy_arn"),
+			},
+		}
+		return output, nil
+	} else {
+		return nil, errors.New("get role failed")
+	}
+}
+func (s *stubIAM) AttachRolePolicy(input *iam.AttachRolePolicyInput) (*iam.AttachRolePolicyOutput, error) {
+	if s.MakeAttachRolePolicyFail == false {
+		return &iam.AttachRolePolicyOutput{}, nil
+	} else {
+		return nil, errors.New("attach role policy failed")
+	}
+}
+
+func (s *stubEKS) DescribeFargateProfile(input *eks.DescribeFargateProfileInput) (*eks.DescribeFargateProfileOutput, error) {
+	if s.MakeDescribeProfileFail {
+		return nil, awserr.New(eks.ErrCodeResourceNotFoundException, "not found", errors.New("notFound"))
+	}
+
+	output := &eks.DescribeFargateProfileOutput{
+		FargateProfile: s.ProfileFromDescribe,
+	}
+	return output, nil
+}
+
+func (s *stubEKS) CreateFargateProfile(input *eks.CreateFargateProfileInput) (*eks.CreateFargateProfileOutput, error) {
+	output := &eks.CreateFargateProfileOutput{
+		FargateProfile: s.ProfileFromCreate,
+	}
+	if s.MakeCreateProfileRetry {
+		return nil, awserr.New(eks.ErrCodeResourceInUseException, "resource in use", errors.New("resource in use"))
+	}
+
+	if s.MakeCreateProfileFail {
+		return nil, errors.New("create profile failed")
+	}
+	if s.CheckArnFor != "" && s.CheckArnFor != *input.PodExecutionRoleArn {
+		return nil, errors.New("bad arn")
+	}
+
+	return output, nil
+}
+
+func (s *stubEKS) DeleteFargateProfile(input *eks.DeleteFargateProfileInput) (*eks.DeleteFargateProfileOutput, error) {
+	if s.MakeDeleteProfileRetry {
+		return nil, awserr.New(eks.ErrCodeResourceInUseException, "resource in use", errors.New("resource in use"))
+	}
+	if s.MakeDeleteProfileFail == false {
+		output := &eks.DeleteFargateProfileOutput{}
+		return output, nil
+	} else {
+		return nil, errors.New("delete profile failed")
+	}
+}
+
+func getProfile(state string) *eks.FargateProfile {
+	return &eks.FargateProfile{
+		Status: aws.String(state)}
+}
+
+func (f *FakeIG) getInstanceGroup() *v1alpha1.InstanceGroup {
+	var deletionTimestamp metav1.Time
+
+	if f.Name == "" {
+		f.Name = "instancegroup-1"
+	}
+	if f.Namespace == "" {
+		f.Namespace = "namespace-1"
+	}
+	if f.ClusterName == "" {
+		f.ClusterName = "EKS-Test"
+	}
+	if f.CurrentState == "" {
+		f.CurrentState = "Null"
+	}
+	if f.IsDeleting == true {
+		deletionTimestamp = metav1.Time{Time: time.Now()}
+	} else {
+		nilTime := time.Time{}
+		deletionTimestamp = metav1.Time{Time: nilTime}
+	}
+	instanceGroup := &v1alpha1.InstanceGroup{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:              f.Name,
+			Namespace:         f.Namespace,
+			DeletionTimestamp: &deletionTimestamp,
+		},
+		Spec: v1alpha1.InstanceGroupSpec{
+			Provisioner: "eks-fargate",
+			EKSFargateSpec: &v1alpha1.EKSFargateSpec{
+				ClusterName:         "",
+				PodExecutionRoleArn: "",
+				Subnets:             []string{"subnet-1111111", "subnet-222222"},
+				Tags: []map[string]string{
+					{
+						"key":   "a-key",
+						"value": "a-value",
+					},
+				},
+			},
+		},
+		Status: v1alpha1.InstanceGroupStatus{
+			CurrentState: f.CurrentState,
+		},
+	}
+
+	return instanceGroup
+}
+func (u *EksFargateUnitTest) BuildProvisioner(t *testing.T) *FargateInstanceGroupContext {
+	aws := &awsprovider.AwsWorker{
+		EksClient: &stubEKS{
+			ProfileBasic:            u.ProfileBasic,
+			ProfileFromDescribe:     u.ProfileFromDescribe,
+			ProfileFromCreate:       u.ProfileFromCreate,
+			ProfileExists:           u.ProfileExists,
+			MakeCreateProfileFail:   u.MakeCreateProfileFail,
+			MakeCreateProfileRetry:  u.MakeCreateProfileRetry,
+			MakeDeleteProfileFail:   u.MakeDeleteProfileFail,
+			MakeDeleteProfileRetry:  u.MakeDeleteProfileRetry,
+			MakeDescribeProfileFail: u.MakeDescribeProfileFail,
+			CheckArnFor:             u.CheckArnFor,
+		},
+		IamClient: &stubIAM{
+			MakeCreateRoleFail:                    u.MakeCreateRoleFail,
+			MakeGetRoleFail:                       u.MakeGetRoleFail,
+			CreateRoleDupFound:                    u.CreateRoleDupFound,
+			MakeAttachRolePolicyFail:              u.MakeAttachRolePolicyFail,
+			DetachRolePolicyNoSuchEntityException: u.DetachRolePolicyNoSuchEntityException,
+			DetachRolePolicyFail:                  u.DetachRolePolicyFail,
+			MakeDeleteRoleFail:                    u.MakeDeleteRoleFail,
+			DeleteRoleNoSuchEntityException:       u.DeleteRoleNoSuchEntityException,
+		},
+	}
+	ctrl.SetLogger(zap.Logger(true))
+	p := provisioners.ProvisionerInput{
+		InstanceGroup: u.InstanceGroup,
+		AwsWorker:     *aws,
+		Log:           ctrl.Log.WithName("unit-test").WithName("InstanceGroup"),
+	}
+	provisioner := New(p)
+
+	u.Provisioner = provisioner
+	return provisioner
+}
+
+func (u *EksFargateUnitTest) Run(t *testing.T) v1alpha1.ReconcileState {
+
+	provisioner := u.BuildProvisioner(t)
+
+	if err := provisioner.CloudDiscovery(); err != nil {
+		t.Fatal(err)
+	}
+
+	provisioner.StateDiscovery()
+	return u.InstanceGroup.GetState()
+}
+func TestAllStateDiscovery(t *testing.T) {
+	type args struct {
+		description  string
+		profileState *string
+		isDeleting   bool
+	}
+	testFunction := func(t *testing.T, args args) v1alpha1.ReconcileState {
+		ig := FakeIG{IsDeleting: args.isDeleting, CurrentState: string(v1alpha1.ReconcileInit)}
+		testCase := EksFargateUnitTest{
+			Description:   args.description,
+			InstanceGroup: ig.getInstanceGroup(),
+			ProfileFromDescribe: &eks.FargateProfile{
+				Status: args.profileState,
+			},
+		}
+		return testCase.Run(t)
+	}
+	tests := []struct {
+		name string
+		args args
+		want v1alpha1.ReconcileState
+	}{
+		{
+			name: "TestStateDiscoveryReconcileModified",
+			args: args{
+				description:  "StateDiscovery - when a profile already exist (active), state should be InitUpdate",
+				profileState: aws.String(eks.FargateProfileStatusActive),
+				isDeleting:   false,
+			},
+			want: v1alpha1.ReconcileInitUpdate,
+		},
+		{
+			name: "TestStateDiscoveryInitCreate",
+			args: args{
+				description:  "StateDiscovery - when a profile NOT exist , state should be ReconcileInitCreate",
+				profileState: nil,
+				isDeleting:   false,
+			},
+			want: v1alpha1.ReconcileInitCreate,
+		},
+		{
+			name: "TestStateDiscoveryReconcileModifying1",
+			args: args{
+				description:  "StateDiscovery - when a profile is CREATING, state should be ReconcileModifying",
+				profileState: aws.String(eks.FargateProfileStatusCreating),
+				isDeleting:   false,
+			},
+			want: v1alpha1.ReconcileModifying,
+		},
+		{
+			name: "TestStateDiscoveryReconcileModifying2",
+			args: args{
+				description:  "StateDiscovery - when a profile is DELETING, state should be ReconcileModifying",
+				profileState: aws.String(eks.FargateProfileStatusDeleting),
+				isDeleting:   false,
+			},
+			want: v1alpha1.ReconcileModifying,
+		},
+		{
+			name: "TestStateDiscoveryErr1",
+			args: args{
+				description:  "StateDiscovery - resource creation state and the profile is DELETE_FAILED, state should be ReconcileErr",
+				profileState: aws.String(eks.FargateProfileStatusDeleteFailed),
+				isDeleting:   false,
+			},
+			want: v1alpha1.ReconcileErr,
+		},
+		{
+			name: "TestStateDiscoveryReconcileRecoverableUpdate",
+			args: args{
+				description:  "StateDiscovery - resource creation state and the profile is CREATE_FAILED, state should be ReconcileErr",
+				profileState: aws.String(eks.FargateProfileStatusCreateFailed),
+				isDeleting:   false,
+			},
+			want: v1alpha1.ReconcileInitDelete,
+		},
+		{
+			name: "TestStateDiscoveryReconcileDeleted",
+			args: args{
+				description:  "StateDiscovery - resource is deleting state and no profile, state should be ReconcileDeleted",
+				profileState: nil,
+				isDeleting:   true,
+			},
+			want: v1alpha1.ReconcileDeleted,
+		},
+		{
+			name: "TestStateDiscoveryReconcileInitDelete",
+			args: args{
+				description:  "StateDiscovery - resource is deleting state and the profile is ACTIVE, state should be ReconcileInitDelete",
+				profileState: aws.String(eks.FargateProfileStatusActive),
+				isDeleting:   true,
+			},
+			want: v1alpha1.ReconcileInitDelete,
+		},
+		{
+			name: "TestStateDiscoveryReconcileDeleting1",
+			args: args{
+				description:  "StateDiscovery - resource is deleting state and the profile is CREATING, state should be ReconcileDeleting",
+				profileState: aws.String(eks.FargateProfileStatusCreating),
+				isDeleting:   true,
+			},
+			want: v1alpha1.ReconcileDeleting,
+		},
+		{
+			name: "TestStateDiscoveryReconcileDeleting2",
+			args: args{
+				description:  "StateDiscovery - resource is deleting state and the profile is DELETING, state should be ReconcileDeleting",
+				profileState: aws.String(eks.FargateProfileStatusDeleting),
+				isDeleting:   true,
+			},
+			want: v1alpha1.ReconcileDeleting,
+		},
+		{
+			name: "TestStateDiscoveryDeleteWithRecoverableUpate",
+			args: args{
+				description:  "StateDiscovery - resource delete state and the profile is CREATE_FAILED, state should be ReconcileErr",
+				profileState: aws.String(eks.FargateProfileStatusCreateFailed),
+				isDeleting:   true,
+			},
+			want: v1alpha1.ReconcileErr,
+		},
+		{
+			name: "TestStateDiscoveryErr2",
+			args: args{
+				description:  "StateDiscovery - resource delete state and the profile is DELETE_FAILED, state should be ReconcileErr",
+				profileState: aws.String(eks.FargateProfileStatusDeleteFailed),
+				isDeleting:   true,
+			},
+			want: v1alpha1.ReconcileErr,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := testFunction(t, tt.args)
+			if got != tt.want {
+				t.Errorf("%v: got %v, want %v", tt.name, got, tt.want)
+			}
+		})
+
+	}
+}
+func TestIsReadyPositive(t *testing.T) {
+	ig := FakeIG{}
+	testCase := EksFargateUnitTest{
+		InstanceGroup: ig.getInstanceGroup(),
+	}
+	ctx := testCase.BuildProvisioner(t)
+	instanceGroup := ctx.GetInstanceGroup()
+	instanceGroup.SetState(v1alpha1.ReconcileModified)
+	if !ctx.IsReady() {
+		t.Fatal("TestIsReadyPositive: got false, expected: true")
+	}
+	instanceGroup.SetState(v1alpha1.ReconcileDeleted)
+	if !ctx.IsReady() {
+		t.Fatal("TestIsReadyPositive: got false, expected: true")
+	}
+}
+
+func TestIsReadyNegative(t *testing.T) {
+	ig := FakeIG{}
+	testCase := EksFargateUnitTest{
+		InstanceGroup: ig.getInstanceGroup(),
+	}
+	ctx := testCase.BuildProvisioner(t)
+	instanceGroup := ctx.GetInstanceGroup()
+	instanceGroup.SetState(v1alpha1.ReconcileInit)
+	if ctx.IsReady() {
+		t.Fatal("TestIsReadyNegative: got true, expected: false")
+	}
+	instanceGroup.SetState(v1alpha1.ReconcileInitCreate)
+	if ctx.IsReady() {
+		t.Fatal("TestIsReadyNegative: got true, expected: false")
+	}
+}
+
+func TestUpgradeNodes(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+		ProfileBasic:  nil,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	if ctx.UpgradeNodes() != nil {
+		t.Fatal("TestUpgradeNodes: expected nil but got exception")
+	}
+}
+func TestBootstrapNodes(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+		ProfileBasic:  nil,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	if ctx.BootstrapNodes() != nil {
+		t.Fatal("TestBootstrapNodes: got an exception when not expected")
+	}
+}
+func TestCreateFargateTags(t *testing.T) {
+	input := []map[string]string{{"key1": "value1"}}
+	output := CreateFargateTags(input)
+	if len(output) != 1 || *output["key1"] != "value1" {
+		t.Fatalf("TestCreateFargateTags: output is %v", output)
+	}
+}
+func TestCreateFargateSelectors(t *testing.T) {
+	input := []v1alpha1.EKSFargateSelectors{
+		{
+			Namespace: "default",
+			Labels:    map[string]string{"foo": "bar"},
+		},
+	}
+	output := CreateFargateSelectors(input)
+	if len(output) != 1 {
+		t.Fatalf("TestCreateFargateSelectors: length is %v", len(output))
+	}
+	if *output[0].Namespace != "default" || len(output[0].Labels) != 1 {
+		t.Fatalf("TestCreateFargateSelectors: label length is %v", len(output[0].Labels))
+
+	}
+	if *output[0].Labels["foo"] != "bar" {
+		t.Fatalf("TestCreateFargateSelectors: bad label value: %v", output[0].Labels["foo"])
+	}
+}
+func TestCloudDiscoveryFailureGettingProfile(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	testCase := EksFargateUnitTest{InstanceGroup: instanceGroup,
+		MakeDescribeProfileFail: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	ctx.CloudDiscovery()
+	if ctx.GetDiscoveredState().GetProfileStatus() != aws.StringValue(nil) {
+		t.Fatalf("TestGetStateFailureGettingProfile: expected nil but got a status")
+	}
+}
+func TestCloudDiscoverySuccessGettingProfile(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	testCase := EksFargateUnitTest{InstanceGroup: instanceGroup,
+		ProfileFromDescribe: &eks.FargateProfile{
+			Status: aws.String(eks.FargateProfileStatusActive),
+		},
+	}
+	ctx := testCase.BuildProvisioner(t)
+	ctx.CloudDiscovery()
+
+	if ctx.GetDiscoveredState().GetProfileStatus() == aws.StringValue(nil) {
+		t.Fatalf("TestGetStateSuccessGettingProfile: expected profile but got a nil")
+	}
+}
+func TestCreateWithSuppliedArnSuccessProfileCreation(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	instanceGroup.Spec.EKSFargateSpec.SetPodExecutionRoleArn("a:b:c:d:e:f")
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+		CheckArnFor:   "a:b:c:d:e:f",
+	}
+	ctx := testCase.BuildProvisioner(t)
+	if ctx.Create() != nil {
+		t.Fatal("TestCreateWithSuppliedArnSuccessProfileCreation: got error, expected: nil")
+	}
+}
+func TestCreateWithoutArnCreateRoleFail(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:      instanceGroup,
+		MakeCreateRoleFail: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err == nil {
+		t.Fatal("TestCreateWithoutArnProfileCreation: expected error got nil")
+	}
+	if err.Error() != "create role failed" {
+		t.Fatalf("TestCreateWithoutArnProfileCreation: Unexpected error. Got %v", err)
+	}
+}
+func TestCreateWithoutArnCreateRoleFindsDup(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:      instanceGroup,
+		CreateRoleDupFound: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err != nil {
+		t.Fatal("TestCreateWithoutArnCreateRoleFindsDup: expected nil")
+	}
+}
+func TestCreateWithoutArnAttachRolePolicyFails(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:            instanceGroup,
+		CreateRoleDupFound:       true,
+		MakeAttachRolePolicyFail: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err == nil {
+		t.Fatal("TestCreateWithoutArnAttachRolePolicyFails: expected error")
+	}
+	if err.Error() != "attach role policy failed" {
+		t.Fatalf("TestCreateWithoutArnAttachRolePolicyFails: bad error message.  Got %v", err.Error())
+	}
+}
+func TestCreateWithoutArnGetRoleFailed(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:      instanceGroup,
+		CreateRoleDupFound: true,
+		MakeGetRoleFail:    true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err == nil {
+		t.Fatal("TestCreateWithoutArnGetRoleFailed: expected error got nil")
+	}
+	if err.Error() != "get role failed" {
+		t.Fatalf("TestCreateWithoutArnGetRoleFailed: bad error message.  Got %v", err.Error())
+	}
+}
+func TestCreateWithoutArnCreateProfileSucceeds(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:      instanceGroup,
+		CreateRoleDupFound: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err != nil {
+		t.Fatal("TestCreateWithoutArnCreateProfileSucceeds: expected nil")
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileModifying {
+		t.Fatalf("TestCreateWithoutArnCreateProfileSucceeds: expected ReconcileModifying state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestCreateWithoutArnCreateProfileFails(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:         instanceGroup,
+		CreateRoleDupFound:    true,
+		MakeCreateProfileFail: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err == nil {
+		t.Fatal("TestCreateWithoutArnCreateProfileFails: expected error")
+	}
+	if err.Error() != "create profile failed" {
+		t.Fatalf("TestCreateWithoutArnCreateProfileFails: Bad error message.  Got %v", err.Error())
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestCreateWithoutArnCreateProfileFails: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestCreateProfileWithRetry(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:          instanceGroup,
+		CreateRoleDupFound:     true,
+		MakeCreateProfileRetry: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Create()
+	if err != nil {
+		t.Fatalf("TestCreateProfileWithRetry: expected nil.  Got %v", err)
+	}
+}
+func TestUpdate(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Update()
+	if err != nil {
+		t.Fatalf("TestUpdate: expected nil but got error: %v", err)
+	}
+}
+func TestUpdate1(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.SetAnnotations(map[string]string{LastAppliedConfigurationKey: "blah"})
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Update()
+	if err == nil {
+		t.Fatal("TestUpdate1: error but got nil")
+	}
+	if err.Error() != "update not supported" {
+		t.Fatalf("TestUpdate1: bad error message.  Got %v", err.Error())
+	}
+}
+func TestDeleteWithArnDeleteProfileSuccess(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	instanceGroup.Spec.EKSFargateSpec.SetPodExecutionRoleArn("a:b:c:d:e:f")
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err != nil {
+		t.Fatalf("TestDeleteWithArnDeleteProfileSuccess: expected nil.  Got %v", err)
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileDeleting {
+		t.Fatalf("TestDeleteWithArnDeleteProfileSuccess: expected ReconcileDeleting state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithArnDeleteProfileFail(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	instanceGroup.Spec.EKSFargateSpec.SetPodExecutionRoleArn("a:b:c:d:e:f")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:         instanceGroup,
+		MakeDeleteProfileFail: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err == nil {
+		t.Fatal("TestDeleteWithArnDeleteProfileFail: expected error.  Got nil")
+	}
+	if err.Error() != "delete profile failed" {
+		t.Fatalf("TestDeleteWithArnDeleteProfileFail: bad error message.  Got %v", err.Error())
+	}
+
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestDeleteWithArnDeleteProfileFail: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithoutArnDetachPolicyFromRoleCreated(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyNoSuchEntityException: true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err != nil {
+		t.Fatalf("TestDeleteWithoutArnDetachPolicyFromRoleCreated: expected nil.  Got %v", err)
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestDeleteWithoutArnDetachPolicyFromRoleCreated: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithoutArnDetachPolicyFromRoleFailed(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyFail:                  true,
+		DetachRolePolicyNoSuchEntityException: false,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err == nil {
+		t.Fatal("TestDeleteWithoutArnDetachPolicyFromRoleFailed: expected error.  Got nil")
+	}
+	if err.Error() != "detach role policy failed" {
+		t.Fatalf("TestDeleteWithoutArnDetachPolicyFromRoleFailed: bad error.  Got %v", err.Error())
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestDeleteWithoutArnDetachPolicyFromRoleFailed: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithoutArnDeleteRoleFailed(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyFail:                  true,
+		DetachRolePolicyNoSuchEntityException: true,
+		MakeDeleteRoleFail:                    true,
+		DeleteRoleNoSuchEntityException:       false,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err == nil {
+		t.Fatal("TestDeleteWithoutArnDeleteRoleFailed: expected error.  Got nil")
+	}
+	if err.Error() != "delete role failed" {
+		t.Fatalf("TestDeleteWithoutArnDeleteRoleFailed: bad error.  Got %v", err.Error())
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestDeleteWithoutArnDeleteRoleFailed: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithoutArnDeleteRoleCreated(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyNoSuchEntityException: false,
+		DeleteRoleNoSuchEntityException:       true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err != nil {
+		t.Fatalf("TestDeleteWithoutArnDeleteRoleCreated: expected nil.  Got %v", err)
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestDeleteWithoutArnDeleteRoleCreated: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithoutArnDeleteProfileFailed(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyFail:                  true,
+		DetachRolePolicyNoSuchEntityException: true,
+		MakeDeleteRoleFail:                    true,
+		DeleteRoleNoSuchEntityException:       true,
+		MakeDeleteProfileFail:                 true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err == nil {
+		t.Fatal("TestDeleteWithoutArnDeleteProfileFailed: expected error.  Got nil")
+	}
+	if err.Error() != "delete profile failed" {
+		t.Fatalf("TestDeleteWithoutArnDeleteProfileFailed: bad error message.  Got %v", err.Error())
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileInit {
+		t.Fatalf("TestDeleteWithoutArnDeleteProfileFailed: expected ReconcileInit state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithoutArnDeleteProfileSuccess(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyFail:                  true,
+		DetachRolePolicyNoSuchEntityException: true,
+		MakeDeleteRoleFail:                    true,
+		DeleteRoleNoSuchEntityException:       true,
+		MakeDeleteProfileFail:                 false,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err != nil {
+		t.Fatalf("TestDeleteWithoutArnDeleteProfileSuccess: expected nil.  Got %v", err)
+	}
+	if instanceGroup.GetState() != v1alpha1.ReconcileDeleting {
+		t.Fatalf("TestDeleteWithoutArnDeleteProfileSuccess: expected ReconcileDeleting state.  Got %v", instanceGroup.GetState())
+	}
+}
+func TestDeleteWithRetry(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup:                         instanceGroup,
+		DetachRolePolicyFail:                  true,
+		DetachRolePolicyNoSuchEntityException: true,
+		MakeDeleteRoleFail:                    true,
+		DeleteRoleNoSuchEntityException:       true,
+		MakeDeleteProfileFail:                 true,
+		MakeDeleteProfileRetry:                true,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	err := ctx.Delete()
+	if err != nil {
+		t.Fatalf("TestDeleteWithRetry: expected nil.  Got %v", err)
+	}
+}
+func TestIsRetryableFalse1(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.SetState(v1alpha1.ReconcileDeleted)
+	retryable := IsRetryable(instanceGroup)
+	if retryable {
+		t.Fatal("TestIsRetryableFalse1: expected false.")
+	}
+
+}
+func TestIsRetryableFalse2(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.SetState(v1alpha1.ReconcileReady)
+	retryable := IsRetryable(instanceGroup)
+	if retryable {
+		t.Fatal("TestIsRetryableFalse2: expected false.")
+	}
+
+}
+func TestIsRetryableFalse3(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.SetState(v1alpha1.ReconcileErr)
+	retryable := IsRetryable(instanceGroup)
+	if retryable {
+		t.Fatal("TestIsRetryableFalse3: expected false.")
+	}
+
+}
+func TestIsRetryableTrue(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.SetState(v1alpha1.ReconcileModifying)
+	retryable := IsRetryable(instanceGroup)
+	if !retryable {
+		t.Fatal("TestIsRetryableTrue: expected true.")
+	}
+
+}
+func TestCreateProfileName(t *testing.T) {
+	ig := FakeIG{}
+	instanceGroup := ig.getInstanceGroup()
+	instanceGroup.Spec.EKSFargateSpec.SetClusterName("TestNameCluster")
+	testCase := EksFargateUnitTest{
+		InstanceGroup: instanceGroup,
+	}
+	ctx := testCase.BuildProvisioner(t)
+	profileName := ctx.generateUniqueName()
+	if profileName != "TestNameCluster-13281223259712081633" {
+		t.Fatalf("TestCreateProfileName: profile name is %v.", profileName)
+	}
+}
diff --git a/test-bdd/features/01_create.feature b/test-bdd/features/01_create.feature
index 551b38b..eca8d83 100644
--- a/test-bdd/features/01_create.feature
+++ b/test-bdd/features/01_create.feature
@@ -34,3 +34,9 @@ Feature: CRUD Create
     And the resource should converge to selector .status.currentState=ready
     And 2 nodes should be ready
 
+  Scenario: Create a fargate profile with default execution role 
+    Given an EKS cluster
+    Then I create a resource instance-group-fargate.yaml
+    And the resource should be created
+    And the fargate profile should be found
+
diff --git a/test-bdd/features/04_delete.feature b/test-bdd/features/04_delete.feature
index 7746e00..b97e0e8 100644
--- a/test-bdd/features/04_delete.feature
+++ b/test-bdd/features/04_delete.feature
@@ -25,4 +25,9 @@ Feature: CRUD Delete
     Given an EKS cluster
     When I delete a resource instance-group-managed.yaml
     Then 0 nodes should be found
-    And the resource should be deleted
\ No newline at end of file
+    And the resource should be deleted
+
+  Scenario: Delete a fargate profile
+    Given an EKS cluster
+    Then I delete a resource instance-group-fargate.yaml
+    And the fargate profile should be not found
diff --git a/test-bdd/main_test.go b/test-bdd/main_test.go
index 0ac6126..3a72235 100644
--- a/test-bdd/main_test.go
+++ b/test-bdd/main_test.go
@@ -18,13 +18,6 @@ package main
 import (
 	"flag"
 	"fmt"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-	"testing"
-	"time"
-
 	"github.com/cucumber/godog"
 	"github.com/cucumber/godog/colors"
 	"github.com/cucumber/godog/gherkin"
@@ -39,6 +32,12 @@ import (
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/tools/clientcmd"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
 )
 
 type FunctionalTest struct {
@@ -61,6 +60,9 @@ const (
 
 	DefaultWaiterInterval = time.Second * 30
 	DefaultWaiterRetries  = 40
+
+	FargateProfileFound    = "found"
+	FargateProfileNotFound = "not found"
 )
 
 var InstanceGroupSchema = schema.GroupVersionResource{
@@ -119,6 +121,7 @@ func FeatureContext(s *godog.Suite) {
 	s.Step(`^the resource condition ([^"]*) should be (true|false)$`, t.theResourceConditionShouldBe)
 	s.Step(`^I (create|delete) a resource ([^"]*)$`, t.iOperateOnResource)
 	s.Step(`^I update a resource ([^"]*) with ([^"]*) set to ([^"]*)$`, t.iUpdateResourceWithField)
+	s.Step(`^the fargate profile should be (found|not found)$`, t.theFargateProfileShouldBeFound)
 
 }
 
@@ -324,7 +327,6 @@ func (t *FunctionalTest) theResourceShouldBe(state string) error {
 	}
 
 }
-
 func (t *FunctionalTest) theResourceShouldConvergeToSelector(selector string) error {
 	var (
 		counter  int
@@ -369,6 +371,44 @@ func (t *FunctionalTest) nodesShouldBeWithLabel(count int, state, key, value str
 	return t.waitForNodeCountState(count, state, selector)
 }
 
+func (t *FunctionalTest) theFargateProfileShouldBeFound(state string) error {
+	const profileName = "test-bdd-profile-name"
+	var (
+		counter int
+		exists  bool
+	)
+	for {
+		exists = true
+		if counter >= DefaultWaiterRetries {
+			return errors.New("waiter timed out waiting for fargate profile state")
+		}
+		log.Infof("BDD >> waiting for resource %v/%v to become %v", t.ResourceNamespace, t.ResourceName, state)
+		_, err := t.DynamicClient.Resource(InstanceGroupSchema).Namespace(t.ResourceNamespace).Get(t.ResourceName, metav1.GetOptions{})
+
+		if err != nil {
+			if !kerrors.IsNotFound(err) {
+				return err
+			}
+			log.Infof("BDD >> %v/%v is not found: %v", t.ResourceNamespace, t.ResourceName, err)
+			exists = false
+		}
+		switch state {
+		case FargateProfileFound:
+			if exists {
+				log.Infof("BDD >> success - resource %v/%v found", t.ResourceNamespace, t.ResourceName)
+				return nil
+			}
+		case FargateProfileNotFound:
+			if !exists {
+				log.Infof("BDD >> success - resource %v/%v not found", t.ResourceNamespace, t.ResourceName)
+				return nil
+			}
+		}
+		counter++
+		time.Sleep(DefaultWaiterInterval)
+	}
+}
+
 func (t *FunctionalTest) waitForNodeCountState(count int, state, selector string) error {
 	var (
 		counter int
diff --git a/test-bdd/templates/instance-group-fargate.yaml b/test-bdd/templates/instance-group-fargate.yaml
new file mode 100644
index 0000000..5a79d57
--- /dev/null
+++ b/test-bdd/templates/instance-group-fargate.yaml
@@ -0,0 +1,29 @@
+apiVersion: instancemgr.keikoproj.io/v1alpha1
+kind: InstanceGroup
+metadata:
+  name: hello-world-fargate
+  namespace: instance-manager
+spec:
+  provisioner: eks-fargate
+  strategy:
+      type: managed
+  eks-fargate:
+      clusterName: {{ .ClusterName }}
+      podExecutionRoleArn: ""
+      subnets: {{range $element := .Subnets}}
+        - {{$element}}
+      {{ end }}
+      selectors:
+      - namespace: "default"
+        labels:
+          key1: "value1"
+          key2: "value2"
+      - namespace: "instance-manager"
+        labels:
+          key1: "value1"
+          key2: "value2"
+          key3: "value3"
+      tags:
+        - key9: value101
+          key7: value202
+          key8: value9