Repository: kubernetes-sigs/cluster-api
Pull request #3605 (2020-09-08 10:31:34)

📖 update KCP proposal with latest changes
**What this PR does / why we need it**:
This PR updates the KCP proposal document with latest changes, so then it will be possible to extend this proposal with the considerations for implementing automatic remediating of unhealthy control plane machines

/assign @vincepri 
/assign @sedefsavas 

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

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **fabriziopandini**
You can assign the PR to them by writing `/assign @fabriziopandini` in a comment when ready.

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

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api/blob/master/OWNERS)**

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 7dc612ab12570f7e9453739eba72d58edafe36a4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3605/pull-cluster-api-test/1303279908776054788) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3605). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3fabriziopandini). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

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

Patch:
diff --git a/Dockerfile b/Dockerfile
index cb2078294..327841bb7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Build the manager binary
-FROM golang:1.13.15 as builder
+FROM golang:1.13.14 as builder
 WORKDIR /workspace
 
 # Run this with docker build --build_arg goproxy=$(go env GOPROXY) to override the goproxy
diff --git a/Makefile b/Makefile
index d573a9523..a16ceef5d 100644
--- a/Makefile
+++ b/Makefile
@@ -487,14 +487,14 @@ release-binary: $(RELEASE_DIR)
 		-e GOARCH=$(GOARCH) \
 		-v "$$(pwd):/workspace$(DOCKER_VOL_OPTS)" \
 		-w /workspace \
-		golang:1.13.15 \
+		golang:1.13.14 \
 		go build -a -ldflags "$(LDFLAGS) -extldflags '-static'" \
 		-o $(RELEASE_DIR)/$(notdir $(RELEASE_BINARY))-$(GOOS)-$(GOARCH) $(RELEASE_BINARY)
 
 .PHONY: release-staging
 release-staging: ## Builds and push container images to the staging bucket.
 	docker pull docker.io/docker/dockerfile:experimental
-	docker pull docker.io/library/golang:1.13.15
+	docker pull docker.io/library/golang:1.13.14
 	docker pull gcr.io/distroless/static:latest
 	REGISTRY=$(STAGING_REGISTRY) $(MAKE) docker-build-all docker-push-all release-alias-tag
 
diff --git a/Tiltfile b/Tiltfile
index 0a589e4cc..fde926dc1 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -117,15 +117,11 @@ def load_provider_tiltfiles():
                 provider_config["context"] = repo + "/" + provider_config["context"]
             else:
                 provider_config["context"] = repo
