Repository: code-ready/crc
Pull request #1700 (2020-11-18 17:08:10)

Adding the ability to build CRC with an embedded OKD bundle.
## Proposed changes

1. The Makefile checks for `OKD_VERSION` to be set.  If set, then it
overrides `CRC_VERSION`, and `BUNDLE_VERSION`.  It also sets `pkg/crc/version.okdBuild=true`
2. `start.go` has been modified to use an embedded fake pull secret if
`version.okdBuild` is true.  The pull secret is in `constants.go`
3. Modified the text output in `start.go`
    Changed some of the sentence structure and added text for OKD if an OKD bundle is used.
4. Removed an unneeded reference to: `DefaultOcURLBase` in `constants.go`

## Testing

Successfully tested with an OKD 4.5 bundle.  Needs to be tested with OCP.


centos-ci: Can one of the admins verify this patch?
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/1700#" title="Author self-approved">cgruver</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **cfergeau** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @cfergeau` 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=code-ready%2Fcrc).

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

- **[OWNERS](https://github.com/code-ready/crc/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":["cfergeau"]} -->
openshift-ci-robot: Hi @cgruver. Thanks for your PR.

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

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

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=code-ready%2Fcrc).

<details>

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

gbraad: add to whitelist
guillaumerose: Thanks for this PR! Small remarks but overall it's good. Tests are not happy for the moment, please run make test.
cgruver: Looks like the tests are unhappy because I changed the output text after starting...

I'll look into that.

cgruver: Test should pass now.  I neglected to modify start_test.go.  It's fixed now.
openshift-merge-robot: @cgruver: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | 66c8aaef619ff50223f3c2900677da2da780d58e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/1700/pull-ci-code-ready-crc-master-e2e-crc/1329536266802302976) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=1700). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:cgruver).

<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 -->
cgruver: /test e2e-crc
cgruver: The Travis CI build is failing on the embedded pull secret.

Is there a trick to tell the gosec test that the pull secret is OK?
cfergeau: > The Travis CI build is failing on the embedded pull secret.
> 
> Is there a trick to tell the gosec test that the pull secret is OK?

