Repository: crossplane-contrib/provider-helm
Pull request #31 (2020-09-28 06:55:39)

Prevent a ProviderConfig that is in use from being deleted
<!--
Thank you for helping to improve Crossplane!

Please read through https://git.io/fj2m9 if this is your first time opening a
Crossplane pull request. Find us in https://slack.crossplane.io/messages/dev if
you need any help contributing.
-->

### Description of your changes

<!--
Briefly describe what this pull request does. Be sure to direct your reviewers'
attention to anything that needs special consideration.

We love pull requests that resolve an open Crossplane issue. If yours does, you
can uncomment the below line to indicate which issue your PR fixes, for example
"Fixes #500":

-->
This PR prevents a Helm `ProviderConfig` from being deleted while a `Release` is still using it. It uses a pattern we hope to introduce for all providers, as detailed in https://github.com/crossplane/crossplane/issues/1737.

I have:

- [x] Read and followed Crossplane's [contribution process].
- [x] Run `make reviewable test` to ensure this PR is ready for review.

### How has this code been tested

I've tested this end-to-end by deploying the updated `in-composition` example using the latest master build of Crossplane (plus https://github.com/crossplane/crossplane/pull/1779), I can confirm that it does in fact ensure the `ProviderConfig` is not deleted while it's still in use. Unfortunately there are other issues unrelated to this PR that I will detail in a follow-up comment.

```console
$ kubectl get -f examples/in-composition/cluster.yaml 
NAME        READY   COMPOSITION
mycluster   True    wordpressclusters.example.crossplane.io

$ kubectl get gcp
NAME                                                     READY   SYNCED   STATE     ENDPOINT       LOCATION      RECLAIM-POLICY   AGE
gkecluster.container.gcp.crossplane.io/mycluster-4g4jm   True    True     RUNNING   35.193.97.64   us-central1                    38m

NAME                                                   READY   SYNCED   STATE     CLUSTER-REF       NODE-POOL-CLASS   RECLAIM-POLICY   AGE
nodepool.container.gcp.crossplane.io/mycluster-8s6r5   True    True     RUNNING   mycluster-4g4jm                                      38m

NAME                                            PROJECT-ID              AGE
providerconfig.gcp.crossplane.io/default        crossplane-playground   151m

NAME                                                READY   SYNCED
network.compute.gcp.crossplane.io/mycluster-vnstz   True    True

NAME                                                          READY   SYNCED   STATE      VERSION     AGE
cloudsqlinstance.database.gcp.crossplane.io/mycluster-j2g6d   True    True     RUNNABLE   MYSQL_5_6   38m

$ kubectl get helm
NAME                                          AGE
providerconfig.helm.crossplane.io/mycluster   39m

NAME                                                                          AGE   CONFIG-NAME   RESOURCE-KIND   RESOURCE-NAME
providerconfigusage.helm.crossplane.io/7e5d0357-a2a9-4f98-a605-190b532e678e   35m   mycluster     Release         mycluster-vnhtz

$ kubectl delete -f examples/in-composition/cluster.yaml 
wordpresscluster.example.crossplane.io "mycluster" deleted

$ kubectl describe providerconfig.helm|tail -n5
  Users:  1
Events:
  Type     Reason           Age   From                                              Message
  ----     ------           ----  ----                                              -------
  Warning  UsageAccounting  15s   providerconfig/providerconfig.helm.crossplane.io  Blocking deletion while usages still exist
```

<!--
Before reviewers can be confident in the correctness of this pull request, it
needs to tested and shown to be correct. Briefly describe the testing that has
already been done or which is planned for this change.
-->

[contribution process]: https://git.io/fj2m9



Patch:
diff --git a/apis/release/v1alpha1/types.go b/apis/release/v1alpha1/types.go
index 2740e67..a026f54 100644
--- a/apis/release/v1alpha1/types.go
+++ b/apis/release/v1alpha1/types.go
@@ -17,10 +17,11 @@ limitations under the License.
 package v1alpha1
 
 import (
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
 	"helm.sh/helm/v3/pkg/release"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
+
+	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
 )
 
 // A ChartSpec defines the chart spec for a Release
@@ -102,7 +103,7 @@ type ReleaseStatus struct {
 // +kubebuilder:subresource:status
 // +kubebuilder:printcolumn:name="STATE",type="string",JSONPath=".status.atProvider.state"
 // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
-// +kubebuilder:resource:scope=Cluster
+// +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,helm}
 type Release struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
diff --git a/apis/release/v1alpha1/zz_generated.managed.go b/apis/release/v1alpha1/zz_generated.managed.go
index 1817112..78e0b5a 100644
--- a/apis/release/v1alpha1/zz_generated.managed.go
+++ b/apis/release/v1alpha1/zz_generated.managed.go
@@ -17,25 +17,7 @@ limitations under the License.
 
 package v1alpha1
 
-import (
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
-	corev1 "k8s.io/api/core/v1"
-)
-
-// GetBindingPhase of this Release.
-func (mg *Release) GetBindingPhase() runtimev1alpha1.BindingPhase {
-	return mg.Status.GetBindingPhase()
-}
-
-// GetClaimReference of this Release.
-func (mg *Release) GetClaimReference() *corev1.ObjectReference {
-	return mg.Spec.ClaimReference
-}
-
-// GetClassReference of this Release.
-func (mg *Release) GetClassReference() *corev1.ObjectReference {
-	return mg.Spec.ClassReference
-}
+import runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
 
 // GetCondition of this Release.
 func (mg *Release) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
@@ -60,31 +42,11 @@ func (mg *Release) GetProviderReference() *runtimev1alpha1.Reference {
 	return mg.Spec.ProviderReference
 }
 
-// GetReclaimPolicy of this Release.
-func (mg *Release) GetReclaimPolicy() runtimev1alpha1.ReclaimPolicy {
-	return mg.Spec.ReclaimPolicy
-}
-
 // GetWriteConnectionSecretToReference of this Release.
 func (mg *Release) GetWriteConnectionSecretToReference() *runtimev1alpha1.SecretReference {
 	return mg.Spec.WriteConnectionSecretToReference
 }
 
-// SetBindingPhase of this Release.
-func (mg *Release) SetBindingPhase(p runtimev1alpha1.BindingPhase) {
-	mg.Status.SetBindingPhase(p)
-}
-
-// SetClaimReference of this Release.
-func (mg *Release) SetClaimReference(r *corev1.ObjectReference) {
-	mg.Spec.ClaimReference = r
-}
-
-// SetClassReference of this Release.
-func (mg *Release) SetClassReference(r *corev1.ObjectReference) {
-	mg.Spec.ClassReference = r
-}
-
 // SetConditions of this Release.
 func (mg *Release) SetConditions(c ...runtimev1alpha1.Condition) {
 	mg.Status.SetConditions(c...)
@@ -108,11 +70,6 @@ func (mg *Release) SetProviderReference(r *runtimev1alpha1.Reference) {
 	mg.Spec.ProviderReference = r
 }
 
-// SetReclaimPolicy of this Release.
-func (mg *Release) SetReclaimPolicy(r runtimev1alpha1.ReclaimPolicy) {
-	mg.Spec.ReclaimPolicy = r
-}
-
 // SetWriteConnectionSecretToReference of this Release.
 func (mg *Release) SetWriteConnectionSecretToReference(r *runtimev1alpha1.SecretReference) {
 	mg.Spec.WriteConnectionSecretToReference = r
diff --git a/apis/v1alpha1/register.go b/apis/v1alpha1/register.go
index c93f4d4..1ce40a7 100644
--- a/apis/v1alpha1/register.go
+++ b/apis/v1alpha1/register.go
@@ -45,6 +45,20 @@ var (
 	ProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigKind)
 )
 
+// ProviderConfigUsage type metadata.
+var (
+	ProviderConfigUsageKind             = reflect.TypeOf(ProviderConfigUsage{}).Name()
+	ProviderConfigUsageGroupKind        = schema.GroupKind{Group: Group, Kind: ProviderConfigUsageKind}.String()
+	ProviderConfigUsageKindAPIVersion   = ProviderConfigUsageKind + "." + SchemeGroupVersion.String()
+	ProviderConfigUsageGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigUsageKind)
+
+	ProviderConfigUsageListKind             = reflect.TypeOf(ProviderConfigUsageList{}).Name()
+	ProviderConfigUsageListGroupKind        = schema.GroupKind{Group: Group, Kind: ProviderConfigUsageListKind}.String()
+	ProviderConfigUsageListKindAPIVersion   = ProviderConfigUsageListKind + "." + SchemeGroupVersion.String()
+	ProviderConfigUsageListGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigUsageListKind)
+)
+
 func init() {
 	SchemeBuilder.Register(&ProviderConfig{}, &ProviderConfigList{})
+	SchemeBuilder.Register(&ProviderConfigUsage{}, &ProviderConfigUsageList{})
 }
diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go
index 43e9130..a02d10c 100644
--- a/apis/v1alpha1/types.go
+++ b/apis/v1alpha1/types.go
@@ -17,8 +17,10 @@ limitations under the License.
 package v1alpha1
 
 import (
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
 )
 
 // A ProviderConfigSpec defines the desired state of a Provider.
@@ -28,7 +30,7 @@ type ProviderConfigSpec struct {
 
 // A ProviderConfigStatus defines the status of a Provider.
 type ProviderConfigStatus struct {
-	runtimev1alpha1.ResourceStatus `json:",inline"`
+	runtimev1alpha1.ProviderConfigStatus `json:",inline"`
 }
 
 // +kubebuilder:object:root=true
@@ -42,12 +44,8 @@ type ProviderConfig struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
 
-	Spec ProviderConfigSpec `json:"spec"`
-
-	// TODO(hasan): Remove below HACK, once https://github.com/crossplane/crossplane/issues/1687 resolved
-	// HACK
+	Spec   ProviderConfigSpec   `json:"spec"`
 	Status ProviderConfigStatus `json:"status,omitempty"`
-	// END OF HACK
 }
 
 // +kubebuilder:object:root=true
@@ -58,3 +56,27 @@ type ProviderConfigList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ProviderConfig `json:"items"`
 }
