Repository: redhat-certification/chart-verifier
Pull request #221 (2021-12-03 19:37:59)

binaries: remove oc, kubectl, helm binaries as dependencies
- [x] Removes oc binary as a dependency, use k8s go client instead.
- [x] Remove kubectl binary as a dependency, use k8s go client instead.
- [x] Remove helm binary as a dependency, use Helm go client instead.
- [x] Merge kubectl.go and oc.go since they are using the same k8s clientset.

Closes:
- https://issues.redhat.com/browse/HELM-251
- https://issues.redhat.com/browse/HELM-271
- https://issues.redhat.com/browse/HELM-272

Signed-off-by: Allen Bai <abai@redhat.com>

zonggen: Local `make` and `make test` passed for me. Ready for review.
zonggen: Trying to test with real charts, got null pointer deref error, fixing

Patch:
diff --git a/Dockerfile b/Dockerfile
index 0a3e7b2..454cb0c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,18 +19,12 @@ RUN go mod download
 
 COPY . .
 
-RUN ./hack/get-oc.sh
-
-RUN ./hack/get-helm.sh
-
-RUN ./hack/build.sh
+RUN go build -o ./out/chart-verifier main.go
 
 FROM registry.access.redhat.com/ubi8/ubi-minimal
 
 COPY --from=build /tmp/src/out/chart-verifier /app/chart-verifier
 