You can use `// #nosec` annotations. Maybe `// #nosec G101` in this case (inspired by pkg/crc/cluster/cluster.go:
```
// #nosec G101
const vmPullSecretPath = "/var/lib/kubelet/config.json"
```
praveenkumar: With following patch I am able to pass the `make cross-lint` .

```patch
$ git diff
diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go
index 04510710..50d69bef 100644
--- a/cmd/crc/cmd/start.go
+++ b/cmd/crc/cmd/start.go
@@ -193,10 +193,12 @@ func getPullSecretFileContent() (string, error) {
 
        if crcversion.IsOkdBuild() {
                // If crc is built from an OKD bundle, then use the fake pull secret in contants.
-               pullsecret = constants.OkdPullSecret
+               return constants.OkdPullSecret, nil
 
                // In case user doesn't provide a file in start command or in config then ask for it.
-       } else if config.Get(cmdConfig.PullSecretFile).AsString() == "" {
+       }
+
+       if config.Get(cmdConfig.PullSecretFile).AsString() == "" {
                pullsecret, err = input.PromptUserForSecret("Image pull secret", fmt.Sprintf("Copy it from %s", constants.CrcLandingPageURL))
                // This is just to provide a new line after user enter the pull secret.
                fmt.Println()
diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go
index 2c396fb0..a259f173 100644
--- a/pkg/crc/constants/constants.go
+++ b/pkg/crc/constants/constants.go
@@ -21,13 +21,13 @@ const (
 
        CrcEnvPrefix = "CRC"
 
-       DefaultWebConsoleURL = "https://console-openshift-console.apps-crc.testing"
-       DefaultAPIURL        = "https://api.crc.testing:6443"
-       DefaultLogLevel      = "info"
-       ConfigFile           = "crc.json"
-       LogFile              = "crc.log"
-       DaemonLogFile        = "crcd.log"
-       CrcLandingPageURL    = "https://cloud.redhat.com/openshift/install/crc/installer-provisioned" // #nosec G101
+       DefaultWebConsoleURL      = "https://console-openshift-console.apps-crc.testing"
+       DefaultAPIURL             = "https://api.crc.testing:6443"
+       DefaultLogLevel           = "info"
+       ConfigFile                = "crc.json"
+       LogFile                   = "crc.log"
+       DaemonLogFile             = "crcd.log"
+       CrcLandingPageURL         = "https://cloud.redhat.com/openshift/install/crc/installer-provisioned" // #nosec G101
        DefaultPodmanURLBase      = "https://storage.googleapis.com/libpod-master-releases"
        DefaultGoodhostsCliBase   = "https://github.com/code-ready/goodhosts-cli/releases/download/v1.0.0"
        CRCMacTrayDownloadURL     = "https://github.com/code-ready/tray-macos/releases/download/v%s/crc-tray-macos.tar.gz"
@@ -37,7 +37,7 @@ const (
        VSockGateway = "192.168.127.1"
        VsockSSHPort = 2222
 
-       OkdPullSecret = `{"auths":{"fake":{"auth": "Zm9vOmJhcgo="}}}`
+       OkdPullSecret = `{"auths":{"fake":{"auth": "Zm9vOmJhcgo="}}}` // #nosec G101
 )
 
 var podmanURLForOs = map[string]string{
```
cgruver: Thanks @praveenkumar!

I'll apply your patch and re-push the commit.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 9e13186c3..61ee2a35f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,7 @@ before_script:
   - echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_18.04 /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
   - curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_18.04/Release.key | sudo apt-key add -
   - sudo apt-get update -qq
-  - sudo apt-get -qq -y install podman slirp4netns
+  - sudo apt-get -qq -y install podman
 
 script:
 - make
diff --git a/Makefile b/Makefile
index 0b55ad10a..857cdf33f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,17 @@
 SHELL := /bin/bash
 
+
 BUNDLE_VERSION = 4.6.3
 BUNDLE_EXTENSION = crcbundle
 CRC_VERSION = 1.19.0
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
 CONTAINER_RUNTIME ?= podman
 
+ifdef OKD_VERSION
+    BUNDLE_VERSION = $(OKD_VERSION)
+    CRC_VERSION := $(CRC_VERSION)-OKD
+endif
+
 # Go and compilation related variables
 BUILD_DIR ?= out
 SOURCE_DIRS = cmd pkg test
@@ -47,6 +53,10 @@ VERSION_VARIABLES := -X $(REPOPATH)/pkg/crc/version.crcVersion=$(CRC_VERSION) \
     -X $(REPOPATH)/pkg/crc/version.bundleVersion=$(BUNDLE_VERSION) \
 	-X $(REPOPATH)/pkg/crc/version.commitSha=$(COMMIT_SHA)
 
+ifdef OKD_VERSION
+	VERSION_VARIABLES := $(VERSION_VARIABLES) -X $(REPOPATH)/pkg/crc/version.okdBuild=true
+endif
+
 # https://golang.org/cmd/link/
 LDFLAGS := $(VERSION_VARIABLES) -extldflags='-static' -s -w
 
diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go
index bf6518cf4..65bc0f230 100644
--- a/cmd/crc/cmd/start.go
+++ b/cmd/crc/cmd/start.go
@@ -140,14 +140,23 @@ func (s *startResult) prettyPrintTo(writer io.Writer) error {
 	}
 
 	_, err := fmt.Fprintln(writer, strings.Join([]string{
+		"\n",
 		"Started the OpenShift cluster",
 		"",
-		"To access the cluster, first set up your environment by following 'crc oc-env' instructions.",
-		fmt.Sprintf("Then you can access it by running 'oc login -u %s -p %s %s'.", s.ClusterConfig.DeveloperCredentials.Username, s.ClusterConfig.DeveloperCredentials.Password, s.ClusterConfig.URL),
-		fmt.Sprintf("To login as an admin, run 'oc login -u %s -p %s %s'.", s.ClusterConfig.AdminCredentials.Username, s.ClusterConfig.AdminCredentials.Password, s.ClusterConfig.URL),
+		"To access the cluster, first set up your environment by following the instructions returned by executing 'crc oc-env'.",
+		fmt.Sprintf("Then you can access your cluster by running 'oc login -u %s -p %s %s'.", s.ClusterConfig.DeveloperCredentials.Username, s.ClusterConfig.DeveloperCredentials.Password, s.ClusterConfig.URL),
+		fmt.Sprintf("To login as a cluster admin, run 'oc login -u %s -p %s %s'.", s.ClusterConfig.AdminCredentials.Username, s.ClusterConfig.AdminCredentials.Password, s.ClusterConfig.URL),
 		"",
-		"You can now run 'crc console' and use these credentials to access the OpenShift web console.",
+		"You can also run 'crc console' and use the above credentials to access the OpenShift web console.",
+		"The console will open in your default browser.",
 	}, "\n"))
+	if crcversion.IsOkdBuild() {
+		fmt.Fprintln(writer, strings.Join([]string{
+			"\n",
+			"NOTE:",
+			"This cluster was built from OKD - The Community Distribution of Kubernetes that powers Red Hat OpenShift.",
+			"If you find an issue, please report it at https://github.com/openshift/okd"}, "\n"))
+	}
 	return err
 }
 