+
+// +kubebuilder:object:root=true
+
+// A ProviderConfigUsage indicates that a resource is using a ProviderConfig.
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:printcolumn:name="CONFIG-NAME",type="string",JSONPath=".providerConfigRef.name"
+// +kubebuilder:printcolumn:name="RESOURCE-KIND",type="string",JSONPath=".resourceRef.kind"
+// +kubebuilder:printcolumn:name="RESOURCE-NAME",type="string",JSONPath=".resourceRef.name"
+// +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,helm}
+type ProviderConfigUsage struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	v1alpha1.ProviderConfigUsage `json:",inline"`
+}
+
+// +kubebuilder:object:root=true
+
+// ProviderConfigUsageList contains a list of ProviderConfigUsage
+type ProviderConfigUsageList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ProviderConfigUsage `json:"items"`
+}
diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go
index 65aeed9..a2f5493 100644
--- a/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/v1alpha1/zz_generated.deepcopy.go
@@ -102,7 +102,7 @@ func (in *ProviderConfigSpec) DeepCopy() *ProviderConfigSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ProviderConfigStatus) DeepCopyInto(out *ProviderConfigStatus) {
 	*out = *in
-	in.ResourceStatus.DeepCopyInto(&out.ResourceStatus)
+	in.ProviderConfigStatus.DeepCopyInto(&out.ProviderConfigStatus)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigStatus.
@@ -114,3 +114,61 @@ func (in *ProviderConfigStatus) DeepCopy() *ProviderConfigStatus {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProviderConfigUsage) DeepCopyInto(out *ProviderConfigUsage) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.ProviderConfigUsage = in.ProviderConfigUsage
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsage.
+func (in *ProviderConfigUsage) DeepCopy() *ProviderConfigUsage {
+	if in == nil {
+		return nil
+	}
+	out := new(ProviderConfigUsage)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ProviderConfigUsage) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProviderConfigUsageList) DeepCopyInto(out *ProviderConfigUsageList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ProviderConfigUsage, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsageList.
+func (in *ProviderConfigUsageList) DeepCopy() *ProviderConfigUsageList {
+	if in == nil {
+		return nil
+	}
+	out := new(ProviderConfigUsageList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ProviderConfigUsageList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
diff --git a/apis/v1alpha1/zz_generated.pc.go b/apis/v1alpha1/zz_generated.pc.go
new file mode 100644
index 0000000..5f7f047
--- /dev/null
+++ b/apis/v1alpha1/zz_generated.pc.go
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 The Crossplane Authors.
+
+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.
+*/
+// Code generated by angryjet. DO NOT EDIT.
+
+package v1alpha1
+
+import runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+
+// GetCondition of this ProviderConfig.
+func (p *ProviderConfig) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
+	return p.Status.GetCondition(ct)
+}
+
+// GetCredentialsSecretReference of this ProviderConfig.
+func (p *ProviderConfig) GetCredentialsSecretReference() *runtimev1alpha1.SecretKeySelector {
+	return p.Spec.CredentialsSecretRef
+}
+
+// GetUsers of this ProviderConfig.
+func (p *ProviderConfig) GetUsers() int64 {
+	return p.Status.Users
+}
+
+// SetConditions of this ProviderConfig.
+func (p *ProviderConfig) SetConditions(c ...runtimev1alpha1.Condition) {
+	p.Status.SetConditions(c...)
+}
+
+// SetCredentialsSecretReference of this ProviderConfig.
+func (p *ProviderConfig) SetCredentialsSecretReference(r *runtimev1alpha1.SecretKeySelector) {
+	p.Spec.CredentialsSecretRef = r
+}
+
+// SetUsers of this ProviderConfig.
+func (p *ProviderConfig) SetUsers(i int64) {
+	p.Status.Users = i
+}
diff --git a/apis/v1alpha1/zz_generated.pcu.go b/apis/v1alpha1/zz_generated.pcu.go
new file mode 100644
index 0000000..c9b2127
--- /dev/null
+++ b/apis/v1alpha1/zz_generated.pcu.go
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 The Crossplane Authors.
+
+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.
+*/
+// Code generated by angryjet. DO NOT EDIT.
+
+package v1alpha1
+
+import runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+
+// GetProviderConfigReference of this ProviderConfigUsage.
+func (p *ProviderConfigUsage) GetProviderConfigReference() runtimev1alpha1.Reference {
+	return p.ProviderConfigReference
+}
+
+// GetResourceReference of this ProviderConfigUsage.
+func (p *ProviderConfigUsage) GetResourceReference() runtimev1alpha1.TypedReference {
+	return p.ResourceReference
+}
+
+// SetProviderConfigReference of this ProviderConfigUsage.
+func (p *ProviderConfigUsage) SetProviderConfigReference(r runtimev1alpha1.Reference) {
+	p.ProviderConfigReference = r
+}
+
+// SetResourceReference of this ProviderConfigUsage.
+func (p *ProviderConfigUsage) SetResourceReference(r runtimev1alpha1.TypedReference) {
+	p.ResourceReference = r
+}
diff --git a/apis/v1alpha1/zz_generated.pculist.go b/apis/v1alpha1/zz_generated.pculist.go
new file mode 100644
index 0000000..468bad0
--- /dev/null
+++ b/apis/v1alpha1/zz_generated.pculist.go
@@ -0,0 +1,29 @@
+/*
+Copyright 2020 The Crossplane Authors.
+
+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.
+*/
+// Code generated by angryjet. DO NOT EDIT.
+
+package v1alpha1
+
+import resource "github.com/crossplane/crossplane-runtime/pkg/resource"
+
+// GetItems of this ProviderConfigUsageList.
+func (p *ProviderConfigUsageList) GetItems() []resource.ProviderConfigUsage {
+	items := make([]resource.ProviderConfigUsage, len(p.Items))
+	for i := range p.Items {
+		items[i] = &p.Items[i]
+	}
+	return items
+}
diff --git a/cmd/provider/main.go b/cmd/provider/main.go
index 0024bc2..4a9cae6 100644
--- a/cmd/provider/main.go
+++ b/cmd/provider/main.go
@@ -25,7 +25,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 
 	"github.com/crossplane/crossplane-runtime/pkg/logging"
-	crossplaneapis "github.com/crossplane/crossplane/apis"
 
 	"github.com/crossplane-contrib/provider-helm/apis"
 	"github.com/crossplane-contrib/provider-helm/pkg/controller"
@@ -56,7 +55,6 @@ func main() {
 	mgr, err := ctrl.NewManager(cfg, ctrl.Options{SyncPeriod: syncPeriod})
 	kingpin.FatalIfError(err, "Cannot create controller manager")
 
-	kingpin.FatalIfError(crossplaneapis.AddToScheme(mgr.GetScheme()), "Cannot add core Crossplane APIs to scheme")
 	kingpin.FatalIfError(apis.AddToScheme(mgr.GetScheme()), "Cannot add Helm APIs to scheme")
 	kingpin.FatalIfError(controller.Setup(mgr, log), "Cannot setup Helm controllers")
 	kingpin.FatalIfError(mgr.Start(ctrl.SetupSignalHandler()), "Cannot start controller manager")
diff --git a/config/crd/helm.crossplane.io_providerconfigs.yaml b/config/crd/helm.crossplane.io_providerconfigs.yaml
index dde0baf..591ea06 100644
--- a/config/crd/helm.crossplane.io_providerconfigs.yaml
+++ b/config/crd/helm.crossplane.io_providerconfigs.yaml
@@ -63,16 +63,8 @@ spec:
               type: object
           type: object
         status:
-          description: 'TODO(hasan): Remove below HACK, once https://github.com/crossplane/crossplane/issues/1687 resolved HACK'
+          description: A ProviderConfigStatus defines the status of a Provider.
           properties:
-            bindingPhase:
-              description: Phase represents the binding phase of a managed resource or claim. Unbindable resources cannot be bound, typically because they are currently unavailable, or still being created. Unbound resource are available for binding, and Bound resources have successfully bound to another resource. Deprecated. See https://github.com/crossplane/crossplane/issues/1670
-              enum:
-              - Unbindable
-              - Unbound
-              - Bound
-              - Released
-              type: string
             conditions:
               description: Conditions of the resource.
               items:
@@ -101,6 +93,10 @@ spec:
                 - type
                 type: object
               type: array
+            users:
+              description: Users of this provider configuration.
+              format: int64
+              type: integer
           type: object
       required:
       - spec
diff --git a/config/crd/helm.crossplane.io_providerconfigusages.yaml b/config/crd/helm.crossplane.io_providerconfigusages.yaml
new file mode 100644
index 0000000..d79c355
--- /dev/null
+++ b/config/crd/helm.crossplane.io_providerconfigusages.yaml
@@ -0,0 +1,91 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.3.0
+  creationTimestamp: null
+  name: providerconfigusages.helm.crossplane.io
+spec:
+  additionalPrinterColumns:
+  - JSONPath: .metadata.creationTimestamp
+    name: AGE
+    type: date
+  - JSONPath: .providerConfigRef.name
+    name: CONFIG-NAME
+    type: string
+  - JSONPath: .resourceRef.kind
+    name: RESOURCE-KIND
+    type: string
+  - JSONPath: .resourceRef.name
+    name: RESOURCE-NAME
+    type: string
+  group: helm.crossplane.io
+  names:
+    categories:
+    - crossplane
+    - provider
+    - helm
+    kind: ProviderConfigUsage
+    listKind: ProviderConfigUsageList
+    plural: providerconfigusages
+    singular: providerconfigusage
+  scope: Cluster
+  subresources: {}
+  validation:
+    openAPIV3Schema:
+      description: A ProviderConfigUsage indicates that a resource is using a ProviderConfig.
+      properties:
+        apiVersion:
+          description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+          type: string
+        kind:
+          description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+          type: string
+        metadata:
+          type: object
+        providerConfigRef:
+          description: ProviderConfigReference to the provider config being used.
+          properties:
+            name:
+              description: Name of the referenced object.
+              type: string
+          required:
+          - name
+          type: object
+        resourceRef:
+          description: ResourceReference to the managed resource using the provider config.
+          properties:
+            apiVersion:
+              description: APIVersion of the referenced object.
+              type: string
+            kind:
+              description: Kind of the referenced object.
+              type: string
+            name:
+              description: Name of the referenced object.
+              type: string
+            uid:
+              description: UID of the referenced object.
+              type: string
+          required:
+          - apiVersion
+          - kind
+          - name
+          type: object
+      required:
+      - providerConfigRef
+      - resourceRef
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
diff --git a/config/crd/helm.crossplane.io_releases.yaml b/config/crd/helm.crossplane.io_releases.yaml
index a3cdf31..9b86986 100644
--- a/config/crd/helm.crossplane.io_releases.yaml
+++ b/config/crd/helm.crossplane.io_releases.yaml
@@ -17,6 +17,10 @@ spec:
     type: date
   group: helm.crossplane.io
   names:
+    categories:
+    - crossplane
+    - provider
+    - helm
     kind: Release
     listKind: ReleaseList
     plural: releases
@@ -39,56 +43,6 @@ spec:
         spec:
           description: A ReleaseSpec defines the desired state of a Release.
           properties:
-            claimRef:
-              description: ClaimReference specifies the resource claim to which this managed resource will be bound. ClaimReference is set automatically during dynamic provisioning. Deprecated. See https://github.com/crossplane/crossplane/issues/1670
-              properties:
-                apiVersion:
-                  description: API version of the referent.
-                  type: string
-                fieldPath:
-                  description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.'
-                  type: string
-                kind:
-                  description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-                  type: string
-                name:
-                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
-                  type: string
-                namespace:
-                  description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
-                  type: string
-                resourceVersion:
-                  description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
-                  type: string
-                uid:
-                  description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
-                  type: string
-              type: object
-            classRef:
-              description: ClassReference specifies the resource class that was used to dynamically provision this managed resource, if any. Deprecated. See https://github.com/crossplane/crossplane/issues/1670
-              properties:
-                apiVersion:
-                  description: API version of the referent.
-                  type: string
-                fieldPath:
-                  description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.'
-                  type: string
-                kind:
-                  description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-                  type: string
-                name:
-                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
-                  type: string
-                namespace:
-                  description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
-                  type: string
-                resourceVersion:
-                  description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
-                  type: string
-                uid:
-                  description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
-                  type: string
-              type: object
             deletionPolicy:
               description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified.
               enum:
@@ -269,12 +223,6 @@ spec:
               required:
               - name
               type: object
-            reclaimPolicy:
-              description: "ReclaimPolicy specifies what will happen to this managed resource when its resource claim is deleted, and what will happen to the underlying external resource when the managed resource is deleted. The \"Delete\" policy causes the managed resource to be deleted when its bound resource claim is deleted, and in turn causes the external resource to be deleted when its managed resource is deleted. The \"Retain\" policy causes the managed resource to be retained, in binding phase \"Released\", when its resource claim is deleted, and in turn causes the external resource to be retained when its managed resource is deleted. The \"Delete\" policy is used when no policy is specified. \n Deprecated. DeletionPolicy takes precedence when both are set. See https://github.com/crossplane/crossplane-runtime/issues/179."
-              enum:
-              - Retain
-              - Delete
-              type: string
             rollbackLimit:
               description: RollbackRetriesLimit is max number of attempts to retry Helm deployment by rolling back the release.
               format: int32
@@ -309,14 +257,6 @@ spec:
                   description: Status is the status of a release
                   type: string
               type: object
-            bindingPhase:
-              description: Phase represents the binding phase of a managed resource or claim. Unbindable resources cannot be bound, typically because they are currently unavailable, or still being created. Unbound resource are available for binding, and Bound resources have successfully bound to another resource. Deprecated. See https://github.com/crossplane/crossplane/issues/1670
-              enum:
-              - Unbindable
-              - Unbound
-              - Bound
-              - Released
-              type: string
             conditions:
               description: Conditions of the resource.
               items:
diff --git a/examples/in-composition/cluster-role.yaml b/examples/in-composition/cluster-role.yaml
deleted file mode 100644
index 5fce20a..0000000
--- a/examples/in-composition/cluster-role.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: wordpressclusters.example.crossplane.io
-  labels:
-    rbac.crossplane.io/aggregate-to-crossplane: "true"
-rules:
-  - apiGroups:
-      - example.crossplane.io
-    resources:
-      - wordpressclusters
-      - wordpressclusters/status
-    verbs:
-      - "*"
\ No newline at end of file
diff --git a/examples/in-composition/composition.yaml b/examples/in-composition/composition.yaml
index 45ddfa6..439e949 100644
--- a/examples/in-composition/composition.yaml
+++ b/examples/in-composition/composition.yaml
@@ -4,11 +4,10 @@ metadata:
   name: wordpressclusters.example.crossplane.io
 spec:
   writeConnectionSecretsToNamespace: crossplane-system
-  reclaimPolicy: Delete
-  from:
+  compositeTypeRef:
     apiVersion: example.crossplane.io/v1alpha1
     kind: WordpressCluster
-  to:
+  resources:
     - base:
         apiVersion: compute.gcp.crossplane.io/v1beta1
         kind: Network
@@ -17,18 +16,10 @@ spec:
             autoCreateSubnetworks: false
             routingConfig:
               routingMode: REGIONAL
-          providerRef:
-            name: gcp-provider
-          reclaimPolicy: Delete
-      patches:
-        - fromFieldPath: "metadata.name"
-          toFieldPath: "metadata.name"
     - base:
         apiVersion: container.gcp.crossplane.io/v1beta1
         kind: GKECluster
         spec:
-          providerRef:
-            name: gcp-provider
           forProvider:
             addonsConfig:
               kubernetesDashboard:
@@ -55,20 +46,15 @@ spec:
               username: admin
             loggingService: logging.googleapis.com/kubernetes
             monitoringService: monitoring.googleapis.com/kubernetes
-            networkRef:
-              name: gcp-wordpressclusters-network
+            networkSelector:
+              matchControllerRef: true
             location: us-central1
             locations:
               - us-central1-a
               - us-central1-f
           writeConnectionSecretToRef:
             namespace: crossplane-system
-          reclaimPolicy: Delete
       patches:
-        - fromFieldPath: "metadata.name"
-          toFieldPath: "spec.forProvider.networkRef.name"
-        - fromFieldPath: "metadata.name"
-          toFieldPath: "metadata.name"
         - fromFieldPath: "metadata.name"
           toFieldPath: "metadata.annotations[crossplane.io/external-name]"
           transforms:
@@ -79,8 +65,12 @@ spec:
           toFieldPath: "metadata.labels"
         - fromFieldPath: "metadata.annotations[crossplane.io/external-name]"
           toFieldPath: "metadata.annotations[crossplane.io/external-name]"
-        - fromFieldPath: "metadata.name"
+        - fromFieldPath: "metadata.uid"
           toFieldPath: "spec.writeConnectionSecretToRef.name"
+          transforms:
+            - type: string
+              string:
+                fmt: "%s-gkecluster"
         - fromFieldPath: "spec.clusterVersion"
           toFieldPath: "spec.forProvider.initialClusterVersion"
       connectionDetails:
@@ -89,9 +79,6 @@ spec:
         apiVersion: container.gcp.crossplane.io/v1alpha1
         kind: NodePool
         spec:
-          providerRef:
-            name: gcp-provider
-          reclaimPolicy: Delete
           forProvider:
             initialNodeCount: 1
             locations:
@@ -147,9 +134,6 @@ spec:
                   - value: "0.0.0.0/0"
           writeConnectionSecretToRef:
             namespace: crossplane-system
-          providerRef:
-            name: gcp-provider
-          reclaimPolicy: Delete
       patches:
         - fromFieldPath: "metadata.uid"
           toFieldPath: "spec.writeConnectionSecretToRef.name"
@@ -164,14 +148,21 @@ spec:
         kind: ProviderConfig
         spec:
           credentialsSecretRef:
+            namespace: crossplane-system
             key: kubeconfig
       patches:
         - fromFieldPath: metadata.name
           toFieldPath: metadata.name
-        - fromFieldPath: spec.writeConnectionSecretToRef.namespace
-          toFieldPath: spec.credentialsSecretRef.namespace
-        - fromFieldPath: spec.writeConnectionSecretToRef.name
+        # This ProviderConfig uses the above GKECluster's connection secret as
+        # its credentials secret.
+        - fromFieldPath: "metadata.uid"
           toFieldPath: spec.credentialsSecretRef.name
+          transforms:
+            - type: string
+              string:
+                fmt: "%s-gkecluster"
+      readinessChecks:
+        - type: None
     - base:
         apiVersion: helm.crossplane.io/v1alpha1
         kind: Release
@@ -186,6 +177,8 @@ spec:
               version: 9.5.1
             namespace: wordpress
             values:
+              persistence:
+                enabled: false
               mariadb:
                 enabled: false
               externalDatabase:
diff --git a/examples/in-composition/definition.yaml b/examples/in-composition/definition.yaml
index 61cbaf7..4d31e83 100644
--- a/examples/in-composition/definition.yaml
+++ b/examples/in-composition/definition.yaml
@@ -3,12 +3,10 @@ kind: CompositeResourceDefinition
 metadata:
   name: wordpressclusters.example.crossplane.io
 spec:
-  # The set of connection secret keys that this composite resource exposes. Each
-  # key must be provided by the connectionDetails of a composed resource.
+  defaultComposition:
+   name: wordpressclusters.example.crossplane.io
   connectionSecretKeys:
     - kubeconfig
-  # The schema of this composite resource, in the form of a partial template for
-  # a Kubernetes CustomResourceDefinition.
   crdSpecTemplate:
     group: example.crossplane.io
     version: v1alpha1
diff --git a/go.mod b/go.mod
index 9dd3b95..8a56efb 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,13 @@ module github.com/crossplane-contrib/provider-helm
 go 1.13
 
 require (
-	github.com/crossplane/crossplane v0.11.1
-	github.com/crossplane/crossplane-runtime v0.9.1-0.20200827134951-29150890d4c1
-	github.com/crossplane/crossplane-tools v0.0.0-20200827141855-f51a6598f2bc
+	github.com/crossplane/crossplane-runtime v0.9.1-0.20201001234308-3f1afd106a8c
+	github.com/crossplane/crossplane-tools v0.0.0-20201001224552-fb258cc0eb30
+	github.com/go-logr/zapr v0.1.1 // indirect
+	github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
 	github.com/google/go-cmp v0.4.0
 	github.com/pkg/errors v0.9.1
+	github.com/prometheus/client_golang v1.1.0 // indirect
 	gopkg.in/alecthomas/kingpin.v2 v2.2.6
 	helm.sh/helm/v3 v3.2.4
 	k8s.io/api v0.18.8
diff --git a/go.sum b/go.sum
index 5b2a663..2aca1f1 100644
--- a/go.sum
+++ b/go.sum
@@ -13,14 +13,10 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
-github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
 github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
-github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
 github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
-github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
 github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
 github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
-github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
 github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@@ -136,17 +132,10 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
-github.com/crossplane/crossplane v0.11.1 h1:LZQbCxSCWODBL1o1gxmfzwibbkGiyOJrkZcepRhQOrk=
-github.com/crossplane/crossplane v0.11.1/go.mod h1:+AH83k737iABb8jUKyo8zML73JZZioeybuShGyAtbkg=
-github.com/crossplane/crossplane-runtime v0.7.1-0.20200421211018-be37c50cc2ab/go.mod h1:e12p4X6dbtqd9GOnph78Epd6aezyvmcMM1+6aQy3VzY=
-github.com/crossplane/crossplane-runtime v0.9.0/go.mod h1:gNY/21MLBaz5KNP7hmfXbBXp8reYRbwY5B/97Kp4tgM=
-github.com/crossplane/crossplane-runtime v0.9.1-0.20200827134951-29150890d4c1 h1:WLHvlKo69ii3ezcCEXK0XVHZRoBMQex2ylQEcCUmxO4=
-github.com/crossplane/crossplane-runtime v0.9.1-0.20200827134951-29150890d4c1/go.mod h1:gNY/21MLBaz5KNP7hmfXbBXp8reYRbwY5B/97Kp4tgM=
-github.com/crossplane/crossplane-tools v0.0.0-20200219001116-bb8b2ce46330/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
-github.com/crossplane/crossplane-tools v0.0.0-20200412230150-efd0edd4565b/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
-github.com/crossplane/crossplane-tools v0.0.0-20200827141855-f51a6598f2bc h1:wRQ9G2HVA51+JuTYP6t0vmYhOGIbpxeETBU1/iDkxgQ=
-github.com/crossplane/crossplane-tools v0.0.0-20200827141855-f51a6598f2bc/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
-github.com/crossplane/oam-kubernetes-runtime v0.0.0-20200426101222-2b61763c2e51/go.mod h1:tY+QSJ5ebP9jKh3CW4UsyeqQflnXEtEkiLYjxqVBJtI=
+github.com/crossplane/crossplane-runtime v0.9.1-0.20201001234308-3f1afd106a8c h1:jPqFF7MSj5ENEkY6PA2glEJsK5c80Koky5Hz6W04VAY=
+github.com/crossplane/crossplane-runtime v0.9.1-0.20201001234308-3f1afd106a8c/go.mod h1:cJl5ZZONisre4v6wTmbrC8Jh3AI+erq/lNaxZzv9tnU=
+github.com/crossplane/crossplane-tools v0.0.0-20201001224552-fb258cc0eb30 h1:NoiOGZCsnrpY4U6sqfiz0RQhi+ohWxFu58rQGoX7MVE=
+github.com/crossplane/crossplane-tools v0.0.0-20201001224552-fb258cc0eb30/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
 github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
 github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
 github.com/dave/jennifer v1.3.0 h1:p3tl41zjjCZTNBytMwrUuiAnherNUZktlhPTKoF/sEk=
@@ -323,7 +312,6 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2V
 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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -331,7 +319,6 @@ 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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 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/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -388,7 +375,6 @@ github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
 github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
-github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
@@ -422,7 +408,6 @@ github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PF
 github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -430,8 +415,6 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
 github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.8/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=
@@ -552,7 +535,6 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
 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/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -560,7 +542,6 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 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.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -753,7 +734,6 @@ 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/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-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -787,7 +767,6 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/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=
@@ -814,7 +793,6 @@ 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-20190923162816-aa69164e4478/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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -830,7 +808,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 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-20180823144017-11551d06cbcc/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=
@@ -867,7 +844,6 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20u
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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/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=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
@@ -979,7 +955,6 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1001,7 +976,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
 k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
-k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
 k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
 k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
 k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
@@ -1014,7 +988,6 @@ k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrq
 k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M=
 k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
 k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
-k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
 k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
 k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
 k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
@@ -1028,7 +1001,6 @@ k8s.io/cli-runtime v0.18.0 h1:jG8XpSqQ5TrV0N+EZ3PFz6+gqlCk71dkggWCCq9Mq34=
 k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ=
 k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
 k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
-k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ=
 k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
 k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
 k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
@@ -1079,8 +1051,6 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM=
 rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
-sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
-sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
 sigs.k8s.io/controller-runtime v0.6.2 h1:jkAnfdTYBpFwlmBn3pS5HFO06SfxvnTZ1p5PeEF/zAA=
 sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E=
 sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
@@ -1098,7 +1068,6 @@ sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:I
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/pkg/controller/config/config.go b/pkg/controller/config/config.go
new file mode 100644
index 0000000..735b494
--- /dev/null
+++ b/pkg/controller/config/config.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2020 The Crossplane Authors.
+
+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 config
+
+import (
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/source"
+
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/reconciler/providerconfig"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+
+	"github.com/crossplane-contrib/provider-helm/apis/v1alpha1"
+)
+
+// Setup adds a controller that reconciles ProviderConfigs by accounting for
+// their current usage.
+func Setup(mgr ctrl.Manager, l logging.Logger) error {
+	name := providerconfig.ControllerName(v1alpha1.ProviderConfigGroupKind)
+
+	of := resource.ProviderConfigKinds{
+		Config:    v1alpha1.ProviderConfigGroupVersionKind,
+		UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind,
+	}
+
+	return ctrl.NewControllerManagedBy(mgr).
+		Named(name).
+		For(&v1alpha1.ProviderConfig{}).
+		Watches(&source.Kind{Type: &v1alpha1.ProviderConfigUsage{}}, &resource.EnqueueRequestForProviderConfig{}).
+		Complete(providerconfig.NewReconciler(mgr, of,
+			providerconfig.WithLogger(l.WithValues("controller", name)),
+			providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name)))))
+}
diff --git a/pkg/controller/helm.go b/pkg/controller/helm.go
index 0f0a30c..c3bd628 100644
--- a/pkg/controller/helm.go
+++ b/pkg/controller/helm.go
@@ -19,14 +19,18 @@ package controller
 import (
 	ctrl "sigs.k8s.io/controller-runtime"
 
+	"github.com/crossplane-contrib/provider-helm/pkg/controller/config"
+	"github.com/crossplane-contrib/provider-helm/pkg/controller/release"
+
 	"github.com/crossplane/crossplane-runtime/pkg/logging"
 )
 
-// Setup creates all Template controllers with the supplied logger and adds them to
-// the supplied manager.
+// Setup creates all Helm controllers with the supplied logger and adds them
+// to the supplied manager.
 func Setup(mgr ctrl.Manager, l logging.Logger) error {
 	for _, setup := range []func(ctrl.Manager, logging.Logger) error{
-		SetupRelease,
+		config.Setup,
+		release.Setup,
 	} {
 		if err := setup(mgr, l); err != nil {
 			return err
diff --git a/pkg/controller/chart.go b/pkg/controller/release/chart.go
similarity index 99%
rename from pkg/controller/chart.go
rename to pkg/controller/release/chart.go
index 3c31661..a2976f3 100644
--- a/pkg/controller/chart.go
+++ b/pkg/controller/release/chart.go
@@ -14,16 +14,17 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package release
 
 import (
 	"context"
 
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
 	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+
 	helmClient "github.com/crossplane-contrib/provider-helm/pkg/clients/helm"
 )
 
diff --git a/pkg/controller/data.go b/pkg/controller/release/data.go
similarity index 99%
rename from pkg/controller/data.go
rename to pkg/controller/release/data.go
index 5f61701..94cf146 100644
--- a/pkg/controller/data.go
+++ b/pkg/controller/release/data.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package release
 
 import (
 	"context"
diff --git a/pkg/controller/doc.go b/pkg/controller/release/doc.go
similarity index 96%
rename from pkg/controller/doc.go
rename to pkg/controller/release/doc.go
index 3682235..0c0253f 100644
--- a/pkg/controller/doc.go
+++ b/pkg/controller/release/doc.go
@@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package release
diff --git a/pkg/controller/observe.go b/pkg/controller/release/observe.go
similarity index 99%
rename from pkg/controller/observe.go
rename to pkg/controller/release/observe.go
index 258a964..d5848a9 100644
--- a/pkg/controller/observe.go
+++ b/pkg/controller/release/observe.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package release
 
 import (
 	"context"
diff --git a/pkg/controller/patch.go b/pkg/controller/release/patch.go
similarity index 99%
rename from pkg/controller/patch.go
rename to pkg/controller/release/patch.go
index 442b79c..ef49922 100644
--- a/pkg/controller/patch.go
+++ b/pkg/controller/release/patch.go
@@ -1,4 +1,4 @@
-package controller
+package release
 
 import (
 	"context"
diff --git a/pkg/controller/release.go b/pkg/controller/release/release.go
similarity index 90%
rename from pkg/controller/release.go
rename to pkg/controller/release/release.go
index 94ebb9e..41fe3c7 100644
--- a/pkg/controller/release.go
+++ b/pkg/controller/release/release.go
@@ -14,17 +14,11 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package release
 
 import (
 	"context"
 
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
-	"github.com/crossplane/crossplane-runtime/pkg/event"
-	"github.com/crossplane/crossplane-runtime/pkg/logging"
-	"github.com/crossplane/crossplane-runtime/pkg/meta"
-	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
-	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/pkg/errors"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/release"
@@ -35,6 +29,13 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	ktype "sigs.k8s.io/kustomize/api/types"
 
+	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/meta"
+	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+
 	"github.com/crossplane-contrib/provider-helm/apis/release/v1alpha1"
 	helmv1alpha1 "github.com/crossplane-contrib/provider-helm/apis/v1alpha1"
 	"github.com/crossplane-contrib/provider-helm/pkg/clients"
@@ -56,13 +57,14 @@ const (
 	errFailedToGetRepoCreds       = "failed to get user name and password from secret reference"
 	errFailedToComposeValues      = "failed to compose values"
 	errFailedToCreateRestConfig   = "cannot create new rest config using provider secret"
+	errFailedToTrackUsage         = "cannot track provider config usage"
 	errFailedToLoadPatches        = "failed to load patches"
 	errFailedToUpdatePatchSha     = "failed to update patch sha"
 	errFailedToSetVersion         = "failed to update chart spec with the latest version"
 )
 
-// SetupRelease adds a controller that reconciles Release managed resources.
-func SetupRelease(mgr ctrl.Manager, l logging.Logger) error {
+// Setup adds a controller that reconciles Release managed resources.
+func Setup(mgr ctrl.Manager, l logging.Logger) error {
 	name := managed.ControllerName(v1alpha1.ReleaseGroupKind)
 	logger := l.WithValues("controller", name)
 
@@ -71,11 +73,11 @@ func SetupRelease(mgr ctrl.Manager, l logging.Logger) error {
 		managed.WithExternalConnecter(&connector{
 			logger:          logger,
 			client:          mgr.GetClient(),
+			usage:           resource.NewProviderConfigUsageTracker(mgr.GetClient(), &helmv1alpha1.ProviderConfigUsage{}),
 			newRestConfigFn: clients.NewRestConfig,
 			newKubeClientFn: clients.NewKubeClient,
 			newHelmClientFn: helmClient.NewClient,
 		}),
-		managed.WithInitializers(managed.NewNameAsExternalName(mgr.GetClient())),
 		managed.WithLogger(logger),
 		managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))
 
@@ -88,6 +90,7 @@ func SetupRelease(mgr ctrl.Manager, l logging.Logger) error {
 type connector struct {
 	logger          logging.Logger
 	client          client.Client
+	usage           resource.Tracker
 	newRestConfigFn func(creds map[string][]byte) (*rest.Config, error)
 	newKubeClientFn func(config *rest.Config) (client.Client, error)
 	newHelmClientFn func(log logging.Logger, config *rest.Config, namespace string) (helmClient.Client, error)
@@ -108,6 +111,10 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
 		return nil, errors.New(errProviderConfigNotSet)
 	}
 
+	if err := c.usage.Track(ctx, cr); err != nil {
+		return nil, errors.Wrap(err, errFailedToTrackUsage)
+	}
+
 	n := types.NamespacedName{Name: cr.GetProviderConfigReference().Name}
 	if err := c.client.Get(ctx, n, p); err != nil {
 		return nil, errors.Wrap(err, errProviderNotRetrieved)
@@ -125,20 +132,15 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
 		return nil, errors.Wrap(err, errFailedToCreateRestConfig)
 	}
 
-	// TODO(hasan): Remove below HACK, once https://github.com/crossplane/crossplane/issues/1687 resolved
-	// HACK
-	p.Status.SetConditions(runtimev1alpha1.Available())
-	if err := c.client.Status().Update(ctx, p); err != nil {
-		return nil, errors.Wrap(err, "Failed to update ProviderConfig status")
-	}
-	// END OF HACK
-
 	k, err := c.newKubeClientFn(rc)
 	if err != nil {
 		return nil, errors.Wrap(err, errNewKubernetesClient)
 	}
 
 	h, err := c.newHelmClientFn(c.logger, rc, cr.Spec.ForProvider.Namespace)
+	if err != nil {
+		return nil, errors.Wrap(err, errNewKubernetesClient)
+	}
 
 	return &helmExternal{
 		logger:    l,
@@ -146,7 +148,7 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
 		kube:      k,
 		helm:      h,
 		patch:     newPatcher(),
-	}, errors.Wrap(err, errNewKubernetesClient)
+	}, nil
 }
 
 type helmExternal struct {
@@ -181,6 +183,16 @@ func (e *helmExternal) Observe(ctx context.Context, mg resource.Managed) (manage
 	}
 
 	cr.Status.AtProvider = generateObservation(rel)
+
+	// Determining whether the release is up to date may involve reading values
+	// from secrets, configmaps, etc. This will fail if said dependencies have
+	// been deleted. We don't need to determine whether we're up to date in
+	// order to delete the release, so if we know we're about to be deleted we
+	// return early to avoid blocking unnecessarily on missing dependencies.
+	if meta.WasDeleted(cr) {
+		return managed.ExternalObservation{ResourceExists: true}, nil
+	}
+
 	s, err := isUpToDate(ctx, e.localKube, &cr.Spec.ForProvider, rel, cr.Status)
 	if err != nil {
 		return managed.ExternalObservation{}, errors.Wrap(err, errFailedToCheckIfUpToDate)
diff --git a/pkg/controller/values.go b/pkg/controller/release/values.go
similarity index 99%
rename from pkg/controller/values.go
rename to pkg/controller/release/values.go
index 5ffa196..88114c0 100644
--- a/pkg/controller/values.go
+++ b/pkg/controller/release/values.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package release
 
 import (
 	"context"


Test patch:
diff --git a/pkg/controller/chart_test.go b/pkg/controller/release/chart_test.go
similarity index 99%
rename from pkg/controller/chart_test.go
rename to pkg/controller/release/chart_test.go
index 183ac81..7ecf72d 100644
--- a/pkg/controller/chart_test.go
+++ b/pkg/controller/release/chart_test.go
@@ -1,12 +1,10 @@
-package controller
+package release
 
 import (
 	"context"
 	"fmt"
 	"testing"
 
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
-	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
@@ -15,6 +13,9 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
 	"github.com/crossplane-contrib/provider-helm/pkg/clients/helm"
 )
 
diff --git a/pkg/controller/data_test.go b/pkg/controller/release/data_test.go
similarity index 99%
rename from pkg/controller/data_test.go
rename to pkg/controller/release/data_test.go
index c84e9c4..1657756 100644
--- a/pkg/controller/data_test.go
+++ b/pkg/controller/release/data_test.go
@@ -1,11 +1,10 @@
-package controller
+package release
 
 import (
 	"context"
 	"fmt"
 	"testing"
 
-	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
@@ -15,6 +14,8 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
 	"github.com/crossplane-contrib/provider-helm/apis/release/v1alpha1"
 )
 
diff --git a/pkg/controller/observe_test.go b/pkg/controller/release/observe_test.go
similarity index 99%
rename from pkg/controller/observe_test.go
rename to pkg/controller/release/observe_test.go
index 60f741a..8807633 100644
--- a/pkg/controller/observe_test.go
+++ b/pkg/controller/release/observe_test.go
@@ -1,4 +1,4 @@
-package controller
+package release
 
 import (
 	"context"
@@ -6,13 +6,14 @@ import (
 
 	"k8s.io/apimachinery/pkg/runtime"
 
-	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	"github.com/pkg/errors"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/release"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
 	"github.com/crossplane-contrib/provider-helm/apis/release/v1alpha1"
 )
 
diff --git a/pkg/controller/patch_test.go b/pkg/controller/release/patch_test.go
similarity index 99%
rename from pkg/controller/patch_test.go
rename to pkg/controller/release/patch_test.go
index 98eba73..c22752a 100644
--- a/pkg/controller/patch_test.go
+++ b/pkg/controller/release/patch_test.go
@@ -1,11 +1,10 @@
-package controller
+package release
 
 import (
 	"context"
 	"fmt"
 	"testing"
 
-	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -15,6 +14,8 @@ import (
 	"sigs.k8s.io/kustomize/api/resid"
 	"sigs.k8s.io/kustomize/api/types"
 
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
 	"github.com/crossplane-contrib/provider-helm/apis/release/v1alpha1"
 )
 
diff --git a/pkg/controller/release_test.go b/pkg/controller/release/release_test.go
similarity index 92%
rename from pkg/controller/release_test.go
rename to pkg/controller/release/release_test.go
index ea449e3..6abc31e 100644
--- a/pkg/controller/release_test.go
+++ b/pkg/controller/release/release_test.go
@@ -1,15 +1,10 @@
-package controller
+package release
 
 import (
 	"context"
 	"fmt"
 	"testing"
 
-	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
-	"github.com/crossplane/crossplane-runtime/pkg/logging"
-	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
-	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	"github.com/pkg/errors"
 	"helm.sh/helm/v3/pkg/chart"
@@ -22,6 +17,12 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/kustomize/api/types"
 
+	runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
 	"github.com/crossplane-contrib/provider-helm/apis/release/v1alpha1"
 	helmv1alpha1 "github.com/crossplane-contrib/provider-helm/apis/v1alpha1"
 	helmClient "github.com/crossplane-contrib/provider-helm/pkg/clients/helm"
@@ -142,6 +143,7 @@ func Test_connector_Connect(t *testing.T) {
 		newRestConfigFn func(creds map[string][]byte) (*rest.Config, error)
 		newKubeClientFn func(config *rest.Config) (client.Client, error)
 		newHelmClientFn func(log logging.Logger, config *rest.Config, namespace string) (helmClient.Client, error)
+		usage           resource.Tracker
 		mg              resource.Managed
 	}
 	type want struct {
@@ -159,6 +161,15 @@ func Test_connector_Connect(t *testing.T) {
 				err: errors.New(errNotRelease),
 			},
 		},
+		"FailedToTrackUsage": {
+			args: args{
+				usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }),
+				mg:    helmRelease(),
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errFailedToTrackUsage),
+			},
+		},
 		"FailedToGetProvider": {
 			args: args{
 				client: &test.MockClient{
@@ -170,7 +181,8 @@ func Test_connector_Connect(t *testing.T) {
 						return nil
 					},
 				},
-				mg: helmRelease(),
+				usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }),
+				mg:    helmRelease(),
 			},
 			want: want{
 				err: errors.Wrap(errBoom, errProviderNotRetrieved),
@@ -190,7 +202,8 @@ func Test_connector_Connect(t *testing.T) {
 						return errBoom
 					},
 				},
-				mg: helmRelease(),
+				usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }),
+				mg:    helmRelease(),
 			},
 			want: want{
 				err: errors.Wrap(errors.Wrap(errBoom, fmt.Sprintf(errFailedToGetSecret, providerSecretNamespace)), errProviderSecretNotRetrieved),
@@ -214,7 +227,8 @@ func Test_connector_Connect(t *testing.T) {
 				newRestConfigFn: func(creds map[string][]byte) (config *rest.Config, err error) {
 					return nil, errBoom
 				},
-				mg: helmRelease(),
+				usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }),
+				mg:    helmRelease(),
 			},
 			want: want{
 				err: errors.Wrap(errBoom, errFailedToCreateRestConfig),
@@ -244,7 +258,8 @@ func Test_connector_Connect(t *testing.T) {
 				newKubeClientFn: func(config *rest.Config) (c client.Client, err error) {
 					return nil, errBoom
 				},
-				mg: helmRelease(),
+				usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }),
+				mg:    helmRelease(),
 			},
 			want: want{
 				err: errors.Wrap(errBoom, errNewKubernetesClient),
@@ -254,15 +269,15 @@ func Test_connector_Connect(t *testing.T) {
 			args: args{
 				client: &test.MockClient{
 					MockGet: func(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
-						if key.Name == providerName {
-							*obj.(*helmv1alpha1.ProviderConfig) = providerConfig
-							return nil
-						}
-						if key.Name == providerSecretName && key.Namespace == providerSecretNamespace {
-							*obj.(*corev1.Secret) = secret
-							return nil
+						switch t := obj.(type) {
+						case *helmv1alpha1.ProviderConfig:
+							*t = providerConfig
+						case *corev1.Secret:
+							*t = secret
+						default:
+							return errBoom
 						}
-						return errBoom
+						return nil
 					},
 					MockStatusUpdate: func(ctx context.Context, obj runtime.Object, opts ...client.UpdateOption) error {
 						return nil
@@ -277,7 +292,8 @@ func Test_connector_Connect(t *testing.T) {
 				newHelmClientFn: func(log logging.Logger, config *rest.Config, namespace string) (h helmClient.Client, err error) {
 					return &MockHelmClient{}, nil
 				},
-				mg: helmRelease(),
+				usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }),
+				mg:    helmRelease(),
 			},
 			want: want{
 				err: nil,
@@ -292,10 +308,11 @@ func Test_connector_Connect(t *testing.T) {
 				newRestConfigFn: tc.args.newRestConfigFn,
 				newKubeClientFn: tc.args.newKubeClientFn,
 				newHelmClientFn: tc.args.newHelmClientFn,
+				usage:           tc.usage,
 			}
 			_, gotErr := c.Connect(context.Background(), tc.args.mg)
 			if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" {
-				t.Fatalf("#TODO(...): -want error, +got error: %s", diff)
+				t.Fatalf("Connect(...): -want error, +got error: %s", diff)
 			}
 		})
 	}
@@ -370,6 +387,26 @@ func Test_helmExternal_Observe(t *testing.T) {
 				err: errors.New(errLastReleaseIsNil),
 			},
 		},
+		"ReleaseIsBeingDeleted": {
+			args: args{
+				localKube: nil,
+				kube:      nil,
+				helm: &MockHelmClient{
+					MockGetLastRelease: func(r string) (hr *release.Release, err error) {
+						return &release.Release{}, nil
+					},
+				},
+				mg: helmRelease(
+					func(release *v1alpha1.Release) {
+						now := metav1.Now()
+						release.SetDeletionTimestamp(&now)
+					},
+				),
+			},
+			want: want{
+				out: managed.ExternalObservation{ResourceExists: true},
+			},
+		},
 		"FailedToCheckIsUpToDate": {
 			args: args{
 				localKube: nil,
diff --git a/pkg/controller/values_test.go b/pkg/controller/release/values_test.go
similarity index 99%
rename from pkg/controller/values_test.go
rename to pkg/controller/release/values_test.go
index 6ce3813..8022803 100644
--- a/pkg/controller/values_test.go
+++ b/pkg/controller/release/values_test.go
@@ -1,4 +1,4 @@
-package controller
+package release
 
 import (
 	"context"
@@ -9,11 +9,12 @@ import (
 
 	"k8s.io/apimachinery/pkg/runtime"
 
-	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	"github.com/pkg/errors"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
 	"github.com/crossplane-contrib/provider-helm/apis/release/v1alpha1"
 )