-            if "kustomize_config" not in provider_config:
-                provider_config["kustomize_config"] = True
-            if "go_main" not in provider_config:
-                provider_config["go_main"] = "main.go"
             providers[provider_name] = provider_config
 
 tilt_helper_dockerfile_header = """
 # Tilt image
-FROM golang:1.13.15 as tilt-helper
+FROM golang:1.13.14 as tilt-helper
 # Support live reloading with Tilt
 RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh  && \
     wget --output-document /start.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/start.sh && \
@@ -176,7 +172,6 @@ def enable_provider(name):
     p = providers.get(name)
 
     context = p.get("context")
-    go_main = p.get("go_main", "main.go")
 
     # Prefix each live reload dependency with context. For example, for if the context is
     # test/infra/docker and main.go is listed as a dep, the result is test/infra/docker/main.go. This adjustment is
@@ -186,10 +181,10 @@ def enable_provider(name):
         live_reload_deps.append(context + "/" + d)
 
     # Set up a local_resource build of the provider's manager binary. The provider is expected to have a main.go in
-    # manager_build_path or the main.go must be provided via go_main option. The binary is written to .tiltbuild/manager.
+    # manager_build_path. The binary is written to .tiltbuild/manager.
     local_resource(
         name + "_manager",
-        cmd = "cd " + context + ';mkdir -p .tiltbuild;CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \'-extldflags "-static"\' -o .tiltbuild/manager ' + go_main,
+        cmd = "cd " + context + ';mkdir -p .tiltbuild;CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \'-extldflags "-static"\' -o .tiltbuild/manager',
         deps = live_reload_deps,
     )
 
@@ -223,16 +218,15 @@ def enable_provider(name):
         ],
     )
 
-    if p.get("kustomize_config", True):
-        # Copy all the substitutions from the user's tilt-settings.json into the environment. Otherwise, the substitutions
-        # are not available and their placeholders will be replaced with the empty string when we call kustomize +
-        # envsubst below.
-        substitutions = settings.get("kustomize_substitutions", {})
-        os.environ.update(substitutions)
+    # Copy all the substitutions from the user's tilt-settings.json into the environment. Otherwise, the substitutions
+    # are not available and their placeholders will be replaced with the empty string when we call kustomize +
+    # envsubst below.
+    substitutions = settings.get("kustomize_substitutions", {})
+    os.environ.update(substitutions)
 
-        # Apply the kustomized yaml for this provider
-        yaml = str(kustomize_with_envsubst(context + "/config"))
-        k8s_yaml(blob(yaml))
+    # Apply the kustomized yaml for this provider
+    yaml = str(kustomize_with_envsubst(context + "/config"))
+    k8s_yaml(blob(yaml))
 
 # Prepull all the cert-manager images to your local environment and then load them directly into kind. This speeds up
 # setup if you're repeatedly destroying and recreating your kind cluster, as it doesn't have to pull the images over
diff --git a/cmd/clusterctl/client/client.go b/cmd/clusterctl/client/client.go
index 496ef1c21..5929ef772 100644
--- a/cmd/clusterctl/client/client.go
+++ b/cmd/clusterctl/client/client.go
@@ -87,16 +87,16 @@ type clusterctlClient struct {
 // RepositoryClientFactoryInput represents the inputs required by the
 // RepositoryClientFactory
 type RepositoryClientFactoryInput struct {
-	Provider  Provider
-	Processor Processor
+	provider  Provider
+	processor Processor
 }
 type RepositoryClientFactory func(RepositoryClientFactoryInput) (repository.Client, error)
 
 // ClusterClientFactoryInput reporesents the inputs required by the
 // ClusterClientFactory
 type ClusterClientFactoryInput struct {
-	Kubeconfig Kubeconfig
-	Processor  Processor
+	kubeconfig Kubeconfig
+	processor  Processor
 }
 type ClusterClientFactory func(ClusterClientFactoryInput) (cluster.Client, error)
 
@@ -167,9 +167,9 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
 func defaultRepositoryFactory(configClient config.Client) RepositoryClientFactory {
 	return func(input RepositoryClientFactoryInput) (repository.Client, error) {
 		return repository.New(
-			input.Provider,
+			input.provider,
 			configClient,
-			repository.InjectYamlProcessor(input.Processor),
+			repository.InjectYamlProcessor(input.processor),
 		)
 	}
 }
@@ -179,9 +179,9 @@ func defaultClusterFactory(configClient config.Client) ClusterClientFactory {
 	return func(input ClusterClientFactoryInput) (cluster.Client, error) {
 		return cluster.New(
 			// Kubeconfig is a type alias to cluster.Kubeconfig
-			cluster.Kubeconfig(input.Kubeconfig),
+			cluster.Kubeconfig(input.kubeconfig),
 			configClient,
-			cluster.InjectYamlProcessor(input.Processor),
+			cluster.InjectYamlProcessor(input.processor),
 		), nil
 	}
 }
diff --git a/cmd/clusterctl/client/cluster/objectgraph.go b/cmd/clusterctl/client/cluster/objectgraph.go
index 8ab8a608e..20e5490b2 100644
--- a/cmd/clusterctl/client/cluster/objectgraph.go
+++ b/cmd/clusterctl/client/cluster/objectgraph.go
@@ -412,7 +412,6 @@ func (o *objectGraph) getMachines() []*node {
 
 // setSoftOwnership searches for soft ownership relations such as secrets linked to the cluster by a naming convention (without any explicit OwnerReference).
 func (o *objectGraph) setSoftOwnership() {
-	log := logf.Log
 	clusters := o.getClusters()
 	for _, secret := range o.getSecrets() {
 		// If the secret has at least one OwnerReference ignore it.
@@ -424,7 +423,6 @@ func (o *objectGraph) setSoftOwnership() {
 		// If the secret name is not a valid cluster secret name, ignore it.
 		secretClusterName, _, err := secretutil.ParseSecretName(secret.identity.Name)
 		if err != nil {
-			log.V(5).Info("Excluding secret from move (not linked with any Cluster)", "name", secret.identity.Name)
 			continue
 		}
 
diff --git a/cmd/clusterctl/client/common.go b/cmd/clusterctl/client/common.go
index e82516f5d..8ef02287f 100644
--- a/cmd/clusterctl/client/common.go
+++ b/cmd/clusterctl/client/common.go
@@ -47,7 +47,7 @@ func (c *clusterctlClient) getComponentsByName(provider string, providerType clu
 	// and watching namespace etc.
 	// Currently we are not supporting custom yaml processors for the provider
 	// components. So we revert to using the default SimpleYamlProcessor.
-	repositoryClientFactory, err := c.repositoryClientFactory(RepositoryClientFactoryInput{Provider: providerConfig})
+	repositoryClientFactory, err := c.repositoryClientFactory(RepositoryClientFactoryInput{provider: providerConfig})
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/clusterctl/client/config.go b/cmd/clusterctl/client/config.go
index eb87d20aa..caec51e62 100644
--- a/cmd/clusterctl/client/config.go
+++ b/cmd/clusterctl/client/config.go
@@ -102,7 +102,7 @@ func (c *clusterctlClient) ProcessYAML(options ProcessYAMLOptions) (YamlPrinter,
 	cluster, err := c.clusterClientFactory(
 		ClusterClientFactoryInput{
 			// use the default kubeconfig
-			Kubeconfig: Kubeconfig{},
+			kubeconfig: Kubeconfig{},
 		},
 	)
 	if err != nil {
@@ -273,7 +273,7 @@ func (c *clusterctlClient) getTemplateFromRepository(cluster cluster.Client, opt
 	if provider == "" {
 		// ensure the custom resource definitions required by clusterctl are in place
 		if err := cluster.ProviderInventory().EnsureCustomResourceDefinitions(); err != nil {
-			return nil, errors.Wrapf(err, "provider custom resource definitions (CRDs) are not installed")
+			return nil, errors.Wrapf(err, "failed to identify the default infrastructure provider. Please specify an infrastructure provider")
 		}
 		ensureCustomResourceDefinitions = true
 
@@ -320,7 +320,7 @@ func (c *clusterctlClient) getTemplateFromRepository(cluster cluster.Client, opt
 		return nil, err
 	}
 
-	repo, err := c.repositoryClientFactory(RepositoryClientFactoryInput{Provider: providerConfig, Processor: processor})
+	repo, err := c.repositoryClientFactory(RepositoryClientFactoryInput{provider: providerConfig, processor: processor})
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/clusterctl/client/config/providers_client.go b/cmd/clusterctl/client/config/providers_client.go
index 0d40010cc..3ea91ba04 100644
--- a/cmd/clusterctl/client/config/providers_client.go
+++ b/cmd/clusterctl/client/config/providers_client.go
@@ -34,7 +34,6 @@ const (
 	AWSProviderName       = "aws"
 	AzureProviderName     = "azure"
 	DockerProviderName    = "docker"
-	DOProviderName        = "do"
 	Metal3ProviderName    = "metal3"
 	OpenStackProviderName = "openstack"
 	PacketProviderName    = "packet"
@@ -110,11 +109,6 @@ func (p *providersClient) defaults() []Provider {
 			url:          "https://github.com/kubernetes-sigs/cluster-api/releases/latest/infrastructure-components-development.yaml",
 			providerType: clusterctlv1.InfrastructureProviderType,
 		},
-		&provider{
-			name:         DOProviderName,
-			url:          "https://github.com/kubernetes-sigs/cluster-api-provider-digitalocean/releases/latest/infrastructure-components.yaml",
-			providerType: clusterctlv1.InfrastructureProviderType,
-		},
 		&provider{
 			name:         PacketProviderName,
 			url:          "https://github.com/kubernetes-sigs/cluster-api-provider-packet/releases/latest/infrastructure-components.yaml",
diff --git a/cmd/clusterctl/client/config/reader_viper.go b/cmd/clusterctl/client/config/reader_viper.go
index 7490eac01..1104fc147 100644
--- a/cmd/clusterctl/client/config/reader_viper.go
+++ b/cmd/clusterctl/client/config/reader_viper.go
@@ -18,13 +18,9 @@ package config
 
 import (
 	"fmt"
-	"io"
-	"net/http"
-	"net/url"
 	"os"
 	"path/filepath"
 	"strings"
-	"time"
 
 	"github.com/pkg/errors"
 	"github.com/spf13/viper"
@@ -37,8 +33,6 @@ const (
 	ConfigFolder = ".cluster-api"
 	// ConfigName defines the name of the config file under ConfigFolder
 	ConfigName = "clusterctl"
-	// DownloadConfigFile is the config file when fetching the config from a remote location
-	DownloadConfigFile = "clusterctl-download.yaml"
 )
 
 // viperReader implements Reader using viper as backend for reading from environment variables
@@ -79,36 +73,13 @@ func (v *viperReader) Init(path string) error {
 	viper.AllowEmptyEnv(true)
 	viper.AutomaticEnv()
 
+	// Reads the clusterctl config file
 	if path != "" {
-		url, err := url.Parse(path)
-		if err != nil {
-			return errors.Wrap(err, "failed to url parse the config path")
-		}
-
-		switch {
-		case url.Scheme == "https" || url.Scheme == "http":
-			configPath := filepath.Join(homedir.HomeDir(), ConfigFolder)
-			if len(v.configPaths) > 0 {
-				configPath = filepath.Join(v.configPaths[0])
-			}
-			if err := os.MkdirAll(configPath, os.ModePerm); err != nil {
-				return err
-			}
-
-			downloadConfigFile := filepath.Join(configPath, DownloadConfigFile)
-			err = downloadFile(url.String(), downloadConfigFile)
-			if err != nil {
-				return err
-			}
-
-			viper.SetConfigFile(downloadConfigFile)
-		default:
-			if _, err := os.Stat(path); err != nil {
-				return errors.Wrap(err, "failed to check if clusterctl config file exists")
-			}
-			// Use path file from the flag.
-			viper.SetConfigFile(path)
+		if _, err := os.Stat(path); err != nil {
+			return err
 		}
+		// Use path file from the flag.
+		viper.SetConfigFile(path)
 	} else {
 		// Checks if there is a default .cluster-api/clusterctl{.extension} file in home directory
 		if !v.checkDefaultConfig() {
@@ -131,36 +102,6 @@ func (v *viperReader) Init(path string) error {
 	return nil
 }
 
-func downloadFile(url string, filepath string) error {
-	// Create the file
-	out, err := os.Create(filepath)
-	if err != nil {
-		return errors.Wrapf(err, "failed to create the clusterctl config file %s", filepath)
-	}
-	defer out.Close()
-
-	client := &http.Client{
-		Timeout: 30 * time.Second,
-	}
-	// Get the data
-	resp, err := client.Get(url)
-	if err != nil {
-		return errors.Wrapf(err, "failed to download the clusterctl config file from %s", url)
-	}
-	if resp.StatusCode != http.StatusOK {
-		return errors.New(fmt.Sprintf("failed to download the clusterctl config file from %s got %d", url, resp.StatusCode))
-	}
-	defer resp.Body.Close()
-
-	// Write the body to file
-	_, err = io.Copy(out, resp.Body)
-	if err != nil {
-		return errors.Wrap(err, "failed to save the data in the clusterctl config")
-	}
-
-	return nil
-}
-
 func (v *viperReader) Get(key string) (string, error) {
 	if viper.Get(key) == nil {
 		return "", errors.Errorf("Failed to get value for variable %q. Please set the variable value using os env variables or using the .clusterctl config file", key)
diff --git a/cmd/clusterctl/client/delete.go b/cmd/clusterctl/client/delete.go
index 40ddfdf0a..95115ba8c 100644
--- a/cmd/clusterctl/client/delete.go
+++ b/cmd/clusterctl/client/delete.go
@@ -61,7 +61,7 @@ type DeleteOptions struct {
 }
 
 func (c *clusterctlClient) Delete(options DeleteOptions) error {
-	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return err
 	}
@@ -81,15 +81,6 @@ func (c *clusterctlClient) Delete(options DeleteOptions) error {
 
 	if options.DeleteAll {
 		providersToDelete = installedProviders.Items
-		if options.Namespace != "" {
-			// Delete only the providers in the specified namespace
-			providersToDelete = []clusterctlv1.Provider{}
-			for _, provider := range installedProviders.Items {
-				if provider.Namespace == options.Namespace {
-					providersToDelete = append(providersToDelete, provider)
-				}
-			}
-		}
 	} else {
 		// Otherwise we are deleting only a subset of providers.
 		var providers []clusterctlv1.Provider
@@ -120,6 +111,21 @@ func (c *clusterctlClient) Delete(options DeleteOptions) error {
 				}
 			}
 
+			// Check the provider/type/namespace tuple actually matches one of the installed provider instances.
+			found := false
+			for _, ip := range installedProviders.Items {
+				if ip.InstanceName() == provider.InstanceName() {
+					found = true
+					providersToDelete = append(providersToDelete, ip)
+					break
+				}
+			}
+			if found {
+				break
+			}
+
+			// In case the provider does not match any installed providers, we still force deletion
+			// so the user can do 'delete' without removing CRD and after some time 'delete --delete-crd' (same for the namespace).
 			providersToDelete = append(providersToDelete, provider)
 		}
 	}
diff --git a/cmd/clusterctl/client/get_kubeconfig.go b/cmd/clusterctl/client/get_kubeconfig.go
index 6e06fdd8a..756a08ad7 100644
--- a/cmd/clusterctl/client/get_kubeconfig.go
+++ b/cmd/clusterctl/client/get_kubeconfig.go
@@ -33,7 +33,7 @@ type GetKubeconfigOptions struct {
 
 func (c *clusterctlClient) GetKubeconfig(options GetKubeconfigOptions) (string, error) {
 	// gets access to the management cluster
-	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return "", err
 	}
diff --git a/cmd/clusterctl/client/init.go b/cmd/clusterctl/client/init.go
index 8a1507453..df957472b 100644
--- a/cmd/clusterctl/client/init.go
+++ b/cmd/clusterctl/client/init.go
@@ -71,7 +71,7 @@ func (c *clusterctlClient) Init(options InitOptions) ([]Components, error) {
 	log := logf.Log
 
 	// gets access to the management cluster
-	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return nil, err
 	}
@@ -136,7 +136,7 @@ func (c *clusterctlClient) Init(options InitOptions) ([]Components, error) {
 // Init returns the list of images required for init.
 func (c *clusterctlClient) InitImages(options InitOptions) ([]string, error) {
 	// gets access to the management cluster
-	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/clusterctl/client/move.go b/cmd/clusterctl/client/move.go
index 63832c21e..d07c4ecfd 100644
--- a/cmd/clusterctl/client/move.go
+++ b/cmd/clusterctl/client/move.go
@@ -33,7 +33,7 @@ type MoveOptions struct {
 
 func (c *clusterctlClient) Move(options MoveOptions) error {
 	// Get the client for interacting with the source management cluster.
-	fromCluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.FromKubeconfig})
+	fromCluster, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.FromKubeconfig})
 	if err != nil {
 		return err
 	}
@@ -44,7 +44,7 @@ func (c *clusterctlClient) Move(options MoveOptions) error {
 	}
 
 	// Get the client for interacting with the target management cluster.
-	toCluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.ToKubeconfig})
+	toCluster, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.ToKubeconfig})
 	if err != nil {
 		return err
 	}
diff --git a/cmd/clusterctl/client/repository/metadata_client.go b/cmd/clusterctl/client/repository/metadata_client.go
index 8fe1c3aee..6646d154e 100644
--- a/cmd/clusterctl/client/repository/metadata_client.go
+++ b/cmd/clusterctl/client/repository/metadata_client.go
@@ -218,18 +218,6 @@ func (f *metadataClient) getEmbeddedMetadata() *clusterctlv1.Metadata {
 					// older version are not supported by clusterctl
 				},
 			}
-		case config.DOProviderName:
-			return &clusterctlv1.Metadata{
-				TypeMeta: metav1.TypeMeta{
-					APIVersion: clusterctlv1.GroupVersion.String(),
-					Kind:       "Metadata",
-				},
-				ReleaseSeries: []clusterctlv1.ReleaseSeries{
-					// v1alpha3 release series
-					{Major: 0, Minor: 3, Contract: "v1alpha3"},
-					// older version are not supported by clusterctl
-				},
-			}
 		case config.DockerProviderName:
 			// NB. The Docker provider is not designed for production use and is intended for development environments only.
 			return &clusterctlv1.Metadata{
diff --git a/cmd/clusterctl/client/upgrade.go b/cmd/clusterctl/client/upgrade.go
index 5d6bcf58f..31e6fc866 100644
--- a/cmd/clusterctl/client/upgrade.go
+++ b/cmd/clusterctl/client/upgrade.go
@@ -33,7 +33,7 @@ type PlanUpgradeOptions struct {
 
 func (c *clusterctlClient) PlanCertManagerUpgrade(options PlanUpgradeOptions) (CertManagerUpgradePlan, error) {
 	// Get the client for interacting with the management cluster.
-	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return CertManagerUpgradePlan{}, err
 	}
@@ -44,7 +44,7 @@ func (c *clusterctlClient) PlanCertManagerUpgrade(options PlanUpgradeOptions) (C
 
 func (c *clusterctlClient) PlanUpgrade(options PlanUpgradeOptions) ([]UpgradePlan, error) {
 	// Get the client for interacting with the management cluster.
-	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return nil, err
 	}
@@ -100,7 +100,7 @@ type ApplyUpgradeOptions struct {
 
 func (c *clusterctlClient) ApplyUpgrade(options ApplyUpgradeOptions) error {
 	// Get the client for interacting with the management cluster.
-	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
+	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{kubeconfig: options.Kubeconfig})
 	if err != nil {
 		return err
 	}
diff --git a/cmd/clusterctl/cmd/delete.go b/cmd/clusterctl/cmd/delete.go
index cfcbd1916..6918d0eba 100644
--- a/cmd/clusterctl/cmd/delete.go
+++ b/cmd/clusterctl/cmd/delete.go
@@ -68,18 +68,6 @@ var deleteCmd = &cobra.Command{
 		# Cluster API Providers are orphaned and there might be ongoing costs incurred as a result of this.
 		clusterctl delete --all
 
-		# Deletes all the providers in a namespace
-		# Important! As a consequence of this operation, all the corresponding resources managed by
-		# Cluster API Providers are orphaned and there might be ongoing costs incurred as a result of this.
-		clusterctl delete --all --namespace=foo
-
-		# Delete the AWS infrastructure provider and Core provider. This will leave behind Bootstrap and ControlPlane
-		# providers
-		# Important! As a consequence of this operation, all the corresponding resources managed by
-		# the AWS infrastructure provider and Cluster API Providers are orphaned and there might be
-		# ongoing costs incurred as a result of this.
-		clusterctl delete --core cluster-api --infrastructure aws
-
 		# Delete the AWS infrastructure provider and related CRDs. Please note that this forces deletion of
 		# all the related objects (e.g. AWSClusters, AWSMachines etc.).
 		# Important! As a consequence of this operation, all the corresponding resources managed by
diff --git a/cmd/clusterctl/cmd/root.go b/cmd/clusterctl/cmd/root.go
index 9da0e18c2..9d40ac637 100644
--- a/cmd/clusterctl/cmd/root.go
+++ b/cmd/clusterctl/cmd/root.go
@@ -20,14 +20,12 @@ import (
 	"flag"
 	"fmt"
 	"os"
-	"path/filepath"
 	"strconv"
 	"strings"
 
 	"github.com/MakeNowJust/heredoc"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
-	"k8s.io/client-go/util/homedir"
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
 	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
 )
@@ -48,16 +46,6 @@ var RootCmd = &cobra.Command{
 	Long: LongDesc(`
 		Get started with Cluster API using clusterctl to create a management cluster,
 		install providers, and create templates for your workload cluster.`),
-	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-		// Check if Config folder (~/.cluster-api) exist and if not create it
-		configFolderPath := filepath.Join(homedir.HomeDir(), config.ConfigFolder)
-		if _, err := os.Stat(configFolderPath); os.IsNotExist(err) {
-			if err := os.MkdirAll(filepath.Dir(configFolderPath), os.ModePerm); err != nil {
-				return errors.Wrapf(err, "failed to create the clusterctl config directory: %s", configFolderPath)
-			}
-		}
-		return nil
-	},
 	PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
 		// Check if clusterctl needs an upgrade "AFTER" running each command
 		// and sub-command.
@@ -73,16 +61,6 @@ var RootCmd = &cobra.Command{
 			// Print the output in yellow so it is more visible.
 			fmt.Fprintf(os.Stderr, "\033[33m%s\033[0m", output)
 		}
-
-		// clean the downloaded config if was fetched from remote
-		downloadConfigFile := filepath.Join(homedir.HomeDir(), config.ConfigFolder, config.DownloadConfigFile)
-		if _, err := os.Stat(downloadConfigFile); err == nil {
-			if verbosity != nil && *verbosity >= 5 {
-				fmt.Fprintf(os.Stdout, "Removing downloaded clusterctl config file: %s\n", config.DownloadConfigFile)
-			}
-			_ = os.Remove(downloadConfigFile)
-		}
-
 		return nil
 	},
 }
@@ -108,7 +86,7 @@ func init() {
 
 	RootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
 	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
-		"Path to clusterctl configuration (default is `$HOME/.cluster-api/clusterctl.yaml`) or to a remote location (i.e. https://example.com/clusterctl.yaml)")
+		"Path to clusterctl configuration (default is `$HOME/.cluster-api/clusterctl.yaml`)")
 
 	cobra.OnInitialize(initConfig)
 }
diff --git a/cmd/clusterctl/cmd/version_checker.go b/cmd/clusterctl/cmd/version_checker.go
index 4ab6e396a..36b68dc3d 100644
--- a/cmd/clusterctl/cmd/version_checker.go
+++ b/cmd/clusterctl/cmd/version_checker.go
@@ -65,7 +65,7 @@ func newVersionChecker(vc config.VariablesClient) *versionChecker {
 	}
 
 	return &versionChecker{
-		versionFilePath: filepath.Join(homedir.HomeDir(), config.ConfigFolder, "version.yaml"),
+		versionFilePath: filepath.Join(homedir.HomeDir(), ".cluster-api", "version.yaml"),
 		cliVersion:      version.Get,
 		githubClient:    client,
 	}
diff --git a/cmd/clusterctl/hack/create-local-repository.py b/cmd/clusterctl/hack/create-local-repository.py
index 4ca3a5ab8..171f92dea 100755
--- a/cmd/clusterctl/hack/create-local-repository.py
+++ b/cmd/clusterctl/hack/create-local-repository.py
@@ -126,7 +126,7 @@ def write_local_repository(provider, version, components_file, components_yaml):
             if e.errno != errno.EEXIST:
                 raise
         components_path = os.path.join(provider_folder, components_file)
-        f = open(components_path, 'w')
+        f = open(components_path, 'wb')
         f.write(components_yaml)
         f.close()
 
@@ -178,7 +178,7 @@ def create_dev_config(repos):
     try:
         repository_folder = get_repository_folder()
         config_path = os.path.join(repository_folder, "config.yaml")
-        f = open(config_path, 'w')
+        f = open(config_path, 'wb')
         f.write(yaml)
         f.close()
         return components_path
diff --git a/cmd/example-provider/Dockerfile b/cmd/example-provider/Dockerfile
index 0dc5ef6e2..2d2439efb 100644
--- a/cmd/example-provider/Dockerfile
+++ b/cmd/example-provider/Dockerfile
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Build the manager binary
-FROM golang:1.13.15 as builder
+FROM golang:1.13.14 as builder
 
 # Run this with docker build --build_arg goproxy=$(go env GOPROXY) to override the goproxy
 ARG goproxy=https://proxy.golang.org
diff --git a/controllers/cluster_controller.go b/controllers/cluster_controller.go
index cdaec257c..7f8c03706 100644
--- a/controllers/cluster_controller.go
+++ b/controllers/cluster_controller.go
@@ -29,7 +29,6 @@ import (
 	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/runtime"
 	kerrors "k8s.io/apimachinery/pkg/util/errors"
-	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/record"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controllers/external"
@@ -68,7 +67,6 @@ type ClusterReconciler struct {
 	Log    logr.Logger
 
 	scheme          *runtime.Scheme
-	restConfig      *rest.Config
 	recorder        record.EventRecorder
 	externalTracker external.ObjectTracker
 }
@@ -90,7 +88,6 @@ func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager, options controlle
 
 	r.recorder = mgr.GetEventRecorderFor("cluster-controller")
 	r.scheme = mgr.GetScheme()
-	r.restConfig = mgr.GetConfig()
 	r.externalTracker = external.ObjectTracker{
 		Controller: controller,
 	}
diff --git a/controllers/cluster_controller_phases.go b/controllers/cluster_controller_phases.go
index 86c46dd6e..853d0a504 100644
--- a/controllers/cluster_controller_phases.go
+++ b/controllers/cluster_controller_phases.go
@@ -66,7 +66,7 @@ func (r *ClusterReconciler) reconcilePhase(_ context.Context, cluster *clusterv1
 func (r *ClusterReconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) (external.ReconcileOutput, error) {
 	logger := r.Log.WithValues("cluster", cluster.Name, "namespace", cluster.Namespace)
 
-	if err := utilconversion.ConvertReferenceAPIContract(ctx, logger, r.Client, r.restConfig, ref); err != nil {
+	if err := utilconversion.ConvertReferenceAPIContract(ctx, r.Client, ref); err != nil {
 		return external.ReconcileOutput{}, err
 	}
 
diff --git a/controllers/machine_controller.go b/controllers/machine_controller.go
index 4fd7196eb..e490328c4 100644
--- a/controllers/machine_controller.go
+++ b/controllers/machine_controller.go
@@ -74,7 +74,7 @@ type MachineReconciler struct {
 	Log     logr.Logger
 	Tracker *remote.ClusterCacheTracker
 
-	restConfig      *rest.Config
+	config          *rest.Config
 	scheme          *runtime.Scheme
 	recorder        record.EventRecorder
 	externalTracker external.ObjectTracker
@@ -108,7 +108,7 @@ func (r *MachineReconciler) SetupWithManager(mgr ctrl.Manager, options controlle
 	}
 
 	r.recorder = mgr.GetEventRecorderFor("machine-controller")
-	r.restConfig = mgr.GetConfig()
+	r.config = mgr.GetConfig()
 	r.scheme = mgr.GetScheme()
 	r.externalTracker = external.ObjectTracker{
 		Controller: controller,
diff --git a/controllers/machine_controller_phases.go b/controllers/machine_controller_phases.go
index 81edf10f7..06af72c23 100644
--- a/controllers/machine_controller_phases.go
+++ b/controllers/machine_controller_phases.go
@@ -90,7 +90,7 @@ func (r *MachineReconciler) reconcilePhase(_ context.Context, m *clusterv1.Machi
 func (r *MachineReconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine, ref *corev1.ObjectReference) (external.ReconcileOutput, error) {
 	logger := r.Log.WithValues("machine", m.Name, "namespace", m.Namespace)
 
-	if err := utilconversion.ConvertReferenceAPIContract(ctx, logger, r.Client, r.restConfig, ref); err != nil {
+	if err := utilconversion.ConvertReferenceAPIContract(ctx, r.Client, ref); err != nil {
 		return external.ReconcileOutput{}, err
 	}
 
diff --git a/controllers/machinedeployment_controller.go b/controllers/machinedeployment_controller.go
index ab8459440..f42fdee9c 100644
--- a/controllers/machinedeployment_controller.go
+++ b/controllers/machinedeployment_controller.go
@@ -27,7 +27,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	kerrors "k8s.io/apimachinery/pkg/util/errors"
-	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/record"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/util"
@@ -57,8 +56,7 @@ type MachineDeploymentReconciler struct {
 	Client client.Client
 	Log    logr.Logger
 
-	recorder   record.EventRecorder
-	restConfig *rest.Config
+	recorder record.EventRecorder
 }
 
 func (r *MachineDeploymentReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
@@ -94,7 +92,6 @@ func (r *MachineDeploymentReconciler) SetupWithManager(mgr ctrl.Manager, options
 	}
 
 	r.recorder = mgr.GetEventRecorderFor("machinedeployment-controller")
-	r.restConfig = mgr.GetConfig()
 	return nil
 }
 
@@ -180,12 +177,12 @@ func (r *MachineDeploymentReconciler) reconcile(ctx context.Context, cluster *cl
 	}
 
 	// Make sure to reconcile the external infrastructure reference.
-	if err := reconcileExternalTemplateReference(ctx, logger, r.Client, r.restConfig, cluster, &d.Spec.Template.Spec.InfrastructureRef); err != nil {
+	if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, &d.Spec.Template.Spec.InfrastructureRef); err != nil {
 		return ctrl.Result{}, err
 	}
 	// Make sure to reconcile the external bootstrap reference, if any.
 	if d.Spec.Template.Spec.Bootstrap.ConfigRef != nil {
-		if err := reconcileExternalTemplateReference(ctx, logger, r.Client, r.restConfig, cluster, d.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil {
+		if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, d.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil {
 			return ctrl.Result{}, err
 		}
 	}
diff --git a/controllers/machineset_controller.go b/controllers/machineset_controller.go
index 821444611..5c605ba9e 100644
--- a/controllers/machineset_controller.go
+++ b/controllers/machineset_controller.go
@@ -30,7 +30,6 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
 	kerrors "k8s.io/apimachinery/pkg/util/errors"
-	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/record"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controllers/external"
@@ -73,9 +72,8 @@ type MachineSetReconciler struct {
 	Log     logr.Logger
 	Tracker *remote.ClusterCacheTracker
 
-	recorder   record.EventRecorder
-	scheme     *runtime.Scheme
-	restConfig *rest.Config
+	recorder record.EventRecorder
+	scheme   *runtime.Scheme
 }
 
 func (r *MachineSetReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
@@ -110,7 +108,6 @@ func (r *MachineSetReconciler) SetupWithManager(mgr ctrl.Manager, options contro
 
 	r.recorder = mgr.GetEventRecorderFor("machineset-controller")
 	r.scheme = mgr.GetScheme()
-	r.restConfig = mgr.GetConfig()
 	return nil
 }
 
@@ -179,12 +176,12 @@ func (r *MachineSetReconciler) reconcile(ctx context.Context, cluster *clusterv1
 	}
 
 	// Make sure to reconcile the external infrastructure reference.
-	if err := reconcileExternalTemplateReference(ctx, logger, r.Client, r.restConfig, cluster, &machineSet.Spec.Template.Spec.InfrastructureRef); err != nil {
+	if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, &machineSet.Spec.Template.Spec.InfrastructureRef); err != nil {
 		return ctrl.Result{}, err
 	}
 	// Make sure to reconcile the external bootstrap reference, if any.
 	if machineSet.Spec.Template.Spec.Bootstrap.ConfigRef != nil {
-		if err := reconcileExternalTemplateReference(ctx, logger, r.Client, r.restConfig, cluster, machineSet.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil {
+		if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, machineSet.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil {
 			return ctrl.Result{}, err
 		}
 	}
@@ -693,12 +690,12 @@ func (r *MachineSetReconciler) getMachineNode(ctx context.Context, cluster *clus
 	return node, nil
 }
 
-func reconcileExternalTemplateReference(ctx context.Context, logger logr.Logger, c client.Client, restConfig *rest.Config, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) error {
+func reconcileExternalTemplateReference(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) error {
 	if !strings.HasSuffix(ref.Kind, external.TemplateSuffix) {
 		return nil
 	}
 
-	if err := utilconversion.ConvertReferenceAPIContract(ctx, logger, c, restConfig, ref); err != nil {
+	if err := utilconversion.ConvertReferenceAPIContract(ctx, c, ref); err != nil {
 		return err
 	}
 
diff --git a/controllers/remote/cluster_cache.go b/controllers/remote/cluster_cache.go
index 0e8cfefc1..933116f87 100644
--- a/controllers/remote/cluster_cache.go
+++ b/controllers/remote/cluster_cache.go
@@ -27,7 +27,6 @@ import (
 	"github.com/pkg/errors"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -59,7 +58,6 @@ type clusterCache struct {
 	cache.Cache
 
 	lock    sync.Mutex
-	mapper  meta.RESTMapper
 	stopped bool
 	stop    chan struct{}
 }
@@ -274,7 +272,7 @@ func (m *ClusterCacheTracker) newDelegatingClient(ctx context.Context, cluster c
 		return nil, errors.Wrap(err, "error fetching REST client config for remote cluster")
 	}
 	config.Timeout = defaultClientTimeout
-	c, err := client.New(config, client.Options{Scheme: m.scheme, Mapper: cache.mapper})
+	c, err := client.New(config, client.Options{Scheme: m.scheme})
 	if err != nil {
 		return nil, err
 	}
@@ -344,9 +342,8 @@ func (m *ClusterCacheTracker) newClusterCache(ctx context.Context, cluster clien
 	stop := make(chan struct{})
 
 	cc := &clusterCache{
-		Cache:  remoteCache,
-		stop:   stop,
-		mapper: mapper,
+		Cache: remoteCache,
+		stop:  stop,
 	}
 	m.clusterCaches[cluster] = cc
 
@@ -405,17 +402,7 @@ func (m *ClusterCacheTracker) healthCheckCluster(in *healthCheckInput) {
 
 	unhealthyCount := 0
 
-	codec := runtime.NoopEncoder{Decoder: scheme.Codecs.UniversalDecoder()}
-	cfg := rest.CopyConfig(in.cfg)
-	cfg.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
-
-	restClient, restClientErr := rest.UnversionedRESTClientFor(cfg)
-
 	runHealthCheckWithThreshold := func() (bool, error) {
-		if restClientErr != nil {
-			return false, restClientErr
-		}
-
 		cluster := &clusterv1.Cluster{}
 		if err := m.client.Get(context.TODO(), in.cluster, cluster); err != nil {
 			if apierrors.IsNotFound(err) {
@@ -440,7 +427,7 @@ func (m *ClusterCacheTracker) healthCheckCluster(in *healthCheckInput) {
 		// healthCheckPath returning an error is considered a failed health check
 		// (Either an issue was encountered connecting or the API returned an error).
 		// If no error occurs, reset the unhealthy coutner.
-		err := healthCheckPath(restClient, in.requestTimeout, in.path)
+		err := healthCheckPath(in.cfg, in.requestTimeout, in.path)
 		if err != nil {
 			unhealthyCount++
 		} else {
@@ -466,8 +453,6 @@ func (m *ClusterCacheTracker) healthCheckCluster(in *healthCheckInput) {
 			return
 		}
 
-		m.log.Error(err, "Error health checking cluster", "cluster", in.cluster.String())
-
 		// Stop the cache and clean up
 		c.Stop()
 		m.deleteClusterCache(in.cluster)
@@ -478,9 +463,23 @@ func (m *ClusterCacheTracker) healthCheckCluster(in *healthCheckInput) {
 
 // healthCheckPath attempts to request a given absolute path from the API server
 // defined in the rest.Config and returns any errors that occurred during the request.
-func healthCheckPath(restClient *rest.RESTClient, requestTimeout time.Duration, path string) error {
-	_, err := restClient.Get().AbsPath(path).Timeout(requestTimeout).DoRaw()
-	return err
+func healthCheckPath(sourceCfg *rest.Config, requestTimeout time.Duration, path string) error {
+	codec := runtime.NoopEncoder{Decoder: scheme.Codecs.UniversalDecoder()}
+	cfg := rest.CopyConfig(sourceCfg)
+	cfg.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
+
+	restClient, err := rest.UnversionedRESTClientFor(cfg)
+	if err != nil {
+		// Config is invalid, cannot perform health checks
+		return err
+	}
+
+	_, err = restClient.Get().AbsPath(path).Timeout(requestTimeout).Do().Get()
+	if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 // ClusterCacheReconciler is responsible for stopping remote cluster caches when
diff --git a/controlplane/kubeadm/controllers/controller.go b/controlplane/kubeadm/controllers/controller.go
index d6a10904c..b9a046b17 100644
--- a/controlplane/kubeadm/controllers/controller.go
+++ b/controlplane/kubeadm/controllers/controller.go
@@ -180,7 +180,7 @@ func (r *KubeadmControlPlaneReconciler) Reconcile(req ctrl.Request) (res ctrl.Re
 		if err := r.updateStatus(ctx, kcp, cluster); err != nil {
 			var connFailure *internal.RemoteClusterConnectionError
 			if errors.As(err, &connFailure) {
-				logger.Info("Could not connect to workload cluster to fetch status", "err", err.Error())
+				logger.Info("Could not connect to workload cluster to fetch status", "err", err)
 			} else {
 				logger.Error(err, "Failed to update KubeadmControlPlane Status")
 				reterr = kerrors.NewAggregate([]error{reterr, err})
diff --git a/docs/book/src/developer/tilt.md b/docs/book/src/developer/tilt.md
index ddde092ca..9ab61301a 100644
--- a/docs/book/src/developer/tilt.md
+++ b/docs/book/src/developer/tilt.md
@@ -148,7 +148,7 @@ base64 -i ~/path/to/gcp/credentials.json
 Set to `manual` to disable auto-rebuilding and require users to trigger rebuilds of individual changed components through the UI.
 
 **extra_args** (Object, default={}): A mapping of provider to additional arguments to pass to the main binary configured
-for this provider. Each item in the array will be passed in to the manager for the given provider.
+for this provider. Each item in the array will be passed in to the manager for the given provider. 
 
 Example:
 
@@ -229,11 +229,6 @@ COPY --from=tilt-helper /usr/bin/docker /usr/bin/docker
 COPY --from=tilt-helper /go/kubernetes/client/bin/kubectl /usr/bin/kubectl
 ```
 
-**kustomize_config** (Bool, default=true): Whether or not running kustomize on the ./config folder of the provider.
-Set to `false` if your provider does not have a ./config folder or you do not want it to be applied in the cluster.
-
-**go_main** (String, default="main.go"): The go main file if not located at the root of the folder
-
 ## Customizing Tilt
 
 If you need to customize Tilt's behavior, you can create files in cluster-api's `tilt.d` directory. This file is ignored
diff --git a/docs/book/src/user/quick-start.md b/docs/book/src/user/quick-start.md
index 1112a4176..e6f9b4982 100644
--- a/docs/book/src/user/quick-start.md
+++ b/docs/book/src/user/quick-start.md
@@ -678,18 +678,6 @@ kubectl --kubeconfig=./capi-quickstart.kubeconfig get nodes
 {{#/tab }}
 {{#/tabs }}
 
-### Clean Up
-
-Delete workload cluster.
-```bash
-kubectl delete cluster capi-quickstart
-```
-
-Delete management cluster
-```bash
-kind delete cluster
-```
-
 ## Next steps
 
 See the [clusterctl] documentation for more detail about clusterctl supported actions.
diff --git a/docs/proposals/20191017-kubeadm-based-control-plane.md b/docs/proposals/20191017-kubeadm-based-control-plane.md
index 1dff19f80..27a7cfb81 100644
--- a/docs/proposals/20191017-kubeadm-based-control-plane.md
+++ b/docs/proposals/20191017-kubeadm-based-control-plane.md
@@ -18,7 +18,7 @@ reviewers:
   - "@hardikdr"
   - "@sbueringer"
 creation-date: 2019-10-17
-last-updated: 2019-12-04
+last-updated: 2020-09-07
 status: implementable
 ---
 
@@ -91,14 +91,15 @@ for the default implementation would not preclude the use of alternative control
 
 - To establish new resource types for control plane management
 - To support single node and multiple node control plane instances, with the requirement that the infrastructure provider supports some type of a stable endpoint for the API Server (Load Balancer, VIP, etc).
+- To enable scaling of the number of control plane nodes
 - To enable declarative orchestrated control plane upgrades
 - To provide a default machine-based implementation using kubeadm
 - To provide a kubeadm-based implementation that is infrastructure provider agnostic
 - To enable declarative orchestrated replacement of control plane machines, such as to roll out an OS-level CVE fix.
 - To manage a kubeadm-based, "stacked etcd" control plane
-- To enable scaling of the number of control plane nodes
-- To support pre-existing, user-managed, external etcd clusters
-- To support only user-initiated remediation:
+- To manage a kubeadm-based, "external etcd" control plane (using a pre-existing, user-managed, etcd clusters).
+- To manage control plane deployments across failure domains.
+- To support user-initiated remediation:
   E.g. user deletes a Machine. Control Plane Provider reconciles by removing the corresponding etcd member and updating related metadata
 
 ### Non-Goals / Future Work
@@ -108,7 +109,6 @@ Non-Goals listed in this document are intended to scope bound the current v1alph
 - To manage non-machine based topologies, e.g.
   - Pod based control planes.
   - Non-node control planes (i.e. EKS, GKE, AKS).
-- To manage control plane deployments across failure domains, follow up work for this will be tracked on [this issue](https://github.com/kubernetes-sigs/cluster-api/issues/1647).
 - To define a mechanism for providing a stable API endpoint for providers that do not currently have one, follow up work for this will be tracked on [this issue](https://github.com/kubernetes-sigs/cluster-api/issues/1687)
 - To predefine the exact contract/interoperability mechanism for alternative control plane providers, follow up work for this will be tracked on [this issue](https://github.com/kubernetes-sigs/cluster-api/issues/1727)
 - To manage CA certificates outside of what is provided by Kubeadm bootstrapping
@@ -151,156 +151,7 @@ Non-Goals listed in this document are intended to scope bound the current v1alph
 
 #### New API Types
 
-KubeadmControlPlane:
-
-```go
-package v1alpha3
-
-import (
-    corev1  "k8s.io/api/core/v1"
-    metav1  "k8s.io/apimachinery/pkg/apis/meta/v1"
-    cabpkv1 "sigs.k8s.io/cluster-api-bootstrap-provider-kubeadm/api/v1alpha2"
-)
-
-// KubeadmControlPlaneSpec defines the desired state of KubeadmControlPlane.
-type KubeadmControlPlaneSpec struct {
-    // Number of desired machines. Defaults to 1. When stacked etcd is used only
-    // odd numbers are permitted, as per [etcd best practice](https://etcd.io/docs/v3.3.12/faq/#why-an-odd-number-of-cluster-members).
-    // This is a pointer to distinguish between explicit zero and not specified.
-    // +optional
-    Replicas *int32 `json:"replicas,omitempty"`
-
-    // Version defines the desired Kubernetes version.
-    Version string `json:"version"`
-
-    // InfrastructureTemplate is a required reference to a custom resource
-    // offered by an infrastructure provider.
-    InfrastructureTemplate corev1.ObjectReference `json:"infrastructureTemplate"`
-
-    // KubeadmConfigSpec is a KubeadmConfigSpec
-    // to use for initializing and joining machines to the control plane.
-    KubeadmConfigSpec cabpkv1.KubeadmConfigSpec `json:"kubeadmConfigSpec"`
-}
-
-// KubeadmControlPlaneStatus defines the observed state of KubeadmControlPlane.
-type KubeadmControlPlaneStatus struct {
-    // Selector is the label selector in string format to avoid introspection
-    // by clients, and is used to provide the CRD-based integration for the
-    // scale subresource and additional integrations for things like kubectl
-    // describe.. The string will be in the same format as the query-param syntax.
-    // More info about label selectors: http://kubernetes.io/docs/user-guide/labels#label-selectors
-    // +optional
-    Selector string `json:"selector,omitempty"`
-
-    // Total number of non-terminated machines targeted by this control plane
-    // (their labels match the selector).
-    // +optional
-    Replicas int32 `json:"replicas,omitempty"`
-
-    // Total number of non-terminated machines targeted by this control plane
-    // that have the desired template spec.
-    // +optional
-    UpdatedReplicas int32 `json:"updatedReplicas,omitempty"`
-
-    // Total number of fully running and ready control plane machines.
-    // +optional
-    ReadyReplicas int32 `json:"readyReplicas,omitempty"`
-
-    // Total number of unavailable machines targeted by this control plane.
-    // This is the total number of machines that are still required for
-    // the deployment to have 100% available capacity. They may either
-    // be machines that are running but not yet ready or machines
-    // that still have not been created.
-    // +optional
-    UnavailableReplicas int32 `json:"unavailableReplicas,omitempty"`
-
-    // Initialized denotes whether or not the control plane has the
-    // uploaded kubeadm-config configmap.
-    // +optional
-    Initialized bool `json:"initialized,omitempty”`
-
-    // Ready denotes that the KubeadmControlPlane API Server is ready to
-    // receive requests.
-    // +optional
-    Ready bool `json:"ready,omitempty"`
-
-    // FailureReason indicates that there is a problem reconciling the
-    // state, and will be set to a token value suitable for
-    // programmatic interpretation.
-    // +optional
-    FailureReason KubeadmControlPlaneStatusError `json:"errorReason,omitempty"`
-
-    // FailureMessage indicates that there is a problem reconciling the
-    // state, and will be set to a descriptive error message.
-    // +optional
-    FailureMessage *string `json:"errorMessage,omitempty"`
-
-// +kubebuilder:object:root=true
-// +kubebuilder:resource:path=controlplanes,shortName=cp,scope=Namespaced,categories=cluster-api
-// +kubebuilder:storageversion
-// +kubebuilder:subresource:status
-// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector
-
-// KubeadmControlPlane is the Schema for the KubeadmControlPlane API.
-type KubeadmControlPlane struct {
-    metav1.TypeMeta   `json:",inline"`
-    metav1.ObjectMeta `json:"metadata,omitempty"`
-
-    Spec   KubeadmControlPlaneSpec   `json:"spec,omitempty"`
-    Status KubeadmControlPlaneStatus `json:"status,omitempty"`
-}
-
-// +kubebuilder:object:root=true
-
-// KubeadmControlPlaneList contains a list of KubeadmControlPlane.
-type KubeadmControlPlaneList struct {
-    metav1.TypeMeta `json:",inline"`
-    metav1.ListMeta `json:"metadata,omitempty"`
-    Items           []KubeadmControlPlane `json:"items"`
-}
-
-func init() {
-    SchemeBuilder.Register(&KubeadmControlPlane{}, &KubeadmControlPlaneList{})
-}
-
-type KubeadmControlPlaneStatusError string
-
-// A more descriptive kind of error that represents an error condition that
-// should be set in the KubeadmControlPlane.Status. The "Reason" field is meant for short,
-// enum-style constants meant to be interpreted by control planes. The "Message"
-// field is meant to be read by humans.
-type KubeadmControlPlaneError struct {
-    Reason  KubeadmControlPlaneStatusError
-    Message string
-}
-
-func (e *KubeadmControlPlaneError) Error() string {
-    return e.Message
-}
-
-const (
-    // InvalidConfigurationKubeadmControlPlaneError indicates that the kubeadm control plane
-    // configuration is invalid.
-    InvalidConfigurationKubeadmControlPlaneError KubeadmControlPlaneStatusError = "InvalidConfiguration"
-
-    // UnsupportedChangeKubeadmControlPlaneError indicates that the kubeadm control plane
-    // spec has been updated in an unsupported way. That cannot be
-    // reconciled.
-    UnsupportedChangeKubeadmControlPlaneError KubeadmControlPlaneStatusError = "UnsupportedChange"
-
-    // CreateKubeadmControlPlaneError indicates that an error was encountered
-    // when trying to create the kubeadm control plane.
-    CreateKubeadmControlPlaneError KubeadmControlPlaneStatusError = "CreateError"
-
-    // UpdateKubeadmControlPlaneError indicates that an error was encountered
-    // when trying to update the kubeadm control plane.
-    UpdateKubeadmControlPlaneError KubeadmControlPlaneStatusError = "UpdateError"
-
-    // DeleteKubeadmControlPlaneError indicates that an error was encountered
-    // when trying to delete the kubeadm control plane.
-    DeleteKubeadmControlPlaneError KubeadmControlPlaneStatusError = "DeleteError"
-)
-```
+See [kubeadm_control_plane_types.go](https://github.com/kubernetes-sigs/cluster-api/blob/master/controlplane/kubeadm/api/v1alpha3/kubeadm_control_plane_types.go)
 
 With the following validations:
 
@@ -308,12 +159,16 @@ With the following validations:
   - `KubeadmControlPlane.Spec.Replicas` is an odd number.
   - Configuration of external etcd is determined by introspecting the provided `KubeadmConfigSpec`.
 - `KubeadmControlPlane.Spec.Replicas` is >= 0 or is nil
-- `KubeadmControlPlane.Spec.Version != ""` (openapi)
-- `KubeadmControlPlane.Spec.KubeadmConfigSpec` must be treated as immutable (via webhook)
+- `KubeadmControlPlane.Spec.Version` should be a valid semantic version
+- `KubeadmControlPlane.Spec.KubeadmConfigSpec` allows mutations required for supporting following use cases:
+    - Change of imagesRepository/imageTags (with validation of CoreDNS supported upgrade)
+    - Change of node registration options
+    - Change of pre/post kubeadm commands 
+    - Change of cloud init files
 
 And the following defaulting:
 
-- `KubeadmControlPlane.Spec.Replicas: 1` (openapi or webhook)
+- `KubeadmControlPlane.Spec.Replicas: 1` 
 
 #### Modifications required to existing API Types
 
@@ -348,7 +203,6 @@ And the following defaulting:
 ##### Create
 
 - After a KubeadmControlPlane object is created, it must bootstrap a control plane with a given number of replicas.
-- If an error occurs, `KubeadmControlPlane.Status.FailureReason` and `KubeadmControlPlane.Status.FailureMessage` are populated.
 - `KubeadmControlPlane.Spec.Replicas` must be an odd number.
 - Can create an arbitrary number of control planes if etcd is external to the control plane, which will be determined by introspecting `KubeadmControlPlane.Spec.KubeadmConfigSpec`.
 - Creating a KubeadmControlPlane with > 1 replicas is equivalent to creating a KubeadmControlPlane with 1 replica followed by scaling the KubeadmControlPlane to the desired number of replicas
@@ -453,70 +307,62 @@ spec:
 - Allow scale up a control plane with stacked etcd to only odd numbers, as per
   [etcd best practice](https://etcd.io/docs/v3.3.12/faq/#why-an-odd-number-of-cluster-members).
 - However, allow a control plane using an external etcd cluster to scale up to other numbers such as 2 or 4.
-- Scale up operations must not be done in conjunction with an upgrade operation, this should be enforced using a validation webhook.
+- Scale up operations must not be done in conjunction with:
+  - Adopting machines
+  - Upgrading machines
 - Scale up operations are blocked based on Etcd and control plane health checks.
   - See [Health checks](#Health checks) below.
+- Scale up operations creates the next machine in the failure domain with the fewest number of machines. 
 
 ![controlplane-init-6](images/controlplane/controlplane-init-6.png)
 
 ##### Scale Down
 
-The annotation steps are required to make scale down re-entrant.
-
-- Scale down a stacked control plane
-  - Wait for deletes to finish before scaling down
-  - Find the oldest machine, or machine marked for removal and target that as the replica to remove
-  - Apply an annotation to the machine marked for removal
-  - Require Etcd to be healthy
-  - Remove the replica’s etcd member from the etcd cluster
-  - Apply an annotation to the machine that has had the etcd member removed from the etcd cluster
-  - Require Control plane to be healthy
-  - Update the kubeadm generated config map
-  - Apply an annotation to the machine that the kubeadm config map has been updated
-  - Require the annotations that the etcd member has been removed and the kubeadm config map updated
-  - Remove the Machine
-- Scale down a control plane with an external etcd
-  - Same as above minus etcd management
-- Scale down operations must not be done in conjunction with an upgrade operation, this should not impact manual operations for recovery. This should also be enforced using a validation webhook.
-- Scale down operations are blocked based on Etcd and control plane health checks
-  - Etcd health is not required if a machine has already been selected for scale down
+- Allow scale down a control plane with stacked etcd to only odd numbers, as per
+  [etcd best practice](https://etcd.io/docs/v3.3.12/faq/#why-an-odd-number-of-cluster-members).
+- However, allow a control plane using an external etcd cluster to scale down to other numbers such as 2 or 4.
+- Scale up operations must not be done in conjunction with:
+  - Adopting machines
+  - Upgrading machines
+- Scale up operations are blocked based on Etcd and control plane health checks.
   - See [Health checks](#Health checks) below.
-- Scale to 0 must be rejected for the initial support, see below for deletion workflows.
+- Scale down operations removes the oldest machine in the failure domain that has the most control-plane machines on it 
 
 ![controlplane-init-7](images/controlplane/controlplane-init-7.png)
 
 ##### Delete of the entire KubeadmControlPlane (kubectl delete controlplane my-controlplane)
 
+- KubeadmControlPlane deletion should be blocked until all the worker nodes are deleted.
 - Completely removing the control plane and issuing a delete on the underlying machines.
-  - Deletion is equivalent to a scale to 1 followed by a deletion of a single replica control plane.
 - User documentation should focus on deletion of the Cluster resource rather than the KubeadmControlPlane resource.
-- Cluster deletion will need to be modified to ensure KubeadmControlPlane deletion is done in the proper order.
 
-##### Cluster upgrade (using create-swap-and-delete)
+##### KubeadmControlPlane rollout (using create-swap-and-delete)
 
-- Triggered on any changes to Version or the InfrastructureTemplate
-  - The KubeadmConfiguration, for the first iteration, is treated and validated as immutable and changes to it do not trigger an upgrade
-  - Must be able to trigger an upgrade for the underlying machine image (e.g. nothing in the above list has changed but an upgrade is required)
-      - By adding a timestamp field, `upgradeAfter`, which can be set to a specific time in the future
+- Triggered by: 
+    - Changes to Version
+    - Changes to the kubeadmConfigSpec
+    - Changes to the infrastructureRef
+    - The `upgradeAfter` field, which can be set to a specific time in the future
         - Set to `nil` or the zero value of `time.Time` if no upgrades are desired
         - An upgrade will run when that timestamp is passed
         - Good for scheduling upgrades/SLOs
         - Set `upgradeAfter` to now (in RFC3339 form) if an upgrade is required immediately
 
-- Upgrade operations rely on scale up and scale down which are be blocked based on Etcd and control plane health checks
+- Rollout operations rely on scale up and scale down which are be blocked based on Etcd and control plane health checks
   - See [Health checks](#Health checks) below.
 
-- The upgrade algorithm is as follows:
-  - Find Machines that need upgrading
-    - Configuration hash doesn't match
-    - `upgradeAfter` field is before now
-  - Mark machine as selected for upgrade
-  - Scale up control plane
-  - Mark machine as having a replacement
-  - Scale down control plane by removing a machine that has been marked for removal and has a replacement already created
-
-- Determining if a Machine is "up to date" will be done through the use of a label (controlplane.cluster.x-k8s.io/hash) that is placed on the Machine at creation time. The value of this label is generated by computing the Hash of the KubeadmControlPlaneSpec (minus the replicas field). This would allow triggering an Upgrade based on any changes to Version, InfrastructureTemplate, or the KubeadmConfiguration used (when a new configuration is referenced in a new InfrastructureTemplate).
-  - For example, a CVE is patched in containerd and updated images are available. Note that the Kubernetes version is not changed. To trigger an upgrade, the user updates the image in the InfrastructureTemplate (as in the Acme cluster example above, the image is stored in InfrastructureTemplate.Spec.OSImage.ID).
+- The rollout algorithm is the following:
+  - Find Machines that have an outdated spec
+  - If there is a machine requiring rollout
+    - Scale up control plane creating a machine with the new spec
+    - Scale down control plane by removing one of the machine that needs rollout (the oldest out-of date machine in the failure domain that has the most control-plane machines on it)
+    
+- In order to determine if a Machine need rollouts includes:
+    - The infrastructureRef link used by each machine at creation time is stored in annotations at machine level.
+    - The kubeadmConfigSpec used by each machine at creation time is stored in annotations at machine level.
+        - If the annotation is not present (machine is either old or adopted), we won't roll out on any possible changes made in KCP's ClusterConfiguration given that we don't have enough information to make a decision.
+           Users should use KCP.Spec.UpgradeAfter field to force a rollout in this case.
+    
 - The controller should tolerate the manual removal of a replica during the upgrade process. A replica that fails during the upgrade may block the completion of the upgrade. Removal or other remedial action may be necessary to allow the upgrade to complete.
 
 ###### Constraints and Assumptions
@@ -606,4 +452,5 @@ For the purposes of designing upgrades, two existing lifecycle managers were exa
 - [x] 10/17/2019: Initial Creation
 - [x] 11/19/2019: Initial KubeadmControlPlane types added [#1765](https://github.com/kubernetes-sigs/cluster-api/pull/1765)
 - [x] 12/04/2019: Updated References to ErrorMessage/ErrorReason to FailureMessage/FailureReason
-- [] 12/04/2019: Initial stubbed KubeadmControlPlane controller added [#1826](https://github.com/kubernetes-sigs/cluster-api/pull/1826)
+- [x] 12/04/2019: Initial stubbed KubeadmControlPlane controller added [#1826](https://github.com/kubernetes-sigs/cluster-api/pull/1826)
+- [x] 07/09/2020: Document updated to reflect latest changes
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 19d7790b1..c08240be8 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,6 @@ require (
 	github.com/drone/envsubst v1.0.3-0.20200709223903-efdb65b94e5a
 	github.com/evanphx/json-patch v4.9.0+incompatible
 	github.com/go-logr/logr v0.1.0
-	github.com/gobuffalo/flect v0.2.2
 	github.com/gogo/protobuf v1.3.1 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/google/go-cmp v0.4.1
diff --git a/go.sum b/go.sum
index fc42d1638..2405ecdce 100644
--- a/go.sum
+++ b/go.sum
@@ -172,8 +172,6 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A=
-github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
 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/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
diff --git a/util/conversion/conversion.go b/util/conversion/conversion.go
index 9b61cf8a9..426b3a9dd 100644
--- a/util/conversion/conversion.go
+++ b/util/conversion/conversion.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/go-logr/logr"
 	"github.com/google/go-cmp/cmp"
 	fuzz "github.com/google/gofuzz"
 	"github.com/onsi/gomega"
@@ -37,7 +36,6 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/serializer"
 	"k8s.io/apimachinery/pkg/util/json"
-	"k8s.io/client-go/rest"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -56,27 +54,17 @@ var (
 // the Custom Resource Definition and looks which one is the stored version available.
 //
 // The object passed as input is modified in place if an updated compatible version is found.
-func ConvertReferenceAPIContract(ctx context.Context, log logr.Logger, c client.Client, restConfig *rest.Config, ref *corev1.ObjectReference) error {
+func ConvertReferenceAPIContract(ctx context.Context, c client.Client, ref *corev1.ObjectReference) error {
 	gvk := ref.GroupVersionKind()
-
-	metadata, err := util.GetCRDMetadataFromGVK(ctx, restConfig, gvk)
+	crd, err := util.GetCRDWithContract(ctx, c, gvk, contract)
 	if err != nil {
-		log.Info("Cannot retrieve CRD with metadata only client, falling back to slower listing", "err", err.Error())
-		// Fallback to slower and more memory intensive method to get the full CRD.
-		crd, err := util.GetCRDWithContract(ctx, c, gvk, contract)
-		if err != nil {
-			return err
-		}
-		metadata = &metav1.PartialObjectMetadata{
-			TypeMeta:   crd.TypeMeta,
-			ObjectMeta: crd.ObjectMeta,
-		}
+		return err
 	}
 
 	// If there is no label, return early without changing the reference.
-	supportedVersions, ok := metadata.Labels[contract]
+	supportedVersions, ok := crd.Labels[contract]
 	if !ok || supportedVersions == "" {
-		return errors.Errorf("cannot find any versions matching contract %q for CRD %v", contract, metadata.Name)
+		return errors.Errorf("cannot find any versions matching contract %q for CRD %v", contract, crd.Name)
 	}
 
 	// Pick the latest version in the slice and validate it.
@@ -84,6 +72,18 @@ func ConvertReferenceAPIContract(ctx context.Context, log logr.Logger, c client.
 	sort.Sort(kubeVersions)
 	chosen := kubeVersions[len(kubeVersions)-1]
 
+	// Validate that the picked version is actually in the CRD spec.
+	found := false
+	for _, version := range crd.Spec.Versions {
+		if version.Name == chosen {
+			found = true
+			break
+		}
+	}
+	if !found {
+		return errors.Errorf("cannot find any versions matching contract %q for CRD %v", contract, crd.Name)
+	}
+
 	// Modify the GroupVersionKind with the new version.
 	if gvk.Version != chosen {
 		gvk.Version = chosen
diff --git a/util/patch/patch.go b/util/patch/patch.go
index 9f5920b4d..e0c2b65b2 100644
--- a/util/patch/patch.go
+++ b/util/patch/patch.go
@@ -54,10 +54,11 @@ func NewHelper(obj runtime.Object, crClient client.Client) (*Helper, error) {
 	}
 
 	// Convert the object to unstructured to compare against our before copy.
-	unstructuredObj, err := toUnstructured(obj)
+	raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject())
 	if err != nil {
 		return nil, err
 	}
+	unstructuredObj := &unstructured.Unstructured{Object: raw}
 
 	// Check if the object satisfies the Cluster API conditions contract.
 	_, canInterfaceConditions := obj.(conditions.Setter)
@@ -83,11 +84,11 @@ func (h *Helper) Patch(ctx context.Context, obj runtime.Object, opts ...Option)
 	}
 
 	// Convert the object to unstructured to compare against our before copy.
-	var err error
-	h.after, err = toUnstructured(obj)
+	raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject())
 	if err != nil {
 		return err
 	}
+	h.after = &unstructured.Unstructured{Object: raw}
 
 	// Determine if the object has status.
 	if unstructuredHasStatus(h.after) {
diff --git a/util/patch/utils.go b/util/patch/utils.go
index be375f270..0d8fd1d47 100644
--- a/util/patch/utils.go
+++ b/util/patch/utils.go
@@ -20,7 +20,6 @@ import (
 	"strings"
 
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
 )
 
 type patchType string
@@ -46,17 +45,3 @@ func unstructuredHasStatus(u *unstructured.Unstructured) bool {
 	_, ok := u.Object["status"]
 	return ok
 }
-
-func toUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) {
-	// If the incoming object is already unstructured, perform a deep copy first
-	// otherwise DefaultUnstructuredConverter ends up returning the inner map without
-	// making a copy.
-	if _, ok := obj.(runtime.Unstructured); ok {
-		obj = obj.DeepCopyObject()
-	}
-	rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
-	if err != nil {
-		return nil, err
-	}
-	return &unstructured.Unstructured{Object: rawMap}, nil
-}
diff --git a/util/predicates/cluster_predicates.go b/util/predicates/cluster_predicates.go
index 1da12d86a..0afa3f14a 100644
--- a/util/predicates/cluster_predicates.go
+++ b/util/predicates/cluster_predicates.go
@@ -26,9 +26,10 @@ import (
 // ClusterCreateInfraReady returns a predicate that returns true for a create event when a cluster has Status.InfrastructureReady set as true
 // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy
 func ClusterCreateInfraReady(logger logr.Logger) predicate.Funcs {
+	log := logger.WithValues("predicate", "ClusterCreateInfraReady")
 	return predicate.Funcs{
 		CreateFunc: func(e event.CreateEvent) bool {
-			log := logger.WithValues("predicate", "ClusterCreateInfraReady", "eventType", "create")
+			log = log.WithValues("eventType", "create")
 
 			c, ok := e.Object.(*clusterv1.Cluster)
 			if !ok {
@@ -56,12 +57,14 @@ func ClusterCreateInfraReady(logger logr.Logger) predicate.Funcs {
 // ClusterCreateNotPaused returns a predicate that returns true for a create event when a cluster has Spec.Paused set as false
 // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy
 func ClusterCreateNotPaused(logger logr.Logger) predicate.Funcs {
+	log := logger.WithValues("predicate", "ClusterCreateNotPaused")
 	return predicate.Funcs{
 		CreateFunc: func(e event.CreateEvent) bool {
-			log := logger.WithValues("predicate", "ClusterCreateNotPaused", "eventType", "create")
+			log = log.WithValues("eventType", "create")
 
 			c, ok := e.Object.(*clusterv1.Cluster)
 			if !ok {
+
 				log.V(4).Info("Expected Cluster", "type", e.Object.GetObjectKind().GroupVersionKind().String())
 				return false
 			}
@@ -85,9 +88,10 @@ func ClusterCreateNotPaused(logger logr.Logger) predicate.Funcs {
 // ClusterUpdateInfraReady returns a predicate that returns true for an update event when a cluster has Status.InfrastructureReady changed from false to true
 // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy
 func ClusterUpdateInfraReady(logger logr.Logger) predicate.Funcs {
+	log := logger.WithValues("predicate", "ClusterUpdateInfraReady")
 	return predicate.Funcs{
 		UpdateFunc: func(e event.UpdateEvent) bool {
-			log := logger.WithValues("predicate", "ClusterUpdateInfraReady", "eventType", "update")
+			log = log.WithValues("eventType", "update")
 
 			oldCluster, ok := e.ObjectOld.(*clusterv1.Cluster)
 			if !ok {
@@ -116,9 +120,10 @@ func ClusterUpdateInfraReady(logger logr.Logger) predicate.Funcs {
 // ClusterUpdateUnpaused returns a predicate that returns true for an update event when a cluster has Spec.Paused changed from true to false
 // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy
 func ClusterUpdateUnpaused(logger logr.Logger) predicate.Funcs {
+	log := logger.WithValues("predicate", "ClusterUpdateUnpaused")
 	return predicate.Funcs{
 		UpdateFunc: func(e event.UpdateEvent) bool {
-			log := logger.WithValues("predicate", "ClusterUpdateUnpaused", "eventType", "update")
+			log = log.WithValues("eventType", "update")
 
 			oldCluster, ok := e.ObjectOld.(*clusterv1.Cluster)
 			if !ok {
diff --git a/util/predicates/generic_predicates.go b/util/predicates/generic_predicates.go
index b20baa281..6fac48261 100644
--- a/util/predicates/generic_predicates.go
+++ b/util/predicates/generic_predicates.go
@@ -30,9 +30,9 @@ import (
 
 // All returns a predicate that returns true only if all given predicates return true
 func All(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
+	log := logger.WithValues("predicateAggregation", "All")
 	return predicate.Funcs{
 		UpdateFunc: func(e event.UpdateEvent) bool {
-			log := logger.WithValues("predicateAggregation", "All")
 			for _, p := range predicates {
 				if !p.UpdateFunc(e) {
 					log.V(4).Info("One of the provided predicates returned false, blocking further processing")
@@ -43,7 +43,6 @@ func All(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 			return true
 		},
 		CreateFunc: func(e event.CreateEvent) bool {
-			log := logger.WithValues("predicateAggregation", "All")
 			for _, p := range predicates {
 				if !p.CreateFunc(e) {
 					log.V(4).Info("One of the provided predicates returned false, blocking further processing")
@@ -54,7 +53,6 @@ func All(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 			return true
 		},
 		DeleteFunc: func(e event.DeleteEvent) bool {
-			log := logger.WithValues("predicateAggregation", "All")
 			for _, p := range predicates {
 				if !p.DeleteFunc(e) {
 					log.V(4).Info("One of the provided predicates returned false, blocking further processing")
@@ -65,7 +63,6 @@ func All(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 			return true
 		},
 		GenericFunc: func(e event.GenericEvent) bool {
-			log := logger.WithValues("predicateAggregation", "All")
 			for _, p := range predicates {
 				if !p.GenericFunc(e) {
 					log.V(4).Info("One of the provided predicates returned false, blocking further processing")
@@ -80,9 +77,9 @@ func All(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 
 // Any returns a predicate that returns true only if any given predicate returns true
 func Any(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
+	log := logger.WithValues("predicateAggregation", "Any")
 	return predicate.Funcs{
 		UpdateFunc: func(e event.UpdateEvent) bool {
-			log := logger.WithValues("predicateAggregation", "Any")
 			for _, p := range predicates {
 				if p.UpdateFunc(e) {
 					log.V(4).Info("One of the provided predicates returned true, allowing further processing")
@@ -93,7 +90,6 @@ func Any(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 			return false
 		},
 		CreateFunc: func(e event.CreateEvent) bool {
-			log := logger.WithValues("predicateAggregation", "Any")
 			for _, p := range predicates {
 				if p.CreateFunc(e) {
 					log.V(4).Info("One of the provided predicates returned true, allowing further processing")
@@ -104,7 +100,6 @@ func Any(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 			return false
 		},
 		DeleteFunc: func(e event.DeleteEvent) bool {
-			log := logger.WithValues("predicateAggregation", "Any")
 			for _, p := range predicates {
 				if p.DeleteFunc(e) {
 					log.V(4).Info("One of the provided predicates returned true, allowing further processing")
@@ -115,7 +110,6 @@ func Any(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
 			return false
 		},
 		GenericFunc: func(e event.GenericEvent) bool {
-			log := logger.WithValues("predicateAggregation", "Any")
 			for _, p := range predicates {
 				if p.GenericFunc(e) {
 					log.V(4).Info("One of the provided predicates returned true, allowing further processing")
diff --git a/util/util.go b/util/util.go
index 91d0def06..68a41036d 100644
--- a/util/util.go
+++ b/util/util.go
@@ -28,7 +28,6 @@ import (
 	"time"
 
 	"github.com/blang/semver"
-	"github.com/gobuffalo/flect"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/api/core/v1"
@@ -39,7 +38,6 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/version"
-	"k8s.io/client-go/metadata"
 	"k8s.io/client-go/rest"
 	"k8s.io/klog/klogr"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
@@ -529,11 +527,10 @@ func GetCRDWithContract(ctx context.Context, c client.Client, gvk schema.GroupVe
 			return nil, errors.Wrapf(err, "failed to list CustomResourceDefinitions for %v", gvk)
 		}
 
-		for i := range crdList.Items {
-			crd := crdList.Items[i]
+		for _, crd := range crdList.Items {
 			if crd.Spec.Group == gvk.Group &&
 				crd.Spec.Names.Kind == gvk.Kind {
-				return &crd, nil
+				return crd.DeepCopy(), nil
 			}
 		}
 
@@ -545,26 +542,6 @@ func GetCRDWithContract(ctx context.Context, c client.Client, gvk schema.GroupVe
 	return nil, errors.Errorf("failed to find a CustomResourceDefinition for %v with contract %q", gvk, contract)
 }
 
-// GetCRDMetadataFromGVK retrieves a CustomResourceDefinition metadata from the API server using client-go's metadata only client.
-//
-// This function is greatly more efficient than GetCRDWithContract and should be preferred in most cases.
-func GetCRDMetadataFromGVK(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind) (*metav1.PartialObjectMetadata, error) {
-	// Make sure a rest config is available.
-	if restConfig == nil {
-		return nil, errors.Errorf("cannot create a metadata client without a rest config")
-	}
-
-	// Create a metadata-only client.
-	metadataClient, err := metadata.NewForConfig(restConfig)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to create metadata only client")
-	}
-
-	// Get the partial metadata CRD.
-	generatedName := fmt.Sprintf("%s.%s", flect.Pluralize(strings.ToLower(gvk.Kind)), gvk.Group)
-	return metadataClient.Resource(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")).Get(generatedName, metav1.GetOptions{})
-}
-
 // KubeAwareAPIVersions is a sortable slice of kube-like version strings.
 //
 // Kube-like version strings are starting with a v, followed by a major version,


Test patch:
diff --git a/cmd/clusterctl/client/client_test.go b/cmd/clusterctl/client/client_test.go
index 3f323f7db..6a80928ee 100644
--- a/cmd/clusterctl/client/client_test.go
+++ b/cmd/clusterctl/client/client_test.go
@@ -133,9 +133,9 @@ func newFakeClient(configClient config.Client) *fakeClient {
 
 	var clusterClientFactory = func(i ClusterClientFactoryInput) (cluster.Client, error) {
 		// converting the client.Kubeconfig to cluster.Kubeconfig alias
-		k := cluster.Kubeconfig(i.Kubeconfig)
+		k := cluster.Kubeconfig(i.kubeconfig)
 		if _, ok := fake.clusters[k]; !ok {
-			return nil, errors.Errorf("Cluster for kubeconfig %q and/or context %q does not exist.", i.Kubeconfig.Path, i.Kubeconfig.Context)
+			return nil, errors.Errorf("Cluster for kubeconfig %q and/or context %q does not exist.", i.kubeconfig.Path, i.kubeconfig.Context)
 		}
 		return fake.clusters[k], nil
 	}
@@ -144,10 +144,10 @@ func newFakeClient(configClient config.Client) *fakeClient {
 		InjectConfig(fake.configClient),
 		InjectClusterClientFactory(clusterClientFactory),
 		InjectRepositoryFactory(func(input RepositoryClientFactoryInput) (repository.Client, error) {
-			if _, ok := fake.repositories[input.Provider.ManifestLabel()]; !ok {
-				return nil, errors.Errorf("Repository for kubeconfig %q does not exist.", input.Provider.ManifestLabel())
+			if _, ok := fake.repositories[input.provider.ManifestLabel()]; !ok {
+				return nil, errors.Errorf("Repository for kubeconfig %q does not exist.", input.provider.ManifestLabel())
 			}
-			return fake.repositories[input.Provider.ManifestLabel()], nil
+			return fake.repositories[input.provider.ManifestLabel()], nil
 		}),
 	)
 
diff --git a/cmd/clusterctl/client/config/reader_viper_test.go b/cmd/clusterctl/client/config/reader_viper_test.go
index 60545989f..585c9800d 100644
--- a/cmd/clusterctl/client/config/reader_viper_test.go
+++ b/cmd/clusterctl/client/config/reader_viper_test.go
@@ -19,8 +19,6 @@ package config
 import (
 	"fmt"
 	"io/ioutil"
-	"net/http"
-	"net/http/httptest"
 	"os"
 	"path/filepath"
 	"strings"
@@ -48,20 +46,6 @@ func Test_viperReader_Init(t *testing.T) {
 	configFileBadContents := filepath.Join(dir, "clusterctl-bad.yaml")
 	g.Expect(ioutil.WriteFile(configFileBadContents, []byte("bad-contents"), 0600)).To(Succeed())
 
-	// To test the remote config file
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "text/plain")
-		_, err := w.Write([]byte("bar: bar"))
-		g.Expect(err).NotTo(HaveOccurred())
-	}))
-	defer ts.Close()
-
-	// To test the remote config file when fails to fetch
-	tsFail := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.WriteHeader(http.StatusNotFound)
-	}))
-	defer tsFail.Close()
-
 	tests := []struct {
 		name       string
 		configPath string
@@ -92,18 +76,6 @@ func Test_viperReader_Init(t *testing.T) {
 			configDirs: []string{clusterctlHomeDir},
 			expectErr:  true,
 		},
-		{
-			name:       "reads in config from remote successfully",
-			configPath: ts.URL,
-			configDirs: []string{clusterctlHomeDir},
-			expectErr:  false,
-		},
-		{
-			name:       "fail to read remote config",
-			configPath: tsFail.URL,
-			configDirs: []string{clusterctlHomeDir},
-			expectErr:  true,
-		},
 	}
 
 	for _, tt := range tests {
@@ -115,6 +87,7 @@ func Test_viperReader_Init(t *testing.T) {
 				return
 			}
 			gg.Expect(v.Init(tt.configPath)).To(Succeed())
+
 		})
 	}
 }
diff --git a/cmd/clusterctl/client/config_test.go b/cmd/clusterctl/client/config_test.go
index 5f254ce39..e05cb5f88 100644
--- a/cmd/clusterctl/client/config_test.go
+++ b/cmd/clusterctl/client/config_test.go
@@ -61,7 +61,6 @@ func Test_clusterctlClient_GetProvidersConfig(t *testing.T) {
 				config.TalosControlPlaneProviderName,
 				config.AWSProviderName,
 				config.AzureProviderName,
-				config.DOProviderName,
 				config.DockerProviderName,
 				config.Metal3ProviderName,
 				config.OpenStackProviderName,
@@ -86,7 +85,6 @@ func Test_clusterctlClient_GetProvidersConfig(t *testing.T) {
 				config.TalosControlPlaneProviderName,
 				config.AWSProviderName,
 				config.AzureProviderName,
-				config.DOProviderName,
 				config.DockerProviderName,
 				config.Metal3ProviderName,
 				config.OpenStackProviderName,
diff --git a/cmd/clusterctl/client/delete_test.go b/cmd/clusterctl/client/delete_test.go
index fd0c7aa4e..51c9c7ced 100644
--- a/cmd/clusterctl/client/delete_test.go
+++ b/cmd/clusterctl/client/delete_test.go
@@ -27,8 +27,6 @@ import (
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
 )
 
-var namespace = "foobar"
-
 func Test_clusterctlClient_Delete(t *testing.T) {
 	type fields struct {
 		client *fakeClient
@@ -82,11 +80,8 @@ func Test_clusterctlClient_Delete(t *testing.T) {
 					DeleteAll:               false,
 				},
 			},
-			wantProviders: sets.NewString(
-				capiProviderConfig.Name(),
-				clusterctlv1.ManifestLabel(controlPlaneProviderConfig.Name(), controlPlaneProviderConfig.Type()),
-				clusterctlv1.ManifestLabel(infraProviderConfig.Name(), infraProviderConfig.Type())),
-			wantErr: false,
+			wantProviders: sets.NewString(capiProviderConfig.Name()),
+			wantErr:       false,
 		},
 		{
 			name: "Delete single provider auto-detect namespace",
@@ -106,57 +101,8 @@ func Test_clusterctlClient_Delete(t *testing.T) {
 					DeleteAll:               false,
 				},
 			},
-			wantProviders: sets.NewString(
-				capiProviderConfig.Name(),
-				clusterctlv1.ManifestLabel(controlPlaneProviderConfig.Name(), controlPlaneProviderConfig.Type()),
-				clusterctlv1.ManifestLabel(infraProviderConfig.Name(), infraProviderConfig.Type())),
-			wantErr: false,
-		},
-		{
-			name: "Delete multiple providers of different type",
-			fields: fields{
-				client: fakeClusterForDelete(),
-			},
-			args: args{
-				options: DeleteOptions{
-					Kubeconfig:              Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
-					IncludeNamespace:        false,
-					IncludeCRDs:             false,
-					Namespace:               "", // empty namespace triggers namespace auto detection
-					CoreProvider:            capiProviderConfig.Name(),
-					BootstrapProviders:      []string{bootstrapProviderConfig.Name()},
-					InfrastructureProviders: nil,
-					ControlPlaneProviders:   nil,
-					DeleteAll:               false,
-				},
-			},
-			wantProviders: sets.NewString(
-				clusterctlv1.ManifestLabel(controlPlaneProviderConfig.Name(), controlPlaneProviderConfig.Type()),
-				clusterctlv1.ManifestLabel(infraProviderConfig.Name(), infraProviderConfig.Type())),
-			wantErr: false,
-		},
-		{
-			name: "Delete all providers in a namespace",
-			fields: fields{
-				client: fakeClusterForDelete(),
-			},
-			args: args{
-				options: DeleteOptions{
-					Kubeconfig:              Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
-					IncludeNamespace:        false,
-					IncludeCRDs:             false,
-					Namespace:               namespace,
-					CoreProvider:            "",
-					BootstrapProviders:      nil,
-					InfrastructureProviders: nil,
-					ControlPlaneProviders:   nil,
-					DeleteAll:               true,
-				},
-			},
-			wantProviders: sets.NewString(
-				capiProviderConfig.Name(),
-				clusterctlv1.ManifestLabel(bootstrapProviderConfig.Name(), bootstrapProviderConfig.Type())),
-			wantErr: false,
+			wantProviders: sets.NewString(capiProviderConfig.Name()),
+			wantErr:       false,
 		},
 	}
 	for _, tt := range tests {
@@ -193,27 +139,27 @@ func fakeClusterForDelete() *fakeClient {
 	config1 := newFakeConfig().
 		WithVar("var", "value").
 		WithProvider(capiProviderConfig).
-		WithProvider(bootstrapProviderConfig).
-		WithProvider(controlPlaneProviderConfig).
-		WithProvider(infraProviderConfig)
-
-	repository1 := newFakeRepository(capiProviderConfig, config1)
-	repository2 := newFakeRepository(bootstrapProviderConfig, config1)
-	repository3 := newFakeRepository(controlPlaneProviderConfig, config1)
-	repository4 := newFakeRepository(infraProviderConfig, config1)
+		WithProvider(bootstrapProviderConfig)
+
+	repository1 := newFakeRepository(capiProviderConfig, config1).
+		WithPaths("root", "components.yaml").
+		WithDefaultVersion("v1.0.0").
+		WithFile("v1.0.0", "components.yaml", componentsYAML("ns1")).
+		WithFile("v1.1.0", "components.yaml", componentsYAML("ns1"))
+	repository2 := newFakeRepository(bootstrapProviderConfig, config1).
+		WithPaths("root", "components.yaml").
+		WithDefaultVersion("v2.0.0").
+		WithFile("v2.0.0", "components.yaml", componentsYAML("ns2")).
+		WithFile("v2.1.0", "components.yaml", componentsYAML("ns2"))
 
 	cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, config1)
 	cluster1.fakeProxy.WithProviderInventory(capiProviderConfig.Name(), capiProviderConfig.Type(), "v1.0.0", "capi-system", "")
 	cluster1.fakeProxy.WithProviderInventory(bootstrapProviderConfig.Name(), bootstrapProviderConfig.Type(), "v1.0.0", "capbpk-system", "")
-	cluster1.fakeProxy.WithProviderInventory(controlPlaneProviderConfig.Name(), controlPlaneProviderConfig.Type(), "v1.0.0", namespace, "")
-	cluster1.fakeProxy.WithProviderInventory(infraProviderConfig.Name(), infraProviderConfig.Type(), "v1.0.0", namespace, "")
 
 	client := newFakeClient(config1).
-		// fake repository for capi, bootstrap, controlplane and infra provider (matching provider's config)
+		// fake repository for capi, bootstrap and infra provider (matching provider's config)
 		WithRepository(repository1).
 		WithRepository(repository2).
-		WithRepository(repository3).
-		WithRepository(repository4).
 		// fake empty cluster
 		WithCluster(cluster1)
 
diff --git a/cmd/clusterctl/cmd/config_repositories_test.go b/cmd/clusterctl/cmd/config_repositories_test.go
index 5ae5dee76..607b860a5 100644
--- a/cmd/clusterctl/cmd/config_repositories_test.go
+++ b/cmd/clusterctl/cmd/config_repositories_test.go
@@ -107,7 +107,6 @@ kubeadm             ControlPlaneProvider     https://github.com/kubernetes-sigs/
 talos               ControlPlaneProvider     https://github.com/talos-systems/cluster-api-control-plane-provider-talos/releases/latest/   control-plane-components.yaml
 aws                 InfrastructureProvider                                                                                                my-aws-infrastructure-components.yaml
 azure               InfrastructureProvider   https://github.com/kubernetes-sigs/cluster-api-provider-azure/releases/latest/               infrastructure-components.yaml
-do                  InfrastructureProvider   https://github.com/kubernetes-sigs/cluster-api-provider-digitalocean/releases/latest/        infrastructure-components.yaml
 docker              InfrastructureProvider   https://github.com/kubernetes-sigs/cluster-api/releases/latest/                              infrastructure-components-development.yaml
 metal3              InfrastructureProvider   https://github.com/metal3-io/cluster-api-provider-metal3/releases/latest/                    infrastructure-components.yaml
 my-infra-provider   InfrastructureProvider   /home/.cluster-api/overrides/infrastructure-docker/latest/                                   infrastructure-components.yaml
@@ -149,10 +148,6 @@ var expectedOutputYaml = `- File: core_components.yaml
   Name: azure
   ProviderType: InfrastructureProvider
   URL: https://github.com/kubernetes-sigs/cluster-api-provider-azure/releases/latest/
-- File: infrastructure-components.yaml
-  Name: do
-  ProviderType: InfrastructureProvider
-  URL: https://github.com/kubernetes-sigs/cluster-api-provider-digitalocean/releases/latest/
 - File: infrastructure-components-development.yaml
   Name: docker
   ProviderType: InfrastructureProvider
diff --git a/controllers/external/testing.go b/controllers/external/testing.go
index 7ee5c579d..15b46a49d 100644
--- a/controllers/external/testing.go
+++ b/controllers/external/testing.go
@@ -30,7 +30,7 @@ var (
 			Kind:       "CustomResourceDefinition",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "bootstrapmachines.bootstrap.cluster.x-k8s.io",
+			Name: "genericmachines.bootstrap.cluster.x-k8s.io",
 			Labels: map[string]string{
 				clusterv1.GroupVersion.String(): "v1alpha3",
 			},
@@ -40,7 +40,7 @@ var (
 			Scope: apiextensionsv1.NamespaceScoped,
 			Names: apiextensionsv1.CustomResourceDefinitionNames{
 				Kind:   "BootstrapMachine",
-				Plural: "bootstrapmachines",
+				Plural: "genericmachines",
 			},
 			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
 				{
@@ -76,7 +76,7 @@ var (
 			Kind:       "CustomResourceDefinition",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "bootstrapmachinetemplates.bootstrap.cluster.x-k8s.io",
+			Name: "genericmachinetemplates.bootstrap.cluster.x-k8s.io",
 			Labels: map[string]string{
 				clusterv1.GroupVersion.String(): "v1alpha3",
 			},
@@ -86,7 +86,7 @@ var (
 			Scope: apiextensionsv1.NamespaceScoped,
 			Names: apiextensionsv1.CustomResourceDefinitionNames{
 				Kind:   "BootstrapMachineTemplate",
-				Plural: "bootstrapmachinetemplates",
+				Plural: "genericmachinetemplates",
 			},
 			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
 				{
@@ -122,7 +122,7 @@ var (
 			Kind:       "CustomResourceDefinition",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "infrastructuremachines.infrastructure.cluster.x-k8s.io",
+			Name: "genericmachines.infrastructure.cluster.x-k8s.io",
 			Labels: map[string]string{
 				clusterv1.GroupVersion.String(): "v1alpha3",
 			},
@@ -132,7 +132,7 @@ var (
 			Scope: apiextensionsv1.NamespaceScoped,
 			Names: apiextensionsv1.CustomResourceDefinitionNames{
 				Kind:   "InfrastructureMachine",
-				Plural: "infrastructuremachines",
+				Plural: "genericmachines",
 			},
 			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
 				{
@@ -168,7 +168,7 @@ var (
 			Kind:       "CustomResourceDefinition",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "infrastructuremachinetemplates.infrastructure.cluster.x-k8s.io",
+			Name: "genericmachinetemplates.infrastructure.cluster.x-k8s.io",
 			Labels: map[string]string{
 				clusterv1.GroupVersion.String(): "v1alpha3",
 			},
@@ -178,7 +178,7 @@ var (
 			Scope: apiextensionsv1.NamespaceScoped,
 			Names: apiextensionsv1.CustomResourceDefinitionNames{
 				Kind:   "InfrastructureMachineTemplate",
-				Plural: "infrastructuremachinetemplates",
+				Plural: "genericmachinetemplates",
 			},
 			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
 				{
diff --git a/controllers/remote/cluster_cache_healthcheck_test.go b/controllers/remote/cluster_cache_healthcheck_test.go
index 7f2847d0d..42b32a778 100644
--- a/controllers/remote/cluster_cache_healthcheck_test.go
+++ b/controllers/remote/cluster_cache_healthcheck_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package remote
 
 import (
+	"context"
 	"fmt"
 	"net"
 	"time"
@@ -27,11 +28,11 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/klogr"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/kubeconfig"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 )
 
@@ -68,7 +69,7 @@ var _ = Describe("ClusterCache HealthCheck suite", func() {
 			k8sClient = mgr.GetClient()
 
 			By("Setting up a ClusterCacheTracker")
-			cct, err = NewClusterCacheTracker(klogr.New(), mgr)
+			cct, err = NewClusterCacheTracker(log.NullLogger{}, mgr)
 			Expect(err).NotTo(HaveOccurred())
 
 			By("Creating a namespace for the test")
@@ -108,17 +109,18 @@ var _ = Describe("ClusterCache HealthCheck suite", func() {
 		})
 
 		It("with a healthy cluster", func() {
-			stop := make(chan struct{})
-			defer close(stop)
-
-			go cct.healthCheckCluster(&healthCheckInput{stop, testClusterKey, testEnv.Config, testPollInterval, testPollTimeout, testUnhealthyThreshold, "/"})
+			// Ensure the context times out after the consistently below
+			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+			defer cancel()
+			config := rest.CopyConfig(testEnv.Config)
+			go cct.healthCheckCluster(&healthCheckInput{ctx.Done(), testClusterKey, config, testPollInterval, testPollTimeout, testUnhealthyThreshold, "/"})
 
-			// Make sure this passes for at least two seconds, to give the health check goroutine time to run.
+			// This should succed after 1 second, approx 10 requests to the API
 			Consistently(func() *clusterCache {
 				cct.clusterCachesLock.RLock()
 				defer cct.clusterCachesLock.RUnlock()
 				return cct.clusterCaches[testClusterKey]
-			}, 2*time.Second, 100*time.Millisecond).ShouldNot(BeNil())
+			}).ShouldNot(BeNil())
 			Expect(func() bool {
 				cc.lock.Lock()
 				defer cc.lock.Unlock()
@@ -127,10 +129,10 @@ var _ = Describe("ClusterCache HealthCheck suite", func() {
 		})
 
 		It("with an invalid path", func() {
-			stop := make(chan struct{})
-			defer close(stop)
-
-			go cct.healthCheckCluster(&healthCheckInput{stop, testClusterKey, testEnv.Config, testPollInterval, testPollTimeout, testUnhealthyThreshold, "/foo"})
+			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+			defer cancel()
+			config := rest.CopyConfig(testEnv.Config)
+			go cct.healthCheckCluster(&healthCheckInput{ctx.Done(), testClusterKey, config, testPollInterval, testPollTimeout, testUnhealthyThreshold, "/foo"})
 
 			// This should succeed after N consecutive failed requests.
 			Eventually(func() *clusterCache {
@@ -146,8 +148,9 @@ var _ = Describe("ClusterCache HealthCheck suite", func() {
 		})
 
 		It("with an invalid config", func() {
-			stop := make(chan struct{})
-			defer close(stop)
+			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+			defer cancel()
+			config := rest.CopyConfig(testEnv.Config)
 
 			// Set the host to a random free port on localhost
 			addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
@@ -155,11 +158,9 @@ var _ = Describe("ClusterCache HealthCheck suite", func() {
 			l, err := net.ListenTCP("tcp", addr)
 			Expect(err).ToNot(HaveOccurred())
 			l.Close()
-
-			config := rest.CopyConfig(testEnv.Config)
 			config.Host = fmt.Sprintf("http://127.0.0.1:%d", l.Addr().(*net.TCPAddr).Port)
 
-			go cct.healthCheckCluster(&healthCheckInput{stop, testClusterKey, config, testPollInterval, testPollTimeout, testUnhealthyThreshold, "/"})
+			go cct.healthCheckCluster(&healthCheckInput{ctx.Done(), testClusterKey, config, testPollInterval, testPollTimeout, testUnhealthyThreshold, "/"})
 
 			// This should succeed after N consecutive failed requests.
 			Eventually(func() *clusterCache {
diff --git a/test/framework/bootstrap/kind_util.go b/test/framework/bootstrap/kind_util.go
index 65288e5dc..6839b3bb7 100644
--- a/test/framework/bootstrap/kind_util.go
+++ b/test/framework/bootstrap/kind_util.go
@@ -62,8 +62,6 @@ func CreateKindBootstrapClusterAndLoadImages(ctx context.Context, input CreateKi
 	clusterProvider.Create(ctx)
 	Expect(clusterProvider.GetKubeconfigPath()).To(BeAnExistingFile(), "The kubeconfig file for the kind cluster with name %q does not exists at %q as expected", input.Name, clusterProvider.GetKubeconfigPath())
 
-	log.Logf("The kubeconfig file for the kind cluster is %s", clusterProvider.kubeconfigPath)
-
 	LoadImagesToKindCluster(ctx, LoadImagesToKindClusterInput{
 		Name:   input.Name,
 		Images: input.Images,
diff --git a/test/framework/clusterctl/clusterctl_helpers.go b/test/framework/clusterctl/clusterctl_helpers.go
index c33cbc991..181c16584 100644
--- a/test/framework/clusterctl/clusterctl_helpers.go
+++ b/test/framework/clusterctl/clusterctl_helpers.go
@@ -18,7 +18,6 @@ package clusterctl
 
 import (
 	"context"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -105,7 +104,6 @@ func InitManagementClusterAndWatchControllerLogs(ctx context.Context, input Init
 type ApplyClusterTemplateAndWaitInput struct {
 	ClusterProxy                 framework.ClusterProxy
 	ConfigCluster                ConfigClusterInput
-	CNIManifestPath              string
 	WaitForClusterIntervals      []interface{}
 	WaitForControlPlaneIntervals []interface{}
 	WaitForMachineDeployments    []interface{}
@@ -144,6 +142,8 @@ func ApplyClusterTemplateAndWait(ctx context.Context, input ApplyClusterTemplate
 	log.Logf("Applying the cluster template yaml to the cluster")
 	Expect(input.ClusterProxy.Apply(ctx, workloadClusterTemplate)).ShouldNot(HaveOccurred())
 
+	log.Logf("Installing a CNI plugin to the workload cluster")
+
 	log.Logf("Waiting for the cluster infrastructure to be provisioned")
 	cluster := framework.DiscoveryAndWaitForCluster(ctx, framework.DiscoveryAndWaitForClusterInput{
 		Getter:    input.ClusterProxy.GetClient(),
@@ -157,16 +157,6 @@ func ApplyClusterTemplateAndWait(ctx context.Context, input ApplyClusterTemplate
 		Cluster: cluster,
 	}, input.WaitForControlPlaneIntervals...)
 
-	if input.CNIManifestPath != "" {
-		log.Logf("Installing a CNI plugin to the workload cluster")
-		workloadCluster := input.ClusterProxy.GetWorkloadCluster(context.TODO(), cluster.Namespace, cluster.Name)
-
-		cniYaml, err := ioutil.ReadFile(input.CNIManifestPath)
-		Expect(err).ShouldNot(HaveOccurred())
-
-		Expect(workloadCluster.Apply(context.TODO(), cniYaml)).ShouldNot(HaveOccurred())
-	}
-
 	log.Logf("Waiting for control plane to be ready")
 	framework.WaitForControlPlaneAndMachinesReady(ctx, framework.WaitForControlPlaneAndMachinesReadyInput{
 		GetLister:    input.ClusterProxy.GetClient(),
diff --git a/test/framework/controlplane_helpers.go b/test/framework/controlpane_helpers.go
similarity index 100%
rename from test/framework/controlplane_helpers.go
rename to test/framework/controlpane_helpers.go
diff --git a/test/infrastructure/docker/Dockerfile b/test/infrastructure/docker/Dockerfile
index 078f069e9..81c8d3e87 100644
--- a/test/infrastructure/docker/Dockerfile
+++ b/test/infrastructure/docker/Dockerfile
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.13.15 as builder
+FROM golang:1.13.14 as builder
 
 # Run this with docker build --build_arg goproxy=$(go env GOPROXY) to override the goproxy
 ARG goproxy=https://proxy.golang.org
diff --git a/test/infrastructure/docker/Dockerfile.dev b/test/infrastructure/docker/Dockerfile.dev
index 0355f8e0e..0e3eb7249 100644
--- a/test/infrastructure/docker/Dockerfile.dev
+++ b/test/infrastructure/docker/Dockerfile.dev
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.13.15
+FROM golang:1.13.14
 
 # ALERT ################################################################
 # This is an unusual dockerfile. The expected build context is all of  #
diff --git a/test/infrastructure/docker/Makefile b/test/infrastructure/docker/Makefile
index 0d75fec84..6f7536f0e 100644
--- a/test/infrastructure/docker/Makefile
+++ b/test/infrastructure/docker/Makefile
@@ -209,7 +209,7 @@ release-manifests: $(RELEASE_DIR) ## Builds the manifests to publish with a rele
 .PHONY: release-staging
 release-staging: ## Builds and push container images to the staging bucket.
 	docker pull docker.io/docker/dockerfile:experimental
-	docker pull docker.io/library/golang:1.13.15
+	docker pull docker.io/library/golang:1.13.14
 	docker pull gcr.io/distroless/static:latest
 	REGISTRY=$(STAGING_REGISTRY) $(MAKE) docker-build-all docker-push-all release-alias-tag
 
diff --git a/test/infrastructure/docker/docker/util.go b/test/infrastructure/docker/docker/util.go
index 4e8821a05..145828c19 100644
--- a/test/infrastructure/docker/docker/util.go
+++ b/test/infrastructure/docker/docker/util.go
@@ -39,9 +39,6 @@ func roleLabel(role string) string {
 }
 
 func machineContainerName(cluster, machine string) string {
-	if strings.HasPrefix(machine, cluster) {
-		return machine
-	}
 	return fmt.Sprintf("%s-%s", cluster, machine)
 }
 
diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum
index f9f0da050..089a94487 100644
--- a/test/infrastructure/docker/go.sum
+++ b/test/infrastructure/docker/go.sum
@@ -155,8 +155,6 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A=
-github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
 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/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
diff --git a/util/patch/utils_test.go b/util/patch/utils_test.go
deleted file mode 100644
index 53c581d7f..000000000
--- a/util/patch/utils_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
-Copyright 2020 The Kubernetes 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 patch
-
-import (
-	"testing"
-
-	. "github.com/onsi/gomega"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
-)
-
-func TestToUnstructured(t *testing.T) {
-
-	t.Run("with a typed object", func(t *testing.T) {
-		g := NewWithT(t)
-		// Test with a typed object.
-		obj := &clusterv1.Cluster{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "cluster-1",
-				Namespace: "namespace-1",
-			},
-			Spec: clusterv1.ClusterSpec{
-				Paused: true,
-			},
-		}
-		newObj, err := toUnstructured(obj)
-		g.Expect(err).ToNot(HaveOccurred())
-		g.Expect(newObj.GetName()).To(Equal(obj.Name))
-		g.Expect(newObj.GetNamespace()).To(Equal(obj.Namespace))
-
-		// Change a spec field and validate that it stays the same in the incoming object.
-		g.Expect(unstructured.SetNestedField(newObj.Object, false, "spec", "paused")).To(Succeed())
-		g.Expect(obj.Spec.Paused).To(BeTrue())
-	})
-
-	t.Run("with an unstructured object", func(t *testing.T) {
-		g := NewWithT(t)
-
-		obj := &unstructured.Unstructured{
-			Object: map[string]interface{}{
-				"apiVersion": "test.x.y.z/v1",
-				"metadata": map[string]interface{}{
-					"name":      "test-1",
-					"namespace": "namespace-1",
-				},
-				"spec": map[string]interface{}{
-					"paused": true,
-				},
-			},
-		}
-
-		newObj, err := toUnstructured(obj)
-		g.Expect(err).ToNot(HaveOccurred())
-		g.Expect(newObj.GetName()).To(Equal(obj.GetName()))
-		g.Expect(newObj.GetNamespace()).To(Equal(obj.GetNamespace()))
-
-		// Validate that the maps point to different addresses.
-		g.Expect(obj.Object).ToNot(BeIdenticalTo(newObj.Object))
-
-		// Change a spec field and validate that it stays the same in the incoming object.
-		g.Expect(unstructured.SetNestedField(newObj.Object, false, "spec", "paused")).To(Succeed())
-		pausedValue, _, err := unstructured.NestedBool(obj.Object, "spec", "paused")
-		g.Expect(err).ToNot(HaveOccurred())
-		g.Expect(pausedValue).To(BeTrue())
-
-		// Change the name of the new object and make sure it doesn't change it the old one.
-		newObj.SetName("test-2")
-		g.Expect(obj.GetName()).To(Equal("test-1"))
-	})
-
-}