@@ -182,6 +191,11 @@ func getPullSecretFileContent() (string, error) {
 		err        error
 	)
 
+	// If crc is built from an OKD bundle, then use the fake pull secret in contants.
+	if crcversion.IsOkdBuild() {
+		pullsecret = constants.OkdPullSecret
+		return pullsecret, nil
+	}
 	// In case user doesn't provide a file in start command or in config then ask for it.
 	if config.Get(cmdConfig.PullSecretFile).AsString() == "" {
 		pullsecret, err = input.PromptUserForSecret("Image pull secret", fmt.Sprintf("Copy it from %s", constants.CrcLandingPageURL))
diff --git a/pkg/crc/cache/cache.go b/pkg/crc/cache/cache.go
index 8856b8dc1..d903e8908 100644
--- a/pkg/crc/cache/cache.go
+++ b/pkg/crc/cache/cache.go
@@ -121,7 +121,7 @@ func (c *Cache) CacheExecutable() error {
 
 	// Copy the requested asset into its final destination
 	err = os.MkdirAll(c.destDir, 0750)
-	if err != nil && !os.IsExist(err) {
+	if err != nil {
 		return errors.Wrap(err, "Cannot create the target directory.")
 	}
 
diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go
index edbd3ec5b..4bfe916e9 100644
--- a/pkg/crc/constants/constants.go
+++ b/pkg/crc/constants/constants.go
@@ -21,15 +21,13 @@ const (
 
 	CrcEnvPrefix = "CRC"
 
-	DefaultWebConsoleURL = "https://console-openshift-console.apps-crc.testing"
-	DefaultAPIURL        = "https://api.crc.testing:6443"
-	DefaultLogLevel      = "info"
-	ConfigFile           = "crc.json"
-	LogFile              = "crc.log"
-	DaemonLogFile        = "crcd.log"
-	CrcLandingPageURL    = "https://cloud.redhat.com/openshift/install/crc/installer-provisioned" // #nosec G101
-
-	DefaultOcURLBase          = "https://mirror.openshift.com/pub/openshift-v4/clients/ocp"
+	DefaultWebConsoleURL      = "https://console-openshift-console.apps-crc.testing"
+	DefaultAPIURL             = "https://api.crc.testing:6443"
+	DefaultLogLevel           = "info"
+	ConfigFile                = "crc.json"
+	LogFile                   = "crc.log"
+	DaemonLogFile             = "crcd.log"
+	CrcLandingPageURL         = "https://cloud.redhat.com/openshift/install/crc/installer-provisioned" // #nosec G101
 	DefaultPodmanURLBase      = "https://storage.googleapis.com/libpod-master-releases"
 	DefaultGoodhostsCliBase   = "https://github.com/code-ready/goodhosts-cli/releases/download/v1.0.0"
 	CRCMacTrayDownloadURL     = "https://github.com/code-ready/tray-macos/releases/download/v%s/crc-tray-macos.tar.gz"
@@ -38,6 +36,8 @@ const (
 
 	VSockGateway = "192.168.127.1"
 	VsockSSHPort = 2222
+
+	OkdPullSecret = `{"auths":{"fake":{"auth": "Zm9vOmJhcgo="}}}` // #nosec G101
 )
 
 var podmanURLForOs = map[string]string{
@@ -116,17 +116,9 @@ func GetHomeDir() string {
 	return os.Getenv("HOME")
 }
 
-// EnsureBaseDirectoriesExist create the ~/.crc and ~/.crc/oc dirs if they are not present
+// EnsureBaseDirectoryExists create the ~/.crc directory if it is not present
 func EnsureBaseDirectoriesExist() error {
-	if _, err := os.Stat(CrcBaseDir); err != nil {
-		if !os.IsNotExist(err) {
-			return err
-		}
-		if err := os.Mkdir(CrcBaseDir, 0750); err != nil {
-			return err
-		}
-	}
-	return nil
+	return os.MkdirAll(CrcBaseDir, 0750)
 }
 
 // IsBundleEmbedded returns true if the executable was compiled to contain the bundle
diff --git a/pkg/crc/machine/bundle/metadata.go b/pkg/crc/machine/bundle/metadata.go
index 9bb1ea8c1..4d25908b7 100644
--- a/pkg/crc/machine/bundle/metadata.go
+++ b/pkg/crc/machine/bundle/metadata.go
@@ -98,13 +98,8 @@ func GetCachedBundleInfo(bundleName string) (*CrcBundleInfo, error) {
 func (bundle *CrcBundleInfo) installSymlinkOrCopy() error {
 	ocInBundle := filepath.Join(bundle.cachedPath, constants.OcExecutableName)
 	ocInBinDir := filepath.Join(constants.CrcOcBinDir, constants.OcExecutableName)
-	if _, err := os.Stat(constants.CrcOcBinDir); err != nil {
-		if !os.IsNotExist(err) {
-			return err
-		}
-		if err := os.MkdirAll(constants.CrcOcBinDir, 0750); err != nil {
-			return err
-		}
+	if err := os.MkdirAll(constants.CrcOcBinDir, 0750); err != nil {
+		return err
 	}
 	_ = os.Remove(ocInBinDir)
 	if runtime.GOOS == "windows" {
diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go
index 07a878ea2..ab7e51455 100644
--- a/pkg/crc/machine/machine.go
+++ b/pkg/crc/machine/machine.go
@@ -269,7 +269,11 @@ func (client *client) Start(startConfig StartConfig) (*StartResult, error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "Error getting the IP")
 	}
-	sshRunner := crcssh.CreateRunner(instanceIP, getSSHPort(client.useVSock()), crcBundleMetadata.GetSSHKeyPath(), constants.GetPrivateKeyPath())
+	sshRunner, err := crcssh.CreateRunner(instanceIP, getSSHPort(client.useVSock()), crcBundleMetadata.GetSSHKeyPath(), constants.GetPrivateKeyPath())
+	if err != nil {
+		return nil, errors.Wrap(err, "Error creating the ssh client")
+	}
+	defer sshRunner.Close()
 
 	logging.Debug("Waiting until ssh is available")
 	if err := cluster.WaitForSSH(sshRunner); err != nil {
@@ -602,7 +606,11 @@ func (client *client) Status() (*ClusterStatusResult, error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "Error getting ip")
 	}
-	sshRunner := crcssh.CreateRunner(ip, getSSHPort(client.useVSock()), constants.GetPrivateKeyPath())
+	sshRunner, err := crcssh.CreateRunner(ip, getSSHPort(client.useVSock()), constants.GetPrivateKeyPath())
+	if err != nil {
+		return nil, errors.Wrap(err, "Error creating the ssh client")
+	}
+	defer sshRunner.Close()
 	// check if all the clusteroperators are running
 	diskSize, diskUse, err := cluster.GetRootPartitionUsage(sshRunner)
 	if err != nil {
diff --git a/pkg/crc/preflight/preflight_checks_common.go b/pkg/crc/preflight/preflight_checks_common.go
index a66cddcee..3a21230be 100644
--- a/pkg/crc/preflight/preflight_checks_common.go
+++ b/pkg/crc/preflight/preflight_checks_common.go
@@ -9,6 +9,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/cache"
 	"github.com/code-ready/crc/pkg/crc/constants"
 	"github.com/code-ready/crc/pkg/crc/logging"
+	"github.com/code-ready/crc/pkg/crc/machine/bundle"
 	"github.com/code-ready/crc/pkg/crc/validation"
 	"github.com/code-ready/crc/pkg/embed"
 	"github.com/docker/go-units"
@@ -30,11 +31,11 @@ var genericPreflightChecks = [...]Check{
 		fix:              fixGoodhostsExecutableCached,
 	},
 	{
-		configKeySuffix:  "check-bundle-cached",
-		checkDescription: "Checking if CRC bundle is cached in '$HOME/.crc'",
-		check:            checkBundleCached,
-		fixDescription:   "Unpacking bundle from the CRC executable",
-		fix:              fixBundleCached,
+		configKeySuffix:  "check-bundle-extracted",
+		checkDescription: "Checking if CRC bundle is extracted in '$HOME/.crc'",
+		check:            checkBundleExtracted,
+		fixDescription:   "Extracting bundle from the CRC executable",
+		fix:              fixBundleExtracted,
 		flags:            SetupOnly,
 	},
 	{
@@ -48,7 +49,7 @@ var genericPreflightChecks = [...]Check{
 	},
 }
 
-func checkBundleCached() error {
+func checkBundleExtracted() error {
 	if !constants.BundleEmbedded() {
 		return nil
 	}
@@ -58,7 +59,7 @@ func checkBundleCached() error {
 	return nil
 }
 
-func fixBundleCached() error {
+func fixBundleExtracted() error {
 	// Should be removed after 1.19 release
 	// This check will ensure correct mode for `~/.crc/cache` directory
 	// in case it exists.
@@ -67,12 +68,15 @@ func fixBundleCached() error {
 	}
 	if constants.BundleEmbedded() {
 		bundleDir := filepath.Dir(constants.DefaultBundlePath)
-		err := os.MkdirAll(bundleDir, 0775)
-		if err != nil && !os.IsExist(err) {
-			return fmt.Errorf("Cannot create directory %s", bundleDir)
+		if err := os.MkdirAll(bundleDir, 0775); err != nil {
+			return fmt.Errorf("Cannot create directory %s: %v", bundleDir, err)
 		}
 
-		return embed.Extract(filepath.Base(constants.DefaultBundlePath), constants.DefaultBundlePath)
+		if err := embed.Extract(filepath.Base(constants.DefaultBundlePath), constants.DefaultBundlePath); err != nil {
+			return err
+		}
+		_, err := bundle.Extract(constants.DefaultBundlePath)
+		return err
 	}
 	return fmt.Errorf("CRC bundle is not embedded in the executable")
 }
diff --git a/pkg/crc/preflight/preflight_checks_tray_darwin.go b/pkg/crc/preflight/preflight_checks_tray_darwin.go
index 5ea5b60cc..05961eee0 100644
--- a/pkg/crc/preflight/preflight_checks_tray_darwin.go
+++ b/pkg/crc/preflight/preflight_checks_tray_darwin.go
@@ -207,7 +207,7 @@ func downloadOrExtractTrayApp() error {
 	archivePath := filepath.Join(tmpArchivePath, filepath.Base(constants.GetCRCMacTrayDownloadURL()))
 	outputPath := constants.CrcBinDir
 	err = goos.MkdirAll(outputPath, 0750)
-	if err != nil && !goos.IsExist(err) {
+	if err != nil {
 		return errors.Wrap(err, "Cannot create the target directory.")
 	}
 	_, err = extract.Uncompress(archivePath, outputPath, false)
diff --git a/pkg/crc/ssh/client.go b/pkg/crc/ssh/client.go
index d75154bda..1cd5c6023 100644
--- a/pkg/crc/ssh/client.go
+++ b/pkg/crc/ssh/client.go
@@ -2,7 +2,6 @@ package ssh
 
 import (
 	"fmt"
-	"io"
 	"io/ioutil"
 	"net"
 	"strconv"
@@ -14,46 +13,43 @@ import (
 
 type Client interface {
 	Output(command string) (string, error)
+	Close()
 }
 
 type NativeClient struct {
-	Config   ssh.ClientConfig
+	User     string
 	Hostname string
 	Port     int
-}
+	Keys     []string
 
-type Auth struct {
-	Keys []string
+	conn *ssh.Client
 }
 
-func NewClient(user string, host string, port int, auth *Auth) (Client, error) {
-	config, err := NewNativeConfig(user, auth)
-	if err != nil {
-		return nil, fmt.Errorf("Error getting config for native Go SSH: %s", err)
-	}
-
+func NewClient(user string, host string, port int, keys ...string) (Client, error) {
 	return &NativeClient{
-		Config:   config,
+		User:     user,
 		Hostname: host,
 		Port:     port,
+		Keys:     keys,
 	}, nil
 }
 
-func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
+func clientConfig(user string, keys []string) (*ssh.ClientConfig, error) {
 	var (
 		privateKeys []ssh.Signer
 		authMethods []ssh.AuthMethod
 	)
 
-	for _, k := range auth.Keys {
+	for _, k := range keys {
 		key, err := ioutil.ReadFile(k)
 		if err != nil {
-			return ssh.ClientConfig{}, err
+			log.Debugf("Cannot read private ssh key %s", k)
+			continue
 		}
 
 		privateKey, err := ssh.ParsePrivateKey(key)
 		if err != nil {
-			return ssh.ClientConfig{}, err
+			return nil, err
 		}
 
 		privateKeys = append(privateKeys, privateKey)
@@ -63,7 +59,7 @@ func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
 		authMethods = append(authMethods, ssh.PublicKeys(privateKeys...))
 	}
 
-	return ssh.ClientConfig{
+	return &ssh.ClientConfig{
 		User: user,
 		Auth: authMethods,
 		// #nosec G106
@@ -72,25 +68,30 @@ func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
 	}, nil
 }
 
-func (client *NativeClient) session() (*ssh.Client, *ssh.Session, error) {
-	conn, err := ssh.Dial("tcp", net.JoinHostPort(client.Hostname, strconv.Itoa(client.Port)), &client.Config)
-	if err != nil {
-		return nil, nil, err
+func (client *NativeClient) session() (*ssh.Session, error) {
+	if client.conn == nil {
+		var err error
+		config, err := clientConfig(client.User, client.Keys)
+		if err != nil {
+			return nil, fmt.Errorf("Error getting config for native Go SSH: %s", err)
+		}
+		client.conn, err = ssh.Dial("tcp", net.JoinHostPort(client.Hostname, strconv.Itoa(client.Port)), config)
+		if err != nil {
+			return nil, err
+		}
 	}
-	session, err := conn.NewSession()
+	session, err := client.conn.NewSession()
 	if err != nil {
-		_ = conn.Close()
-		return nil, nil, err
+		return nil, err
 	}
-	return conn, session, err
+	return session, err
 }
 
 func (client *NativeClient) Output(command string) (string, error) {
-	conn, session, err := client.session()
+	session, err := client.session()
 	if err != nil {
 		return "", err
 	}
-	defer closeConn(conn)
 	defer session.Close()
 
 	output, err := session.CombinedOutput(command)
@@ -100,8 +101,11 @@ func (client *NativeClient) Output(command string) (string, error) {
 	return string(output), nil
 }
 
-func closeConn(c io.Closer) {
-	err := c.Close()
+func (client *NativeClient) Close() {
+	if client.conn == nil {
+		return
+	}
+	err := client.conn.Close()
 	if err != nil {
 		log.Debugf("Error closing SSH Client: %s", err)
 	}
diff --git a/pkg/crc/ssh/ssh.go b/pkg/crc/ssh/ssh.go
index 13fc20e2c..170ae4400 100644
--- a/pkg/crc/ssh/ssh.go
+++ b/pkg/crc/ssh/ssh.go
@@ -13,17 +13,21 @@ import (
 )
 
 type Runner struct {
-	ip             string
-	port           int
-	privateSSHKeys []string
+	client Client
 }
 
-func CreateRunner(ip string, port int, privateKeys ...string) *Runner {
-	return &Runner{
-		ip:             ip,
-		port:           port,
-		privateSSHKeys: privateKeys,
+func CreateRunner(ip string, port int, privateKeys ...string) (*Runner, error) {
+	client, err := NewClient(constants.DefaultSSHUser, ip, port, privateKeys...)
+	if err != nil {
+		return nil, err
 	}
+	return &Runner{
+		client: client,
+	}, nil
+}
+
+func (runner *Runner) Close() {
+	runner.client.Close()
 }
 
 // Create a host using the driver's config
@@ -53,27 +57,13 @@ func (runner *Runner) CopyFile(srcFilename string, destFilename string, mode os.
 }
 
 func (runner *Runner) runSSHCommandFromDriver(command string, runPrivate bool) (string, error) {
-	var availableKeys []string
-	for _, privateKey := range runner.privateSSHKeys {
-		if _, err := os.Stat(privateKey); err == nil {
-			availableKeys = append(availableKeys, privateKey)
-		}
-	}
-
-	client, err := NewClient(constants.DefaultSSHUser, runner.ip, runner.port, &Auth{
-		Keys: availableKeys,
-	})
-	if err != nil {
-		return "", err
-	}
-
 	if runPrivate {
 		logging.Debugf("About to run SSH command with hidden output")
 	} else {
 		logging.Debugf("About to run SSH command:\n%s", command)
 	}
 
-	output, err := client.Output(command)
+	output, err := runner.client.Output(command)
 	if runPrivate {
 		if err != nil {
 			logging.Debugf("SSH command failed")
diff --git a/pkg/crc/version/version.go b/pkg/crc/version/version.go
index 1cd3c05fd..b2a3ae1a3 100644
--- a/pkg/crc/version/version.go
+++ b/pkg/crc/version/version.go
@@ -20,6 +20,8 @@ var (
 
 	// Bundle version which used for the release.
 	bundleVersion = "0.0.0-unset"
+
+	okdBuild = "false"
 )
 
 const (
@@ -48,6 +50,10 @@ func GetBundleVersion() string {
 	return bundleVersion
 }
 
+func IsOkdBuild() bool {
+	return okdBuild == "true"
+}
+
 func GetCRCMacTrayVersion() string {
 	return crcMacTrayVersion
 }
diff --git a/pkg/extract/extract.go b/pkg/extract/extract.go
index 26e798ba7..592c648dd 100644
--- a/pkg/extract/extract.go
+++ b/pkg/extract/extract.go
@@ -115,10 +115,8 @@ func untar(reader io.Reader, targetDir string, fileFilter func(string) bool, sho
 
 		// if its a dir and it doesn't exist create it
 		case tar.TypeDir:
-			if _, err := os.Stat(path); err != nil {
-				if err := os.MkdirAll(path, header.FileInfo().Mode()); err != nil {
-					return nil, err
-				}
+			if err := os.MkdirAll(path, header.FileInfo().Mode()); err != nil {
+				return nil, err
 			}
 
 		// if it's a file create it


Test patch:
diff --git a/cmd/crc/cmd/start_test.go b/cmd/crc/cmd/start_test.go
index a8b318448..2810b1190 100644
--- a/cmd/crc/cmd/start_test.go
+++ b/cmd/crc/cmd/start_test.go
@@ -24,13 +24,16 @@ func TestRenderActionPlainSuccess(t *testing.T) {
 			},
 		},
 	}, out, ""))
-	assert.Equal(t, `Started the OpenShift cluster
+	assert.Equal(t, `
 
-To access the cluster, first set up your environment by following 'crc oc-env' instructions.
-Then you can access it by running 'oc login -u developer -p developer https://api.crc.testing:6443'.
-To login as an admin, run 'oc login -u kubeadmin -p secret https://api.crc.testing:6443'.
+Started the OpenShift cluster
 
-You can now run 'crc console' and use these credentials to access the OpenShift web console.
+To access the cluster, first set up your environment by following the instructions returned by executing 'crc oc-env'.
+Then you can access your cluster by running 'oc login -u developer -p developer https://api.crc.testing:6443'.
+To login as a cluster admin, run 'oc login -u kubeadmin -p secret https://api.crc.testing:6443'.
+
+You can also run 'crc console' and use the above credentials to access the OpenShift web console.
+The console will open in your default browser.
 `, out.String())
 }
 
diff --git a/pkg/crc/ssh/ssh_test.go b/pkg/crc/ssh/ssh_test.go
index 9ef829b56..96bf698cc 100644
--- a/pkg/crc/ssh/ssh_test.go
+++ b/pkg/crc/ssh/ssh_test.go
@@ -36,7 +36,7 @@ func TestRunner(t *testing.T) {
 	require.NoError(t, err)
 	defer listener.Close()
 
-	createSSHServer(t, listener, clientKey, func(input string) (byte, string) {
+	totalConn := createSSHServer(t, listener, clientKey, func(input string) (byte, string) {
 		escaped := fmt.Sprintf("%q", input)
 		if escaped == `"echo hello"` {
 			return 0, "hello"
@@ -48,7 +48,9 @@ func TestRunner(t *testing.T) {
 	})
 
 	addr := listener.Addr().String()
-	runner := CreateRunner(ipFor(addr), portFor(addr), clientKeyFile)
+	runner, err := CreateRunner(ipFor(addr), portFor(addr), clientKeyFile)
+	assert.NoError(t, err)
+	defer runner.Close()
 
 	bin, err := runner.Run("echo hello")
 	assert.NoError(t, err)
@@ -60,9 +62,12 @@ func TestRunner(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "hello", stdout)
 	assert.Empty(t, stderr)
+
+	assert.Equal(t, 1, *totalConn)
 }
 
-func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.PrivateKey, fun func(string) (byte, string)) {
+func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.PrivateKey, fun func(string) (byte, string)) *int {
+	totalConn := 0
 	config := &ssh.ServerConfig{
 		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
 			pub, err := ssh.NewPublicKey(&clientKey.PublicKey)
@@ -93,6 +98,7 @@ func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.Private
 				logrus.Debugf("cannot accept connection: %v", err)
 				return
 			}
+			totalConn++
 
 			conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
 			require.NoError(t, err)
@@ -126,6 +132,7 @@ func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.Private
 			}
 		}
 	}()
+	return &totalConn
 }
 
 func writePrivateKey(t *testing.T, clientKeyFile string, clientKey *rsa.PrivateKey) {
diff --git a/test/integration/crcsuite/collect.go b/test/integration/crcsuite/collect.go
index 9d1ace498..38e7050cb 100644
--- a/test/integration/crcsuite/collect.go
+++ b/test/integration/crcsuite/collect.go
@@ -182,9 +182,7 @@ func (collector *VMCommandCollector) Collect(w Writer) error {
 	if err != nil {
 		return err
 	}
-	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, &ssh.Auth{
-		Keys: []string{constants.GetPrivateKeyPath()},
-	})
+	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, constants.GetPrivateKeyPath())
 	if err != nil {
 		return err
 	}
@@ -205,9 +203,7 @@ func (collector *ContainerLogCollector) Collect(w Writer) error {
 	if err != nil {
 		return err
 	}
-	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, &ssh.Auth{
-		Keys: []string{constants.GetPrivateKeyPath()},
-	})
+	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, constants.GetPrivateKeyPath())
 	if err != nil {
 		return err
 	}
diff --git a/test/integration/features/basic.feature b/test/integration/features/basic.feature
index 07185795d..049413e7b 100644
--- a/test/integration/features/basic.feature
+++ b/test/integration/features/basic.feature
@@ -37,7 +37,7 @@ Feature: Basic test
     @linux
     Scenario: CRC setup on Linux
         When executing "crc setup" succeeds
-        And stderr should contain "Checking if CRC bundle is cached in '$HOME/.crc'"
+        And stderr should contain "Checking if CRC bundle is extracted in '$HOME/.crc'"
         And stderr should contain "Checking if running as non-root"
         And stderr should contain "Checking if Virtualization is enabled"
         And stderr should contain "Checking if KVM is enabled"
diff --git a/test/integration/features/cert_rotation.feature b/test/integration/features/cert_rotation.feature
index e1ca76cae..0cacde1d1 100644
--- a/test/integration/features/cert_rotation.feature
+++ b/test/integration/features/cert_rotation.feature
@@ -18,7 +18,8 @@ Feature: Check the cert is rotation happen after it expire
       Then login to the oc cluster succeeds
 
     Scenario: Set clock back to original time
-      Given executing "sudo timedatectl set-ntp on" succeeds
+      When executing "sudo date -s '-3 month'" succeeds
+      And executing "sudo timedatectl set-ntp on" succeeds
 
     Scenario: CRC delete and cleanup
       When executing "crc delete -f" succeeds
diff --git a/test/integration/features/config.feature b/test/integration/features/config.feature
index 9599931ad..155d353c8 100644
--- a/test/integration/features/config.feature
+++ b/test/integration/features/config.feature
@@ -67,7 +67,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @darwin
         Examples: Config warnings on Mac
             | property                             | value1 | value2 |
-            | warn-check-bundle-cached             | true   | false  |
+            | warn-check-bundle-extracted          | true   | false  |
             | warn-check-hosts-file-permissions    | true   | false  |
             | warn-check-hyperkit-driver           | true   | false  |
             | warn-check-hyperkit-installed        | true   | false  |
@@ -77,7 +77,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @linux
         Examples: Config warnings on Linux
             | property                             | value1 | value2 |
-            | warn-check-bundle-cached             | true   | false  |
+            | warn-check-bundle-extracted          | true   | false  |
             | warn-check-crc-dnsmasq-file          | true   | false  |
             | warn-check-crc-network               | true   | false  |
             | warn-check-crc-network-active        | true   | false  |
@@ -97,7 +97,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         Examples: Config warnings on Windows
             | property                        | value1 | value2 |
             | warn-check-administrator-user   | true   | false  |
-            | warn-check-bundle-cached        | true   | false  |
+            | warn-check-bundle-extracted     | true   | false  |
             | warn-check-hyperv-installed     | true   | false  |
             | warn-check-hyperv-switch        | true   | false  |
             | warn-check-user-in-hyperv-group | true   | false  |
@@ -120,7 +120,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @darwin
         Examples:
             | property                             | value1 | value2 |
-            | skip-check-bundle-cached             | true   | false  |
+            | skip-check-bundle-extracted          | true   | false  |
             | skip-check-hosts-file-permissions    | true   | false  |
             | skip-check-hyperkit-driver           | true   | false  |
             | skip-check-hyperkit-installed        | true   | false  |
@@ -130,7 +130,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @linux
         Examples:
             | property                             | value1 | value2 |
-            | skip-check-bundle-cached             | true   | false  |
+            | skip-check-bundle-extracted          | true   | false  |
             | skip-check-crc-dnsmasq-file          | true   | false  |
             | skip-check-crc-network               | true   | false  |
             | skip-check-crc-network-active        | true   | false  |
@@ -150,7 +150,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         Examples:
             | property                        | value1 | value2 |
             | skip-check-administrator-user   | true   | false  |
-            | skip-check-bundle-cached        | true   | false  |
+            | skip-check-bundle-extracted     | true   | false  |
             | skip-check-hyperv-installed     | true   | false  |
             | skip-check-hyperv-switch        | true   | false  |
             | skip-check-user-in-hyperv-group | true   | false  |