-COPY --from=build /usr/local/bin/* /usr/local/bin/
-
 COPY ./config /app/config
 
 COPY cmd/release /app/releases
diff --git a/Makefile b/Makefile
index 7391fc6..5a254cd 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ gofmt:
 
 .PHONY: bin
 bin:
-	 hack/build.sh
+	 go build -o ./out/chart-verifier main.go
 
 .PHONY: test
 test:
diff --git a/go.mod b/go.mod
index 0f31624..e69234f 100644
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,13 @@ require (
 	helm.sh/helm/v3 v3.7.1
 )
 
+require (
+	k8s.io/apimachinery v0.22.1
+	k8s.io/client-go v0.22.1
+	k8s.io/helm v2.17.0+incompatible
+	k8s.io/kubectl v0.22.1
+)
+
 require (
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/BurntSushi/toml v0.3.1 // indirect
@@ -48,6 +55,7 @@ require (
 	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
 	github.com/fatih/color v1.10.0 // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
+	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/go-errors/errors v1.0.1 // indirect
 	github.com/go-logr/logr v0.4.0 // indirect
 	github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -139,14 +147,11 @@ require (
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/api v0.22.1 // indirect
 	k8s.io/apiextensions-apiserver v0.22.1 // indirect
-	k8s.io/apimachinery v0.22.1 // indirect
 	k8s.io/apiserver v0.22.1 // indirect
 	k8s.io/cli-runtime v0.22.1 // indirect
-	k8s.io/client-go v0.22.1 // indirect
 	k8s.io/component-base v0.22.1 // indirect
 	k8s.io/klog/v2 v2.9.0 // indirect
 	k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
-	k8s.io/kubectl v0.22.1 // indirect
 	k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 // indirect
 	oras.land/oras-go v0.4.0 // indirect
 	sigs.k8s.io/kustomize/api v0.8.11 // indirect
diff --git a/go.sum b/go.sum
index 6c84a90..ea1a766 100644
--- a/go.sum
+++ b/go.sum
@@ -434,6 +434,7 @@ github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwL
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
@@ -1781,6 +1782,8 @@ k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
 k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao=
+k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
 k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
diff --git a/hack/build.sh b/hack/build.sh
deleted file mode 100755
index a785883..0000000
--- a/hack/build.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright 2021 Red Hat
-#
-# 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.
-#
-
-go build -o ./out/chart-verifier main.go
diff --git a/hack/get-helm.sh b/hack/get-helm.sh
deleted file mode 100755
index b2c40b6..0000000
--- a/hack/get-helm.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-
-HELM_VERSION="${HELM_VERSION:-3.6.2}"
-DOWNLOAD_DIR=${DOWNLOAD_DIR:-/tmp/chart-verifier}
-INSTALL_DIR=${INSTALL_DIR:-/usr/local/bin}
-mkdir -p "${DOWNLOAD_DIR}"
-pushd "${DOWNLOAD_DIR}"
-curl -O "https://mirror.openshift.com/pub/openshift-v4/clients/helm/${HELM_VERSION}/helm-linux-amd64.tar.gz"
-tar xfz helm-linux-amd64.tar.gz
-mkdir -p "${INSTALL_DIR}"
-mv "${DOWNLOAD_DIR}/helm" "${INSTALL_DIR}/helm"
-popd
diff --git a/hack/get-oc.sh b/hack/get-oc.sh
deleted file mode 100755
index 10ce16f..0000000
--- a/hack/get-oc.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-curl -O https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/4.7.9/openshift-client-linux-4.7.9.tar.gz
-mkdir -p /usr/local/bin
-tar xfz  openshift-client-linux-4.7.9.tar.gz -C /usr/local/bin
diff --git a/pkg/tool/exec.go b/pkg/tool/exec.go
index eda2790..5cb8350 100644
--- a/pkg/tool/exec.go
+++ b/pkg/tool/exec.go
@@ -56,3 +56,11 @@ func (p ProcessExecutor) RunProcessInDirAndCaptureOutput(
 	}
 	return capturedOutput, nil
 }
+
+func toStringArray(args []interface{}) []string {
+	cpy := make([]string, len(args))
+	for i, a := range args {
+		cpy[i] = fmt.Sprint(a)
+	}
+	return cpy
+}
diff --git a/pkg/tool/helm.go b/pkg/tool/helm.go
index 5d9202e..918594e 100644
--- a/pkg/tool/helm.go
+++ b/pkg/tool/helm.go
@@ -2,82 +2,165 @@ package tool
 
 import (
 	"fmt"
+	"os"
+	"time"
 
-	"github.com/helm/chart-testing/v3/pkg/tool"
+	"helm.sh/helm/v3/pkg/action"
+	"helm.sh/helm/v3/pkg/chart/loader"
+	"helm.sh/helm/v3/pkg/cli"
+	"helm.sh/helm/v3/pkg/cli/values"
+	"helm.sh/helm/v3/pkg/getter"
+	"k8s.io/helm/pkg/strvals"
 )
 
-// Helm is an interface to the helm binary; it is a thin layer on top of the Helm abstraction offered by chart-testing
-// to silence output being streamed to Stdout.
 type Helm struct {
-	tool.Helm
-	ProcessExecutor
-	extraArgs []string
+	config      *action.Configuration
+	envSettings *cli.EnvSettings
+	args        map[string]interface{}
 }
 
-func NewHelm(exec ProcessExecutor, extraArgs []string) Helm {
-	return Helm{
-		tool.NewHelm(exec.ProcessExecutor, extraArgs),
-		exec,
-		extraArgs,
+func NewHelm(envSettings *cli.EnvSettings, args map[string]interface{}) (*Helm, error) {
+	helm := &Helm{envSettings: envSettings, args: args}
+	config := new(action.Configuration)
+	if err := config.Init(envSettings.RESTClientGetter(), envSettings.Namespace(), os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
+		LogInfo(fmt.Sprintf(format, v))
+	}); err != nil {
+		return nil, err
 	}
+	helm.config = config
+	return helm, nil
 }
 
-func toStringArray(args []interface{}) []string {
-	copy := make([]string, len(args))
-	for i, a := range args {
-		copy[i] = fmt.Sprint(a)
-	}
-	return copy
-}
+func (h Helm) Install(namespace, chart, release, valuesFile string) error {
+	LogInfo(fmt.Sprintf("Execute helm install. namespace: %s, release: %s chart: %s", namespace, release, chart))
+	client := action.NewInstall(h.config)
+	client.Namespace = namespace
+	client.ReleaseName = release
+	client.Wait = true
+	// default timeout duration
+	// ref: https://helm.sh/docs/helm/helm_install
+	client.Timeout = 5 * time.Minute
 
-func toInterfaceArray(args []string) []interface{} {
-	copy := make([]interface{}, len(args))
-	for i, a := range args {
-		copy[i] = a
+	cp, err := client.ChartPathOptions.LocateChart(chart, h.envSettings)
+	if err != nil {
+		LogError(fmt.Sprintf("Error LocateChart: %v", err))
+		return err
 	}
-	return copy
-}
 
-// InstallWithValues overrides chart-testing's tool.Helm method to execute the modified RunProcessAndCaptureOutput
-// method.
-func (h Helm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error {
-	var values []interface{}
+	p := getter.All(h.envSettings)
+	valueOpts := &values.Options{}
 	if valuesFile != "" {
-		values = []interface{}{"--values", valuesFile}
+		valueOpts.ValueFiles = append(valueOpts.ValueFiles, valuesFile)
+	}
+	vals, err := valueOpts.MergeValues(p)
+	if err != nil {
+		LogError(fmt.Sprintf("Error MergeValues: %v", err))
+		return err
 	}
 
-	LogInfo(fmt.Sprintf("Execute helm install. namespace: %s, release: %s chart: %s", namespace, release, chart))
-	helmArgs := []interface{}{"install", release, chart, "--namespace", namespace, "--wait"}
-	helmArgs = append(helmArgs, values...)
-	helmArgs = append(helmArgs, toInterfaceArray(h.extraArgs)...)
+	if val, ok := h.args["set"]; ok {
+		if err := strvals.ParseInto(fmt.Sprintf("%v", val), vals); err != nil {
+			LogError(fmt.Sprintf("Error parsing --set values: %v", err))
+			return err
+		}
+	}
 
-	_, err := h.RunProcessAndCaptureOutput("helm", helmArgs...)
+	if val, ok := h.args["set-file"]; ok {
+		if err := strvals.ParseInto(fmt.Sprintf("%v", val), vals); err != nil {
+			LogError(fmt.Sprintf("Error parsing --set-file values: %v", err))
+			return err
+		}
+	}
+
+	if val, ok := h.args["set-string"]; ok {
+		if err := strvals.ParseInto(fmt.Sprintf("%v", val), vals); err != nil {
+			LogError(fmt.Sprintf("Error parsing --set-string values: %v", err))
+			return err
+		}
+	}
+
+	c, err := loader.Load(cp)
 	if err != nil {
-		LogError(fmt.Sprintf("Execute helm install. error %v", err))
-	} else {
-		LogInfo("Helm install complete")
+		LogError(fmt.Sprintf("Error loading chart path: %v", err))
+		return err
 	}
 
-	return err
+	// TODO: support other options if required
+	_, err = client.Run(c, vals)
+	if err != nil {
+		LogError(fmt.Sprintf("Error running chart install: %v", err))
+		return err
+	}
+
+	LogInfo("Helm install complete")
+	return nil
 }
 
-func (h Helm) Test(namespace string, release string) error {
-	LogInfo(fmt.Sprintf("Execute helm test. namespace: %s, release: %s, extraArgd: %v", namespace, release, h.extraArgs))
-	_, err := h.RunProcessAndCaptureOutput("helm", "test", release, "--namespace", namespace, h.extraArgs)
+func (h Helm) Test(namespace, release string) error {
+	LogInfo(fmt.Sprintf("Execute helm test. namespace: %s, release: %s, args: %+v", namespace, release, h.args))
+	client := action.NewReleaseTesting(h.config)
+	client.Namespace = namespace
+
+	// TODO: support filter and timeout options if required
+	_, err := client.Run(release)
 	if err != nil {
 		LogError(fmt.Sprintf("Execute helm test. error %v", err))
-	} else {
-		LogInfo("Helm test complete")
+		return err
 	}
-	return err
+
+	LogInfo("Helm test complete")
+	return nil
 }
 
-func (h Helm) DeleteRelease(namespace string, release string) {
+func (h Helm) Uninstall(namespace, release string) error {
 	LogInfo(fmt.Sprintf("Execute helm uninstall. namespace: %s, release: %s", namespace, release))
-	_, err := h.RunProcessAndCaptureOutput("helm", "uninstall", release, "--namespace", namespace, h.extraArgs)
+	client := action.NewUninstall(h.config)
+	// TODO: support other options if required
+	_, err := client.Run(release)
+
 	if err != nil {
 		LogError(fmt.Sprintf("Error from helm uninstall : %v", err))
-	} else {
-		LogInfo("Delete release complete")
+		return err
 	}
+
+	LogInfo("Delete release complete")
+	return nil
+}
+
+func (h Helm) Upgrade(namespace, chart, release string) error {
+	LogInfo(fmt.Sprintf("Execute helm upgrade. namespace: %s, release: %s chart: %s", namespace, release, chart))
+	client := action.NewUpgrade(h.config)
+	client.Namespace = namespace
+	client.ReuseValues = true
+	client.Wait = true
+
+	cp, err := client.ChartPathOptions.LocateChart(chart, h.envSettings)
+	if err != nil {
+		LogError(fmt.Sprintf("Error LocateChart: %v", err))
+		return err
+	}
+
+	p := getter.All(h.envSettings)
+	valueOpts := &values.Options{}
+	vals, err := valueOpts.MergeValues(p)
+	if err != nil {
+		LogError(fmt.Sprintf("Error MergeValues: %v", err))
+		return err
+	}
+
+	c, err := loader.Load(cp)
+	if err != nil {
+		LogError(fmt.Sprintf("Error loading chart path: %v", err))
+		return err
+	}
+
+	// TODO: support other options if required
+	_, err = client.Run(release, c, vals)
+	if err != nil {
+		LogError(fmt.Sprintf("Error running chart upgrade: %v", err))
+		return err
+	}
+
+	LogInfo("Helm upgrade complete")
+	return nil
 }
diff --git a/pkg/tool/kubectl.go b/pkg/tool/kubectl.go
index 7736699..84a0ce3 100644
--- a/pkg/tool/kubectl.go
+++ b/pkg/tool/kubectl.go
@@ -1,55 +1,100 @@
 package tool
 
 import (
+	"context"
 	"fmt"
-	"strings"
 
-	"github.com/helm/chart-testing/v3/pkg/tool"
+	"helm.sh/helm/v3/pkg/cli"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/apimachinery/pkg/version"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/kubectl/pkg/scheme"
 )
 
-// Kubectl is an interface to the helm binary; it is a thin layer on top of the Kubectl abstraction offered by
-// chart-testing to silence output being streamed to Stdout.
+// Based on https://access.redhat.com/solutions/4870701
+var kubeOpenShiftVersionMap map[string]string = map[string]string{
+	"1.22": "4.9",
+	"1.21": "4.8",
+	"1.20": "4.7",
+	"1.19": "4.6",
+	"1.18": "4.5",
+	"1.17": "4.4",
+	"1.16": "4.3",
+	"1.14": "4.2",
+	"1.13": "4.1",
+}
+
 type Kubectl struct {
-	tool.Kubectl
-	ProcessExecutor
+	clientset kubernetes.Interface
 }
 
-func NewKubectl(exec ProcessExecutor) Kubectl {
-	return Kubectl{
-		tool.NewKubectl(exec.ProcessExecutor),
-		exec,
+func NewKubectl(kubeConfig clientcmd.ClientConfig) (*Kubectl, error) {
+	config, err := kubeConfig.ClientConfig()
+	if err != nil {
+		return nil, err
 	}
+
+	config.APIPath = "/api"
+	config.GroupVersion = &schema.GroupVersion{Group: "core", Version: "v1"}
+	config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
+	kubectl := new(Kubectl)
+	kubectl.clientset, err = kubernetes.NewForConfig(config)
+	if err != nil {
+		return nil, err
+	}
+
+	return kubectl, nil
 }
 
-func (k Kubectl) WaitForDeployments(namespace string, selector string) error {
-	output, err := k.RunProcessAndCaptureOutput(
-		"kubectl", "get", "deployments", "--namespace", namespace, "--selector", selector, "--output", "jsonpath={.items[*].metadata.name}")
+func (k Kubectl) WaitForDeployments(context context.Context, namespace string, selector string) error {
+	deployments, err := k.clientset.AppsV1().Deployments(namespace).List(context, metav1.ListOptions{
+		LabelSelector: selector,
+	})
 	if err != nil {
 		return err
 	}
 
-	deployments := strings.Fields(output)
-	for _, deployment := range deployments {
-		deployment = strings.Trim(deployment, "'")
-		_, err := k.RunProcessAndCaptureOutput("kubectl", "rollout", "status", "deployment", deployment, "--namespace", namespace)
-		if err != nil {
-			return err
-		}
-
-		// 'kubectl rollout status' does not return a non-zero exit code when rollouts fail.
-		// We, thus, need to double-check here.
-		//
+	for _, deployment := range deployments.Items {
 		// Just after rollout, pods from the previous deployment revision may still be in a
 		// terminating state.
-		unavailable, err := k.RunProcessAndCaptureOutput("kubectl", "get", "deployment", deployment, "--namespace", namespace, "--output",
-			`jsonpath={.status.unavailableReplicas}`)
-		if err != nil {
-			return err
-		}
-		if unavailable != "" && unavailable != "0" {
-			return fmt.Errorf("%s replicas unavailable", unavailable)
+		unavailable := deployment.Status.UnavailableReplicas
+		if unavailable != 0 {
+			return fmt.Errorf("%d replicas unavailable", unavailable)
 		}
 	}
 
 	return nil
 }
+
+func (k Kubectl) DeleteNamespace(context context.Context, namespace string) error {
+	if err := k.clientset.CoreV1().Namespaces().Delete(context, namespace, *metav1.NewDeleteOptions(0)); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (k Kubectl) GetServerVersion() (*version.Info, error) {
+	version, err := k.clientset.Discovery().ServerVersion()
+	if err != nil {
+		return nil, err
+	}
+	return version, err
+}
+
+func GetKubeOpenShiftVersionMap() map[string]string {
+	return kubeOpenShiftVersionMap
+}
+
+func GetClientConfig(envSettings *cli.EnvSettings) clientcmd.ClientConfig {
+	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
+	if len(envSettings.KubeConfig) > 0 {
+		loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: envSettings.KubeConfig}
+	}
+
+	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
+		loadingRules,
+		&clientcmd.ConfigOverrides{})
+}
diff --git a/pkg/tool/oc.go b/pkg/tool/oc.go
deleted file mode 100644
index 0438146..0000000
--- a/pkg/tool/oc.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package tool
-
-import (
-	"fmt"
-
-	"gopkg.in/yaml.v3"
-)
-
-type Oc struct {
-	ProcessExecutorer
-}
-
-func NewOc(exec ProcessExecutorer) Oc {
-	return Oc{
-		ProcessExecutorer: exec,
-	}
-}
-
-const osVersionKey = "serverVersion"
-
-// Based on https://access.redhat.com/solutions/4870701
-var kubeOpenShiftVersionMap map[string]string = map[string]string{
-	"1.22": "4.9",
-	"1.21": "4.8",
-	"1.20": "4.7",
-	"1.19": "4.6",
-	"1.18": "4.5",
-	"1.17": "4.4",
-	"1.16": "4.3",
-	"1.14": "4.2",
-	"1.13": "4.1",
-}
-
-func (o Oc) GetVersion() (string, error) {
-	rawOutput, err := o.RunProcessAndCaptureOutput("oc", "version", "-o", "yaml")
-	if err != nil {
-		return "", err
-	}
-	out := map[string]interface{}{}
-	err = yaml.Unmarshal([]byte(rawOutput), &out)
-	if err != nil {
-		return "", err
-	}
-	// Relying on Kubernetes version can be replaced after fixing this issue:
-	// https://bugzilla.redhat.com/show_bug.cgi?id=1850656
-	kubeServerVersion := out[osVersionKey].(map[string]interface{})
-	kubeVersion := fmt.Sprintf("%s.%s", kubeServerVersion["major"], kubeServerVersion["minor"])
-	osVersion, ok := kubeOpenShiftVersionMap[kubeVersion]
-	if !ok {
-		return "", fmt.Errorf("Internal error: %q not found in Kubernetes-OpenShift version map", kubeVersion)
-	}
-
-	return osVersion, nil
-}
-
-func GetKubeOpenShiftVersionMap() map[string]string {
-	return kubeOpenShiftVersionMap
-}


Test patch:
diff --git a/pkg/chartverifier/checks/charttesting.go b/pkg/chartverifier/checks/charttesting.go
index f7a823c..06f6b80 100644
--- a/pkg/chartverifier/checks/charttesting.go
+++ b/pkg/chartverifier/checks/charttesting.go
@@ -1,11 +1,11 @@
 package checks
 
 import (
+	"context"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path"
-	"strings"
 
 	"github.com/Masterminds/semver"
 	"github.com/helm/chart-testing/v3/pkg/chart"
@@ -14,6 +14,8 @@ import (
 	"github.com/imdario/mergo"
 	"github.com/redhat-certification/chart-verifier/pkg/tool"
 	"gopkg.in/yaml.v3"
+	"helm.sh/helm/v3/pkg/cli"
+	helmcli "helm.sh/helm/v3/pkg/cli"
 )
 
 const (
@@ -21,16 +23,29 @@ const (
 )
 
 // Versioner provides OpenShift version
-type Versioner func() (string, error)
+type Versioner func(envSettings *cli.EnvSettings) (string, error)
 
-func getVersion() (string, error) {
+func getVersion(envSettings *cli.EnvSettings) (string, error) {
+	kubeConfig := tool.GetClientConfig(envSettings)
+	kubectl, err := tool.NewKubectl(kubeConfig)
+	if err != nil {
+		return "", err
+	}
+
+	serverVersion, err := kubectl.GetServerVersion()
+	if err != nil {
+		return "", err
+	}
 
-	procExec := tool.NewProcessExecutor(false)
-	oc := tool.NewOc(procExec)
+	// Relying on Kubernetes version can be replaced after fixing this issue:
+	// https://bugzilla.redhat.com/show_bug.cgi?id=1850656
+	kubeVersion := fmt.Sprintf("%s.%s", serverVersion.Major, serverVersion.Minor)
+	osVersion, ok := tool.GetKubeOpenShiftVersionMap()[kubeVersion]
+	if !ok {
+		return "", fmt.Errorf("internal error: %q not found in Kubernetes-OpenShift version map", kubeVersion)
+	}
 
-	// oc.GetVersion() returns an error both in case the oc command can't be executed and
-	// the value for the OpenShift version key not present.
-	return oc.GetVersion()
+	return osVersion, nil
 }
 
 type OpenShiftVersionErr string
@@ -85,19 +100,23 @@ func buildChartTestingConfiguration(opts *CheckOptions) config.Configuration {
 // interpretation the main logic chart-testing carries, and other
 // functions used in this context were also ported from
 // chart-verifier.
-//
-// Helm and kubectl are requirements in the system executing the check
-// in order to orchestrate the install, upgrade and chart testing
-// phases.
 func ChartTesting(opts *CheckOptions) (Result, error) {
 
 	tool.LogInfo("Start chart install and test check")
 
 	cfg := buildChartTestingConfiguration(opts)
-	procExec := tool.NewProcessExecutor(cfg.Debug)
-	extraArgs := strings.Fields(cfg.HelmExtraArgs)
-	helm := tool.NewHelm(procExec, extraArgs)
-	kubectl := tool.NewKubectl(procExec)
+	helm, err := tool.NewHelm(opts.HelmEnvSettings, opts.Values)
+	if err != nil {
+		tool.LogError("End chart install and test check with NewHelm error")
+		return NewResult(false, err.Error()), nil
+	}
+
+	kubeConfig := tool.GetClientConfig(opts.HelmEnvSettings)
+	kubectl, err := tool.NewKubectl(kubeConfig)
+	if err != nil {
+		tool.LogError("End chart install and test check with NewKubectl error")
+		return NewResult(false, err.Error()), nil
+	}
 
 	_, path, err := LoadChartFromURI(opts.URI)
 	if err != nil {
@@ -150,7 +169,7 @@ func ChartTesting(opts *CheckOptions) (Result, error) {
 		}
 	}
 
-	if versionError := setOCVersion(opts.AnnotationHolder, getVersion); versionError != nil {
+	if versionError := setOCVersion(opts.AnnotationHolder, opts.HelmEnvSettings, getVersion); versionError != nil {
 		if versionError != nil {
 			tool.LogWarning(fmt.Sprintf("End chart install and test check with version error: %v", versionError))
 		}
@@ -167,8 +186,8 @@ func ChartTesting(opts *CheckOptions) (Result, error) {
 func generateInstallConfig(
 	cfg config.Configuration,
 	chrt *chart.Chart,
-	helm tool.Helm,
-	kubectl tool.Kubectl,
+	helm *tool.Helm,
+	kubectl *tool.Kubectl,
 	configRelease string,
 ) (namespace, release, releaseSelector string, cleanup func()) {
 	release = configRelease
@@ -179,7 +198,7 @@ func generateInstallConfig(
 		}
 		releaseSelector = fmt.Sprintf("%s=%s", cfg.ReleaseLabel, release)
 		cleanup = func() {
-			helm.DeleteRelease(namespace, release)
+			helm.Uninstall(namespace, release)
 		}
 	} else {
 		if len(release) == 0 {
@@ -188,8 +207,8 @@ func generateInstallConfig(
 			_, namespace = chrt.CreateInstallParams(cfg.BuildId)
 		}
 		cleanup = func() {
-			helm.DeleteRelease(namespace, release)
-			kubectl.DeleteNamespace(namespace)
+			helm.Uninstall(namespace, release)
+			kubectl.DeleteNamespace(context.TODO(), namespace)
 		}
 	}
 	return
@@ -197,12 +216,12 @@ func generateInstallConfig(
 
 // testRelease tests a release.
 func testRelease(
-	helm tool.Helm,
-	kubectl tool.Kubectl,
+	helm *tool.Helm,
+	kubectl *tool.Kubectl,
 	release, namespace, releaseSelector string,
 	cleanupHelmTests bool,
 ) error {
-	if err := kubectl.WaitForDeployments(namespace, releaseSelector); err != nil {
+	if err := kubectl.WaitForDeployments(context.TODO(), namespace, releaseSelector); err != nil {
 		return err
 	}
 	if err := helm.Test(namespace, release); err != nil {
@@ -224,8 +243,8 @@ func getChartPreviousVersion(chrt *chart.Chart) (*chart.Chart, error) {
 func upgradeAndTestChart(
 	cfg config.Configuration,
 	oldChrt, chrt *chart.Chart,
-	helm tool.Helm,
-	kubectl tool.Kubectl,
+	helm *tool.Helm,
+	kubectl *tool.Kubectl,
 	configRelease string,
 ) chart.TestResult {
 
@@ -255,14 +274,14 @@ func upgradeAndTestChart(
 			defer cleanup()
 
 			// Install previous version of chart. If installation fails, ignore this release.
-			if err := helm.InstallWithValues(oldChrt.Path(), valuesFile, namespace, release); err != nil {
+			if err := helm.Install(namespace, oldChrt.Path(), release, valuesFile); err != nil {
 				return fmt.Errorf("Upgrade testing for release '%s' skipped because of previous revision installation error: %w", release, err)
 			}
 			if err := testRelease(helm, kubectl, release, namespace, releaseSelector, true); err != nil {
 				return fmt.Errorf("Upgrade testing for release '%s' skipped because of previous revision testing error", release)
 			}
 
-			if err := helm.Upgrade(oldChrt.Path(), namespace, release); err != nil {
+			if err := helm.Upgrade(namespace, oldChrt.Path(), release); err != nil {
 				return err
 			}
 
@@ -360,8 +379,8 @@ func newTempValuesFileWithOverrides(filename string, valuesOverrides map[string]
 func installAndTestChartRelease(
 	cfg config.Configuration,
 	chrt *chart.Chart,
-	helm tool.Helm,
-	kubectl tool.Kubectl,
+	helm *tool.Helm,
+	kubectl *tool.Kubectl,
 	valuesOverrides map[string]interface{},
 	configRelease string,
 ) chart.TestResult {
@@ -395,7 +414,7 @@ func installAndTestChartRelease(
 			namespace, release, releaseSelector, releaseCleanup := generateInstallConfig(cfg, chrt, helm, kubectl, configRelease)
 			defer releaseCleanup()
 
-			if err := helm.InstallWithValues(chrt.Path(), tmpValuesFile, namespace, release); err != nil {
+			if err := helm.Install(namespace, chrt.Path(), release, tmpValuesFile); err != nil {
 				return err
 			}
 			return testRelease(helm, kubectl, release, namespace, releaseSelector, false)
@@ -411,10 +430,10 @@ func installAndTestChartRelease(
 	return result
 }
 
-func setOCVersion(holder AnnotationHolder, versioner Versioner) error {
-	// oc.GetVersion() returns an error both in case the oc command can't be executed and
+func setOCVersion(holder AnnotationHolder, envSettings *helmcli.EnvSettings, versioner Versioner) error {
+	// kubectl.GetVersion() returns an error both in case the kubectl command can't be executed and
 	// the value for the OpenShift version key not present.
-	osVersion, getVersionErr := versioner()
+	osVersion, getVersionErr := versioner(envSettings)
 
 	// From this point on, an error is set and osVersion is empty.
 	if getVersionErr != nil && holder.GetCertifiedOpenShiftVersionFlag() != "" {
diff --git a/pkg/chartverifier/checks/charttesting_test.go b/pkg/chartverifier/checks/charttesting_test.go
index 1a2b3a3..f232b5d 100644
--- a/pkg/chartverifier/checks/charttesting_test.go
+++ b/pkg/chartverifier/checks/charttesting_test.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"runtime"
@@ -30,25 +29,11 @@ func absPathFromSourceFileLocation(name string) (string, error) {
 	return filepath.Join(dirname, name), nil
 }
 
-func lookPath(programs ...string) error {
-	for _, p := range programs {
-		_, err := exec.LookPath(p)
-		if err != nil {
-			return fmt.Errorf("required program %q not found", p)
-		}
-	}
-	return nil
-}
-
 func TestChartTesting(t *testing.T) {
 	if os.Getenv("CHART_VERIFIER_ENABLE_CLUSTER_TESTING") == "" {
 		t.Skip("CHART_VERIFIER_ENABLE_CLUSTER_TESTING not set, skipping in cluster tests")
 	}
 
-	if err := lookPath("helm", "kubectl"); err != nil {
-		t.Skip(err.Error())
-	}
-
 	type testCase struct {
 		description string
 		opts        CheckOptions
@@ -119,11 +104,11 @@ func TestChartTesting(t *testing.T) {
 	}
 }
 
-func getVersionError() (string, error) {
+func getVersionError(settings *cli.EnvSettings) (string, error) {
 	return "", errors.New("error")
 }
 
-func getVersionGood() (string, error) {
+func getVersionGood(settings *cli.EnvSettings) (string, error) {
 	return "4.7.9", nil
 }
 
@@ -145,7 +130,7 @@ func (holder *testAnnotationHolder) SetSupportedOpenShiftVersions(version string
 func TestVersionSetting(t *testing.T) {
 	type testCase struct {
 		description string
-		holder      *testAnnotationHolder
+		opts        *CheckOptions
 		versioner   Versioner
 		version     string
 		error       string
@@ -154,25 +139,25 @@ func TestVersionSetting(t *testing.T) {
 	testCases := []testCase{
 		{
 			description: "oc.Version returns 4.7.9",
-			holder:      &testAnnotationHolder{},
+			opts:        &CheckOptions{AnnotationHolder: &testAnnotationHolder{}},
 			versioner:   getVersionGood,
 			version:     "4.7.9",
 		},
 		{
 			description: "oc.Version returns error, flag set to 4.7.8",
-			holder:      &testAnnotationHolder{CertifiedOpenShiftVersionFlag: "4.7.8"},
+			opts:        &CheckOptions{AnnotationHolder: &testAnnotationHolder{CertifiedOpenShiftVersionFlag: "4.7.8"}},
 			versioner:   getVersionError,
 			version:     "4.7.8",
 		},
 		{
 			description: "oc.Version returns semantic error, flag set to fourseveneight",
-			holder:      &testAnnotationHolder{CertifiedOpenShiftVersionFlag: "fourseveneight"},
+			opts:        &CheckOptions{AnnotationHolder: &testAnnotationHolder{CertifiedOpenShiftVersionFlag: "fourseveneight"}},
 			versioner:   getVersionError,
 			error:       "OpenShift version is not following SemVer spec. Invalid Semantic Version",
 		},
 		{
 			description: "oc.Version returns error, flag not set",
-			holder:      &testAnnotationHolder{},
+			opts:        &CheckOptions{AnnotationHolder: &testAnnotationHolder{}},
 			versioner:   getVersionError,
 			error:       "Missing OpenShift version. error. And the 'openshift-version' flag has not set.",
 		},
@@ -182,14 +167,14 @@ func TestVersionSetting(t *testing.T) {
 
 		t.Run(tc.description, func(t *testing.T) {
 
-			err := setOCVersion(tc.holder, tc.versioner)
+			err := setOCVersion(tc.opts.AnnotationHolder, tc.opts.HelmEnvSettings, tc.versioner)
 
 			if len(tc.error) > 0 {
 				require.Error(t, err)
 				require.Equal(t, tc.error, err.Error())
 			} else {
 				require.NoError(t, err)
-				require.Equal(t, tc.version, tc.holder.OpenShiftVersion)
+				require.Equal(t, tc.version, tc.opts.AnnotationHolder.(*testAnnotationHolder).OpenShiftVersion)
 			}
 
 		})
diff --git a/pkg/tool/helm_test.go b/pkg/tool/helm_test.go
index b33d12f..ffff9a0 100644
--- a/pkg/tool/helm_test.go
+++ b/pkg/tool/helm_test.go
@@ -1,65 +1,266 @@
 package tool
 
 import (
-	"fmt"
-	"os"
-	"strings"
+	"io/ioutil"
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"helm.sh/helm/v3/pkg/action"
+	"helm.sh/helm/v3/pkg/chart"
+	"helm.sh/helm/v3/pkg/chartutil"
+	"helm.sh/helm/v3/pkg/cli"
+	kubefake "helm.sh/helm/v3/pkg/kube/fake"
+	"helm.sh/helm/v3/pkg/release"
+	"helm.sh/helm/v3/pkg/storage"
+	"helm.sh/helm/v3/pkg/storage/driver"
 )
 
-func temporarilyToggleEnv(name string) (untoggleFn func(), err error) {
-	originalValue := os.Getenv(name)
-	err = os.Unsetenv(name)
-	untoggleFn = func() {
-		os.Setenv(name, originalValue)
+func TestInstall(t *testing.T) {
+	tests := []struct {
+		releaseName string
+		chartPath   string
+		expected    string
+	}{
+		{
+			releaseName: "valid chart",
+			chartPath:   "../chartverifier/checks/psql-service-0.1.7",
+			expected:    "",
+		},
+		{
+			releaseName: "invalid chart",
+			chartPath:   "../chartverifier/checks/psql-service-9.9.9",
+			expected:    "path \"../chartverifier/checks/psql-service-9.9.9\" not found",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.releaseName, func(t *testing.T) {
+			actionConfig := &action.Configuration{
+				Releases:     storage.Init(driver.NewMemory()),
+				KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
+				Capabilities: chartutil.DefaultCapabilities,
+				Log:          func(format string, v ...interface{}) {},
+			}
+			helm := Helm{
+				config:      actionConfig,
+				args:        map[string]interface{}{"set": "k8Project=default"},
+				envSettings: &cli.EnvSettings{},
+			}
+			err := helm.Install("default", tt.chartPath, tt.releaseName, "")
+			if err == nil {
+				require.Equal(t, tt.expected, "")
+			} else {
+				require.Equal(t, tt.expected, err.Error())
+			}
+		})
 	}
-	return
 }
 
-func TestInstallWithValues(t *testing.T) {
-	t.Run("helm not available in path", func(t *testing.T) {
-		untogglePathEnv, err := temporarilyToggleEnv("PATH")
-		require.NoError(t, err)
-		defer untogglePathEnv()
-
-		// arrange
-		valuesFile := ""
-		chrt := "non-existing-chart.tgz"
-		release := "non-existing-release"
-		namespace := "default"
-		expectedErrorMessage := fmt.Sprintf(
-			"Error running process: executing helm with args \"install %s %s --namespace %s --wait\": "+
-				"exec: \"helm\": executable file not found in $PATH",
-			release, chrt, namespace)
-		h := NewHelm(NewProcessExecutor(false), []string{})
+func TestUninstall(t *testing.T) {
+	tests := []struct {
+		name     string
+		release  *release.Release
+		expected string
+	}{
+		{
+			name: "successful release uninstall should remove release installed",
+			release: &release.Release{
+				Name: "test-release-valid",
+				Info: &release.Info{
+					Status: release.StatusDeployed,
+				},
+				Namespace: "default",
+			},
+			expected: "",
+		},
+		{
+			name: "remove non-existent release should result in error",
+			release: &release.Release{
+				Name: "test-release-invalid",
+				Info: &release.Info{
+					Status: release.StatusDeployed,
+				},
+				Namespace: "default",
+			},
+			expected: "uninstall: Release not loaded: test-release-invalid: release: not found",
+		},
+	}
 
-		// act
-		// for this test, none of the arguments matter si
-		err = h.InstallWithValues(chrt, valuesFile, namespace, release)
+	for _, tt := range tests {
+		store := storage.Init(driver.NewMemory())
+		t.Run(tt.name, func(t *testing.T) {
+			actionConfig := &action.Configuration{
+				Releases:     store,
+				KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
+				Capabilities: chartutil.DefaultCapabilities,
+				Log:          func(format string, v ...interface{}) {},
+			}
+			helm := Helm{
+				config:      actionConfig,
+				args:        make(map[string]interface{}),
+				envSettings: &cli.EnvSettings{},
+			}
+			if tt.expected == "" {
+				// create fake release
+				if err := store.Create(tt.release); err != nil {
+					t.Error(err)
+				}
+			}
+			err := helm.Uninstall("default", tt.release.Name)
+			if err == nil {
+				require.Equal(t, tt.expected, "")
+			} else {
+				require.Equal(t, tt.expected, err.Error())
+			}
+		})
+	}
+}
 
-		// assert
-		require.Error(t, err)
-		require.Equal(t, expectedErrorMessage, err.Error())
-	})
+func TestUpgrade(t *testing.T) {
+	testValues, err := chartutil.ReadValuesFile("../chartverifier/checks/psql-service-0.1.7/values.yaml")
+	if err != nil {
+		t.Error(err)
+	}
+	testValues.AsMap()["k8Project"] = "default"
+	tests := []struct {
+		name      string
+		chartPath string
+		release   *release.Release
+		expected  string
+	}{
+		{
+			name:      "successful release upgrade should not return error",
+			chartPath: "../chartverifier/checks/psql-service-0.1.7",
+			release: &release.Release{
+				Name: "test-release-valid",
+				Info: &release.Info{
+					Status: release.StatusDeployed,
+				},
+				Namespace: "default",
+				Chart:     &chart.Chart{Values: testValues},
+			},
+			expected: "",
+		},
+		{
+			name:      "upgrade non-existent release should result in error",
+			chartPath: "../chartverifier/checks/psql-service-0.1.7",
+			release: &release.Release{
+				Name: "test-release-invalid",
+				Info: &release.Info{
+					Status: release.StatusDeployed,
+				},
+				Namespace: "default",
+				Chart:     &chart.Chart{Values: testValues},
+			},
+			expected: "\"test-release-invalid\" has no deployed releases",
+		},
+	}
 
-	t.Run("helm failure should include content streamed to both Stderr and Stdout", func(t *testing.T) {
-		// arrange
-		chrt := "non-existing-chart.tgz"
-		release := "non-existing-release"
-		valuesFile := ""
-		namespace := "default"
-		expectedErrorMessage := "Error running process"
-		processExecutor := NewProcessExecutor(false)
-		extraArgs := []string{}
-		h := NewHelm(processExecutor, extraArgs)
+	for _, tt := range tests {
+		store := storage.Init(driver.NewMemory())
+		t.Run(tt.name, func(t *testing.T) {
+			actionConfig := &action.Configuration{
+				Releases:     store,
+				KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
+				Capabilities: chartutil.DefaultCapabilities,
+				Log:          func(format string, v ...interface{}) {},
+			}
+			helm := Helm{
+				config:      actionConfig,
+				args:        make(map[string]interface{}),
+				envSettings: &cli.EnvSettings{},
+			}
+			if tt.expected == "" {
+				// create fake release
+				if err := store.Create(tt.release); err != nil {
+					t.Error(err)
+				}
+			}
+			err := helm.Upgrade("default", tt.chartPath, tt.release.Name)
+			if err == nil {
+				require.Equal(t, tt.expected, "")
+			} else {
+				require.Equal(t, tt.expected, err.Error())
+			}
+		})
+	}
+}
 
-		// act
-		err := h.InstallWithValues(chrt, valuesFile, namespace, release)
+func TestReleaseTesting(t *testing.T) {
+	releaseTestPath := "../chartverifier/checks/psql-service-0.1.7/templates/tests/test-psql-connection.yaml"
+	releaseTest, err := ioutil.ReadFile(releaseTestPath)
+	if err != nil {
+		t.Error(err)
+	}
+	testHooks := []*release.Hook{
+		{
+			Name:     "test-success-hook",
+			Kind:     "Pod",
+			Path:     releaseTestPath,
+			Manifest: string(releaseTest),
+			LastRun:  release.HookExecution{},
+			Events:   []release.HookEvent{release.HookTest},
+		},
+	}
+	tests := []struct {
+		name      string
+		chartPath string
+		release   *release.Release
+		expected  string
+	}{
+		{
+			name:      "successful release test should not return error",
+			chartPath: "../chartverifier/checks/psql-service-0.1.7",
+			release: &release.Release{
+				Name: "test-release-valid",
+				Info: &release.Info{
+					Status: release.StatusDeployed,
+				},
+				Namespace: "default",
+				Hooks:     testHooks,
+			},
+			expected: "",
+		},
+		{
+			name:      "release test on non-existent release should result in error",
+			chartPath: "../chartverifier/checks/psql-service-0.1.7",
+			release: &release.Release{
+				Name: "test-release-invalid",
+				Info: &release.Info{
+					Status: release.StatusDeployed,
+				},
+				Namespace: "default",
+				Hooks:     testHooks,
+			},
+			expected: "release: not found",
+		},
+	}
 
-		// assert
-		require.Error(t, err)
-		require.True(t, strings.Contains(err.Error(), expectedErrorMessage))
-	})
+	for _, tt := range tests {
+		store := storage.Init(driver.NewMemory())
+		t.Run(tt.name, func(t *testing.T) {
+			actionConfig := &action.Configuration{
+				Releases:     store,
+				KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
+				Capabilities: chartutil.DefaultCapabilities,
+				Log:          func(format string, v ...interface{}) {},
+			}
+			helm := Helm{
+				config:      actionConfig,
+				args:        make(map[string]interface{}),
+				envSettings: &cli.EnvSettings{},
+			}
+			if tt.expected == "" {
+				// create fake release
+				if err := store.Create(tt.release); err != nil {
+					t.Error(err)
+				}
+			}
+			err := helm.Test("default", tt.release.Name)
+			if err == nil {
+				require.Equal(t, tt.expected, "")
+			} else {
+				require.Equal(t, tt.expected, err.Error())
+			}
+		})
+	}
 }
diff --git a/pkg/tool/kubectl_test.go b/pkg/tool/kubectl_test.go
new file mode 100644
index 0000000..5adffe8
--- /dev/null
+++ b/pkg/tool/kubectl_test.go
@@ -0,0 +1,60 @@
+package tool
+
+import (
+	"fmt"
+	"testing"
+
+	"k8s.io/apimachinery/pkg/version"
+	discoveryfake "k8s.io/client-go/discovery/fake"
+	"k8s.io/client-go/kubernetes/fake"
+)
+
+type testData struct {
+	getVersionOut version.Info
+	OCVersion     string
+}
+
+var output120 = version.Info{
+	Major: "1",
+	Minor: "20",
+}
+
+var output121 = version.Info{
+	Major: "1",
+	Minor: "21",
+}
+
+var output122 = version.Info{
+	Major: "1",
+	Minor: "22",
+}
+
+var testsData []testData
+
+func TestOCVersions(t *testing.T) {
+
+	testsData = append(testsData, testData{getVersionOut: output120, OCVersion: "4.7"})
+	testsData = append(testsData, testData{getVersionOut: output121, OCVersion: "4.8"})
+	testsData = append(testsData, testData{getVersionOut: output122, OCVersion: "4.9"})
+
+	for _, testdata := range testsData {
+		clientset := fake.NewSimpleClientset()
+		clientset.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = &version.Info{
+			Major: testdata.getVersionOut.Major,
+			Minor: testdata.getVersionOut.Minor,
+		}
+		kubectl := Kubectl{clientset: clientset}
+		serverVersion, err := kubectl.GetServerVersion()
+		if err != nil {
+			t.Error(err)
+		}
+		if serverVersion.Major != testdata.getVersionOut.Major || serverVersion.Minor != testdata.getVersionOut.Minor {
+			t.Error(fmt.Sprintf("server version mismatch, expected: %+v, got: %+v", testdata.getVersionOut, serverVersion))
+		}
+		kubeVersion := fmt.Sprintf("%s.%s", serverVersion.Major, serverVersion.Minor)
+		ocVersion := GetKubeOpenShiftVersionMap()[kubeVersion]
+		if ocVersion != testdata.OCVersion {
+			t.Error(fmt.Sprintf("version mismatch, expected: %s, got: %s", testdata.OCVersion, ocVersion))
+		}
+	}
+}
diff --git a/pkg/tool/oc_test.go b/pkg/tool/oc_test.go
deleted file mode 100644
index 661624d..0000000
--- a/pkg/tool/oc_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package tool
-
-import (
-	"fmt"
-	"testing"
-)
-
-type TestData struct {
-	getVersionOut string
-	OCVersion     string
-}
-
-type fakeProcessExecutor struct {
-	Output string
-}
-
-var output120 = `
-serverVersion:
-  major: "1"
-  minor: "20"
-`
-var output121 = `
-serverVersion:
-  major: "1"
-  minor: "21"
-`
-var output122 = `
-serverVersion:
-  major: "1"
-  minor: "22"
-`
-
-var testsData []TestData
-
-func (p fakeProcessExecutor) RunProcessAndCaptureOutput(executable string, execArgs ...interface{}) (string, error) {
-	return p.Output, nil
-}
-
-func TestOCVersions(t *testing.T) {
-
-	testsData = append(testsData, TestData{getVersionOut: output120, OCVersion: "4.7"})
-	testsData = append(testsData, TestData{getVersionOut: output121, OCVersion: "4.8"})
-	testsData = append(testsData, TestData{getVersionOut: output122, OCVersion: "4.9"})
-
-	for _, testdata := range testsData {
-		t.Logf("Check for OC %s", testdata.OCVersion)
-		checkOcVersion(testdata.getVersionOut, testdata.OCVersion, t)
-	}
-}
-
-func checkOcVersion(kubeVersion string, expectedOCVersion string, t *testing.T) {
-	fp := fakeProcessExecutor{Output: kubeVersion}
-	oc := NewOc(fp)
-	version, _ := oc.GetVersion()
-	if version != expectedOCVersion {
-		t.Error(fmt.Sprintf("Version mismatch expected: %s, got: %s", expectedOCVersion, version))
-	}
-}