diff --git a/cfg/config.yaml b/cfg/config.yaml
index 5c21e37dd1e0096c3dfdeebfbc55a3e230a5e05e..8ad2e89acf585f7482c8abc9ff0f793d3130e6f1 100644
--- a/cfg/config.yaml
+++ b/cfg/config.yaml
@@ -7,106 +7,112 @@
 # nodeControls: ./cfg/node.yaml
 # federatedControls: ./cfg/federated.yaml
 
-## Support components
-etcd:
-  bin: etcd
-  conf: /etc/etcd/etcd.conf
-
-flanneld:
-  bin: flanneld
-  conf: /etc/sysconfig/flanneld
-
-# Installation
-# Configure kubernetes component binaries and paths to their configuration files.
-installation:
-  default:
-    config: /etc/kubernetes/config
-    master:
-      bin:
-        apiserver: apiserver
-        scheduler: scheduler
-        controller-manager: controller-manager
-      conf:
-        apiserver: /etc/kubernetes/apiserver
-        scheduler: /etc/kubernetes/scheduler
-        controller-manager: /etc/kubernetes/controller-manager
-    node:
-      bin:
-        kubelet: kubelet
-        proxy: proxy
-      conf:
-        kubelet: /etc/kubernetes/kubelet
-        proxy: /etc/kubernetes/proxy
-    federated:
-      bin:
-        apiserver: federation-apiserver
-        controller-manager: federation-controller-manager
-
-  kops:
-    config: /etc/kubernetes/config
-    master:
-      bin:
-        apiserver: apiserver
-        scheduler: scheduler
-        controller-manager: controller-manager
-      conf:
-        apiserver: /etc/kubernetes/apiserver
-        scheduler: /etc/kubernetes/scheduler
-        controller-manager: /etc/kubernetes/apiserver
-    node:
-      bin:
-        kubelet: kubelet
-        proxy: proxy
-      conf:
-        kubelet: /etc/kubernetes/kubelet
-        proxy: /etc/kubernetes/proxy
-    federated:
-      bin:
-        apiserver: federation-apiserver
-        controller-manager: federation-controller-manager
-
-  hyperkube:
-    config: /etc/kubernetes/config
-    master:
-      bin:
-        apiserver: hyperkube apiserver
-        scheduler: hyperkube scheduler
-        controller-manager: hyperkube controller-manager
-      conf:
-        apiserver: /etc/kubernetes/manifests/kube-apiserver.yaml
-        scheduler: /etc/kubernetes/manifests/kube-scheduler.yaml
-        controller-manager: /etc/kubernetes/manifests/kube-controller-manager.yaml
-    node:
-      bin:
-        kubelet: hyperkube kubelet
-        proxy: hyperkube proxy
-      conf:
-        kubelet: /etc/kubernetes/kubelet
-        proxy: /etc/kubernetes/addons/kube-proxy-daemonset.yaml
-    federated:
-      bin:
-        apiserver: hyperkube federation-apiserver
-        controller-manager: hyperkube federation-controller-manager
-
-  kubeadm:
-    config: /etc/kubernetes/config
-    master:
-      bin:
-        apiserver: kube-apiserver
-        scheduler: kube-scheduler
-        controller-manager: kube-controller-manager
-      conf:
-        apiserver: /etc/kubernetes/admin.conf
-        scheduler: /etc/kubernetes/scheduler.conf
-        controller-manager: /etc/kubernetes/controller-manager.conf
-    node:
-      bin:
-        kubelet: kubelet
-        proxy: kube-proxy
-      conf:
-        kubelet: /etc/kubernetes/kubelet.conf
-        proxy: /etc/kubernetes/proxy.conf
-    federated:
-      bin:
-        apiserver: kube-federation-apiserver
-        controller-manager: kube-federation-controller-manager
+master:
+  components:
+    - apiserver
+    - scheduler
+    - controllermanager
+    - etcd 
+    - flanneld
+    # kubernetes is a component to cover the config file /etc/kubernetes/config that is referred to in the benchmark
+    - kubernetes
+
+  kubernetes:
+    defaultconf: /etc/kubernetes/config
+
+  apiserver:
+    bins:
+      - "kube-apiserver"
+      - "hyperkube apiserver"
+      - "apiserver"
+    confs:
+      - /etc/kubernetes/manifests/kube-apiserver.yaml
+      - /etc/kubernetes/apiserver.conf
+      - /etc/kubernetes/apiserver
+    defaultconf: /etc/kubernetes/apiserver
+
+  scheduler:
+    bins:
+      - "kube-scheduler"
+      - "hyperkube scheduler"
+      - "scheduler"
+    confs: 
+      - /etc/kubernetes/manifests/kube-scheduler.yaml
+      - /etc/kubernetes/scheduler.conf
+      - /etc/kubernetes/scheduler
+    defaultconf: /etc/kubernetes/scheduler
+
+  controllermanager:
+    bins:
+      - "kube-controller-manager"
+      - "hyperkube controller-manager"
+      - "controller-manager"
+    confs:
+      - /etc/kubernetes/manifests/kube-controller-manager.yaml
+      - /etc/kubernetes/controller-manager.conf
+      - /etc/kubernetes/controller-manager
+    defaultconf: /etc/kubernetes/controller-manager
+
+  etcd:
+    optional: true
+    bins:
+      - "etcd"
+    confs:
+      - /etc/kubernetes/manifests/etcd.yaml
+      - /etc/etcd/etcd.conf
+    defaultconf: /etc/etcd/etcd.conf
+
+  flanneld:
+    optional: true
+    bins:
+      - flanneld
+    defaultconf: /etc/sysconfig/flanneld
+
+
+node:
+  components:
+    - kubelet
+    - proxy
+    # kubernetes is a component to cover the config file /etc/kubernetes/config that is referred to in the benchmark
+    - kubernetes
+
+  kubernetes:
+    defaultconf: /etc/kubernetes/config    
+
+  kubelet:
+    bins:
+      - "hyperkube kubelet"
+      - "kubelet"
+    confs:
+      - /etc/kubernetes/kubelet.conf
+      - /etc/kubernetes/kubelet 
+    defaultconf: "/etc/kubernetes/kubelet.conf"
+  
+  proxy:
+    bins:
+      - "kube-proxy"
+      - "hyperkube proxy"
+      - "proxy"
+    confs:
+      - /etc/kubernetes/proxy.conf
+      - /etc/kubernetes/proxy
+      - /etc/kubernetes/addons/kube-proxy-daemonset.yaml
+
+federated:
+  components:
+    - fedapiserver
+    - fedcontrollermanager
+
+  fedapiserver:
+    bins:
+      - "hyperkube federation-apiserver"
+      - "kube-federation-apiserver"
+      - "federation-apiserver"
+
+  fedcontrollermanager:
+    bins:
+      - "hyperkube federation-controller-manager"
+      - "kube-federation-controller-manager"
+      - "federation-controller-manager"
+
+
diff --git a/cfg/master.yaml b/cfg/master.yaml
index f54bf6aa6d5c751a67190b09951cc167f32113b7..dc9295e4b99646751ffaef7e7062f97b95a17945 100644
--- a/cfg/master.yaml
+++ b/cfg/master.yaml
@@ -636,7 +636,7 @@ groups:
 
     - id: 1.4.3
       text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)"
-      audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; fi'"
+      audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %a $kubernetesconf; fi'"
       tests:
         bin_op: or
         test_items:
@@ -656,12 +656,12 @@ groups:
             value: "600"
           set: true
       remediation: "Run the below command (based on the file location on your system) on the master node. 
-              \nFor example, chmod 644 $config"
+              \nFor example, chmod 644 $kubernetesconf"
       scored: true
 
     - id: 1.4.4
       text: "Ensure that the config file ownership is set to root:root (Scored)"
-      audit: "/bin/sh -c 'if test -e $config; then stat -c %U:%G $config; fi'"
+      audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %U:%G $kubernetesconf; fi'"
       tests:
         test_items:
         - flag: "root:root"
@@ -670,7 +670,7 @@ groups:
             value: "root:root"
           set: true
       remediation: "Run the below command (based on the file location on your system) on the master node. 
-              \nFor example, chown root:root $config"
+              \nFor example, chown root:root $kubernetesconf"
       scored: true
 
     - id: 1.4.5
diff --git a/cfg/node.yaml b/cfg/node.yaml
index a146baa6fc9abc4bcec4e65d09c5bab0e3e56fed..0a1c0b68dc4e2284c31251247ee2d67042748e98 100644
--- a/cfg/node.yaml
+++ b/cfg/node.yaml
@@ -17,7 +17,7 @@ groups:
               op: eq
               value: false
             set: true
-      remediation: "Edit the $config file on each node and set the KUBE_ALLOW_PRIV 
+      remediation: "Edit the $kubeletconf file on each node and set the KUBE_ALLOW_PRIV 
               parameter to \"--allow-privileged=false\""
       scored: true
 
@@ -199,7 +199,7 @@ groups:
               op: eq
               value: true
             set: true
-      remediation: "Edit the /etc/kubernetes/kubelet file on each node and set the KUBELET_ARGS parameter
+      remediation: "Edit the $kubeletconf file on each node and set the KUBELET_ARGS parameter
               to a value to include \"--feature-gates=RotateKubeletClientCertificate=true\"."
       scored: true
 
@@ -213,7 +213,7 @@ groups:
               op: eq
               value: true
             set: true
-      remediation: "Edit the /etc/kubernetes/kubelet file on each node and set the KUBELET_ARGS parameter
+      remediation: "Edit the $kubeletconf file on each node and set the KUBELET_ARGS parameter
               to a value to include \"--feature-gates=RotateKubeletServerCertificate=true\"."
       scored: true
 
@@ -222,7 +222,7 @@ groups:
   checks:
     - id: 2.2.1
       text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)"
-      audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; fi'"
+      audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %a $kubernetesconf; fi'"
       tests:
         bin_op: or
         test_items:
@@ -242,12 +242,12 @@ groups:
               value: "600"
             set: true
       remediation: "Run the below command (based on the file location on your system) on the each worker node. 
-              \nFor example, chmod 644 $config"
+              \nFor example, chmod 644 $kubernetesconf"
       scored: true
 
     - id: 2.2.2
       text: "Ensure that the config file ownership is set to root:root (Scored)"
-      audit: "/bin/sh -c 'if test -e $config; then stat -c %U:%G $config; fi'"
+      audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %U:%G $kubernetesconf; fi'"
       tests:
         test_items:
           - flag: "root:root"
@@ -256,7 +256,7 @@ groups:
               value: root:root
             set: true
       remediation: "Run the below command (based on the file location on your system) on the each worker node. 
-              \nFor example, chown root:root $config"
+              \nFor example, chown root:root $kubernetesconf"
       scored: true
 
     - id: 2.2.3
diff --git a/check/check.go b/check/check.go
index 4f91340380f171a3a4eb9b7c02d7d5e626cae1f7..16c3984d14a9040d5fa4fe4b77d9ebd8212625d8 100644
--- a/check/check.go
+++ b/check/check.go
@@ -156,7 +156,9 @@ func (c *Check) Run() {
 		i++
 	}
 
-	glog.V(2).Info("%s\n", errmsgs)
+	if errmsgs != "" {
+		glog.V(2).Info(errmsgs)
+	}
 
 	res := c.Tests.execute(out.String())
 	if res {
diff --git a/cmd/common.go b/cmd/common.go
index 89f45bc4883faec29db8af16b60f7f5411408827..3708d1fb1a6feb91d12a2269132c6c20e0b6f71c 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -17,9 +17,9 @@ package cmd
 import (
 	"fmt"
 	"io/ioutil"
-	"os"
 
 	"github.com/aquasecurity/kube-bench/check"
+	"github.com/golang/glog"
 	"github.com/spf13/viper"
 )
 
@@ -52,34 +52,30 @@ var (
 func runChecks(t check.NodeType) {
 	var summary check.Summary
 	var file string
+	var err error
+	var typeConf *viper.Viper
 
-	// Master variables
-	apiserverBin = viper.GetString("installation." + installation + ".master.bin.apiserver")
-	apiserverConf = viper.GetString("installation." + installation + ".master.conf.apiserver")
-	schedulerBin = viper.GetString("installation." + installation + ".master.bin.scheduler")
-	schedulerConf = viper.GetString("installation." + installation + ".master.conf.scheduler")
-	controllerManagerBin = viper.GetString("installation." + installation + ".master.bin.controller-manager")
-	controllerManagerConf = viper.GetString("installation." + installation + ".master.conf.controller-manager")
-	config = viper.GetString("installation." + installation + ".config")
-
-	etcdBin = viper.GetString("etcd.bin")
-	etcdConf = viper.GetString("etcd.conf")
-	flanneldBin = viper.GetString("flanneld.bin")
-	flanneldConf = viper.GetString("flanneld.conf")
-
-	// Node variables
-	kubeletBin = viper.GetString("installation." + installation + ".node.bin.kubelet")
-	kubeletConf = viper.GetString("installation." + installation + ".node.conf.kubelet")
-	proxyBin = viper.GetString("installation." + installation + ".node.bin.proxy")
-	proxyConf = viper.GetString("installation." + installation + ".node.conf.proxy")
-
-	// Federated
-	fedApiserverBin = viper.GetString("installation." + installation + ".federated.bin.apiserver")
-	fedControllerManagerBin = viper.GetString("installation." + installation + ".federated.bin.controller-manager")
+	glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
+
+	switch t {
+	case check.MASTER:
+		file = masterFile
+		typeConf = viper.Sub("master")
+	case check.NODE:
+		file = nodeFile
+		typeConf = viper.Sub("node")
+	case check.FEDERATED:
+		file = federatedFile
+		typeConf = viper.Sub("federated")
+	}
+
+	// Get the set of exectuables and config files we care about on this type of node. This also
+	// checks that the executables we need for the node type are running.
+	binmap := getBinaries(typeConf)
+	confmap := getConfigFiles(typeConf)
 
 	// Run kubernetes installation validation checks.
 	verifyKubeVersion(kubeMajorVersion, kubeMinorVersion)
-	verifyNodeType(t)
 
 	switch t {
 	case check.MASTER:
@@ -96,26 +92,9 @@ func runChecks(t check.NodeType) {
 	}
 
 	// Variable substitutions. Replace all occurrences of variables in controls files.
-	s := multiWordReplace(string(in), "$apiserverbin", apiserverBin)
-	s = multiWordReplace(s, "$apiserverconf", apiserverConf)
-	s = multiWordReplace(s, "$schedulerbin", schedulerBin)
-	s = multiWordReplace(s, "$schedulerconf", schedulerConf)
-	s = multiWordReplace(s, "$controllermanagerbin", controllerManagerBin)
-	s = multiWordReplace(s, "$controllermanagerconf", controllerManagerConf)
-	s = multiWordReplace(s, "$config", config)
-
-	s = multiWordReplace(s, "$etcdbin", etcdBin)
-	s = multiWordReplace(s, "$etcdconf", etcdConf)
-	s = multiWordReplace(s, "$flanneldbin", flanneldBin)
-	s = multiWordReplace(s, "$flanneldconf", flanneldConf)
-
-	s = multiWordReplace(s, "$kubeletbin", kubeletBin)
-	s = multiWordReplace(s, "$kubeletconf", kubeletConf)
-	s = multiWordReplace(s, "$proxybin", proxyBin)
-	s = multiWordReplace(s, "$proxyconf", proxyConf)
-
-	s = multiWordReplace(s, "$fedapiserverbin", fedApiserverBin)
-	s = multiWordReplace(s, "$fedcontrollermanagerbin", fedControllerManagerBin)
+	s := string(in)
+	s = makeSubstitutions(s, "bin", binmap)
+	s = makeSubstitutions(s, "conf", confmap)
 
 	controls, err := check.NewControls(t, []byte(s))
 	if err != nil {
@@ -147,41 +126,6 @@ func runChecks(t check.NodeType) {
 	}
 }
 
-// verifyNodeType checks the executables and config files are as expected
-// for the specified tests (master, node or federated).
-func verifyNodeType(t check.NodeType) {
-	var bins []string
-	var confs []string
-
-	switch t {
-	case check.MASTER:
-		bins = []string{apiserverBin, schedulerBin, controllerManagerBin}
-		confs = []string{apiserverConf, schedulerConf, controllerManagerConf}
-	case check.NODE:
-		bins = []string{kubeletBin, proxyBin}
-		confs = []string{kubeletConf, proxyConf}
-	case check.FEDERATED:
-		bins = []string{fedApiserverBin, fedControllerManagerBin}
-	}
-
-	for _, bin := range bins {
-		if !verifyBin(bin, ps) {
-			printlnWarn(fmt.Sprintf("%s is not running", bin))
-		}
-	}
-
-	for _, conf := range confs {
-		_, err := os.Stat(conf)
-		if err != nil {
-			if os.IsNotExist(err) {
-				printlnWarn(fmt.Sprintf("Missing kubernetes config file: %s", conf))
-			} else {
-				exitWithError(fmt.Errorf("error looking for file %s: %v", conf, err))
-			}
-		}
-	}
-}
-
 // colorPrint outputs the state in a specific colour, along with a message string
 func colorPrint(state check.State, s string) {
 	colors[state].Printf("[%s] ", state)
@@ -190,8 +134,6 @@ func colorPrint(state check.State, s string) {
 
 // prettyPrint outputs the results to stdout in human-readable format
 func prettyPrint(r *check.Controls, summary check.Summary) {
-	colorPrint(check.INFO, fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
-
 	colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Text))
 	for _, g := range r.Groups {
 		colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text))
diff --git a/cmd/root.go b/cmd/root.go
index aed8b422c030a4a7e26d322993b63e3e30087c95..601804e088a69a8e2abb3e91d9474aea483b9c36 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -34,14 +34,6 @@ var (
 	masterFile    string
 	nodeFile      string
 	federatedFile string
-
-	loud bool
-
-	kubeConfDir     string
-	etcdConfDir     string
-	flanneldConfDir string
-
-	installation string
 )
 
 // RootCmd represents the base command when called without any subcommands
@@ -67,12 +59,6 @@ func init() {
 	cobra.OnInitialize(initConfig)
 
 	RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
-	RootCmd.PersistentFlags().StringVar(
-		&installation,
-		"installation",
-		"default",
-		"Specify how kubernetes cluster was installed. Possible values are default,hyperkube,kops,kubeadm",
-	)
 	RootCmd.PersistentFlags().StringVarP(
 		&checkList,
 		"check",
diff --git a/cmd/util.go b/cmd/util.go
index 478ae21ce75796cfff0834afbe75575b312ee009..4d96c514cc87568fabb7815e5b38a8e3883bcf87 100644
--- a/cmd/util.go
+++ b/cmd/util.go
@@ -10,6 +10,7 @@ import (
 	"github.com/aquasecurity/kube-bench/check"
 	"github.com/fatih/color"
 	"github.com/golang/glog"
+	"github.com/spf13/viper"
 )
 
 var (
@@ -22,6 +23,14 @@ var (
 	}
 )
 
+var psFunc func(string) string
+var statFunc func(string) (os.FileInfo, error)
+
+func init() {
+	psFunc = ps
+	statFunc = os.Stat
+}
+
 func printlnWarn(msg string) {
 	fmt.Fprintf(os.Stderr, "[%s] %s\n",
 		colors[check.WARN].Sprintf("%s", check.WARN),
@@ -43,7 +52,7 @@ func exitWithError(err error) {
 
 func continueWithError(err error, msg string) string {
 	if err != nil {
-		glog.V(1).Info(err)
+		glog.V(2).Info(err)
 	}
 
 	if msg != "" {
@@ -75,8 +84,71 @@ func ps(proc string) string {
 	return string(out)
 }
 
+// getBinaries finds which of the set of candidate executables are running
+func getBinaries(v *viper.Viper) map[string]string {
+	binmap := make(map[string]string)
+
+	for _, component := range v.GetStringSlice("components") {
+		s := v.Sub(component)
+		if s == nil {
+			continue
+		}
+
+		optional := s.GetBool("optional")
+		bins := s.GetStringSlice("bins")
+		if len(bins) > 0 {
+			bin, err := findExecutable(bins)
+			if err != nil && !optional {
+				exitWithError(fmt.Errorf("need %s executable but none of the candidates are running", component))
+			}
+
+			// Default the executable name that we'll substitute to the name of the component
+			if bin == "" {
+				bin = component
+				glog.V(2).Info(fmt.Sprintf("Component %s not running", component))
+			} else {
+				glog.V(2).Info(fmt.Sprintf("Component %s uses running binary %s", component, bin))
+			}
+			binmap[component] = bin
+		}
+	}
+
+	return binmap
+}
+
+// getConfigFiles finds which of the set of candidate config files exist
+func getConfigFiles(v *viper.Viper) map[string]string {
+	confmap := make(map[string]string)
+
+	for _, component := range v.GetStringSlice("components") {
+		s := v.Sub(component)
+		if s == nil {
+			continue
+		}
+
+		// See if any of the candidate config files exist
+		conf := findConfigFile(s.GetStringSlice("confs"))
+		if conf == "" {
+			if s.IsSet("defaultconf") {
+				conf = s.GetString("defaultconf")
+				glog.V(2).Info(fmt.Sprintf("Using default config file name '%s' for component %s", conf, component))
+			} else {
+				// Default the config file name that we'll substitute to the name of the component
+				printlnWarn(fmt.Sprintf("Missing config file for %s", component))
+				conf = component
+			}
+		} else {
+			glog.V(2).Info(fmt.Sprintf("Component %s uses config file '%s'", component, conf))
+		}
+
+		confmap[component] = conf
+	}
+
+	return confmap
+}
+
 // verifyBin checks that the binary specified is running
-func verifyBin(bin string, psFunc func(string) string) bool {
+func verifyBin(bin string) bool {
 
 	// Strip any quotes
 	bin = strings.Trim(bin, "'\"")
@@ -87,7 +159,47 @@ func verifyBin(bin string, psFunc func(string) string) bool {
 	proc := strings.Fields(bin)[0]
 	out := psFunc(proc)
 
-	return strings.Contains(out, bin)
+	// There could be multiple lines in the ps output
+	// The binary needs to be the first word in the ps output, except that it could be preceded by a path
+	// e.g. /usr/bin/kubelet is a match for kubelet
+	// but apiserver is not a match for kube-apiserver
+	reFirstWord := regexp.MustCompile(`^(\S*\/)*` + bin)
+	lines := strings.Split(out, "\n")
+	for _, l := range lines {
+		if reFirstWord.Match([]byte(l)) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// fundConfigFile looks through a list of possible config files and finds the first one that exists
+func findConfigFile(candidates []string) string {
+	for _, c := range candidates {
+		_, err := statFunc(c)
+		if err == nil {
+			return c
+		}
+		if !os.IsNotExist(err) {
+			exitWithError(fmt.Errorf("error looking for file %s: %v", c, err))
+		}
+	}
+
+	return ""
+}
+
+// findExecutable looks through a list of possible executable names and finds the first one that's running
+func findExecutable(candidates []string) (string, error) {
+	for _, c := range candidates {
+		if verifyBin(c) {
+			return c, nil
+		} else {
+			glog.V(1).Info(fmt.Sprintf("executable '%s' not running", c))
+		}
+	}
+
+	return "", fmt.Errorf("no candidates running")
 }
 
 func verifyKubeVersion(major string, minor string) {
@@ -159,3 +271,17 @@ func multiWordReplace(s string, subname string, sub string) string {
 
 	return strings.Replace(s, subname, sub, -1)
 }
+
+func makeSubstitutions(s string, ext string, m map[string]string) string {
+	for k, v := range m {
+		subst := "$" + k + ext
+		if v == "" {
+			glog.V(2).Info(fmt.Sprintf("No subsitution for '%s'\n", subst))
+			continue
+		}
+		glog.V(1).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v))
+		s = multiWordReplace(s, subst, v)
+	}
+
+	return s
+}
diff --git a/cmd/util_test.go b/cmd/util_test.go
index dbd434b533e04ab68631670bb21a737fcdbe085f..1f3e5a2454e7c1477ee24a02fe80b500541c0519 100644
--- a/cmd/util_test.go
+++ b/cmd/util_test.go
@@ -15,9 +15,13 @@
 package cmd
 
 import (
+	"os"
+	"reflect"
 	"regexp"
 	"strconv"
 	"testing"
+
+	"github.com/spf13/viper"
 )
 
 func TestCheckVersion(t *testing.T) {
@@ -78,10 +82,19 @@ func TestVersionMatch(t *testing.T) {
 }
 
 var g string
+var e []error
+var eIndex int
 
 func fakeps(proc string) string {
 	return g
 }
+
+func fakestat(file string) (os.FileInfo, error) {
+	err := e[eIndex]
+	eIndex++
+	return nil, err
+}
+
 func TestVerifyBin(t *testing.T) {
 	cases := []struct {
 		proc  string
@@ -95,12 +108,18 @@ func TestVerifyBin(t *testing.T) {
 		{proc: "cmd", psOut: "cmd param1 param2", exp: true},
 		{proc: "cmd param", psOut: "cmd param1 param2", exp: true},
 		{proc: "cmd param", psOut: "cmd", exp: false},
+		{proc: "cmd", psOut: "cmd x \ncmd y", exp: true},
+		{proc: "cmd y", psOut: "cmd x \ncmd y", exp: true},
+		{proc: "cmd", psOut: "/usr/bin/cmd", exp: true},
+		{proc: "cmd", psOut: "kube-cmd", exp: false},
+		{proc: "cmd", psOut: "/usr/bin/kube-cmd", exp: false},
 	}
 
+	psFunc = fakeps
 	for id, c := range cases {
 		t.Run(strconv.Itoa(id), func(t *testing.T) {
 			g = c.psOut
-			v := verifyBin(c.proc, fakeps)
+			v := verifyBin(c.proc)
 			if v != c.exp {
 				t.Fatalf("Expected %v got %v", c.exp, v)
 			}
@@ -108,6 +127,96 @@ func TestVerifyBin(t *testing.T) {
 	}
 }
 
+func TestFindExecutable(t *testing.T) {
+	cases := []struct {
+		candidates []string // list of executables we'd consider
+		psOut      string   // fake output from ps
+		exp        string   // the one we expect to find in the (fake) ps output
+		expErr     bool
+	}{
+		{candidates: []string{"one", "two", "three"}, psOut: "two", exp: "two"},
+		{candidates: []string{"one", "two", "three"}, psOut: "two three", exp: "two"},
+		{candidates: []string{"one double", "two double", "three double"}, psOut: "two double is running", exp: "two double"},
+		{candidates: []string{"one", "two", "three"}, psOut: "blah", expErr: true},
+		{candidates: []string{"one double", "two double", "three double"}, psOut: "two", expErr: true},
+		{candidates: []string{"apiserver", "kube-apiserver"}, psOut: "kube-apiserver", exp: "kube-apiserver"},
+		{candidates: []string{"apiserver", "kube-apiserver", "hyperkube-apiserver"}, psOut: "kube-apiserver", exp: "kube-apiserver"},
+	}
+
+	psFunc = fakeps
+	for id, c := range cases {
+		t.Run(strconv.Itoa(id), func(t *testing.T) {
+			g = c.psOut
+			e, err := findExecutable(c.candidates)
+			if e != c.exp {
+				t.Fatalf("Expected %v got %v", c.exp, e)
+			}
+
+			if err == nil && c.expErr {
+				t.Fatalf("Expected error")
+			}
+
+			if err != nil && !c.expErr {
+				t.Fatalf("Didn't expect error: %v", err)
+			}
+		})
+	}
+}
+
+func TestGetBinaries(t *testing.T) {
+	cases := []struct {
+		config map[string]interface{}
+		psOut  string
+		exp    map[string]string
+	}{
+		{
+			config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}},
+			psOut:  "kube-apiserver",
+			exp:    map[string]string{"apiserver": "kube-apiserver"},
+		},
+		{
+			// "thing" is not in the list of components
+			config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
+			psOut:  "kube-apiserver thing",
+			exp:    map[string]string{"apiserver": "kube-apiserver"},
+		},
+		{
+			// "anotherthing" in list of components but doesn't have a defintion
+			config: map[string]interface{}{"components": []string{"apiserver", "anotherthing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
+			psOut:  "kube-apiserver thing",
+			exp:    map[string]string{"apiserver": "kube-apiserver"},
+		},
+		{
+			// more than one component
+			config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
+			psOut:  "kube-apiserver \nthing",
+			exp:    map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
+		},
+		{
+			// default binary to component name
+			config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}},
+			psOut:  "kube-apiserver \notherthing some params",
+			exp:    map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
+		},
+	}
+
+	v := viper.New()
+	psFunc = fakeps
+
+	for id, c := range cases {
+		t.Run(strconv.Itoa(id), func(t *testing.T) {
+			g = c.psOut
+			for k, val := range c.config {
+				v.Set(k, val)
+			}
+			m := getBinaries(v)
+			if !reflect.DeepEqual(m, c.exp) {
+				t.Fatalf("Got %v\nExpected %v", m, c.exp)
+			}
+		})
+	}
+}
+
 func TestMultiWordReplace(t *testing.T) {
 	cases := []struct {
 		input   string
@@ -129,3 +238,115 @@ func TestMultiWordReplace(t *testing.T) {
 		})
 	}
 }
+
+func TestFindConfigFile(t *testing.T) {
+	cases := []struct {
+		input       []string
+		statResults []error
+		exp         string
+	}{
+		{input: []string{"myfile"}, statResults: []error{nil}, exp: "myfile"},
+		{input: []string{"thisfile", "thatfile"}, statResults: []error{os.ErrNotExist, nil}, exp: "thatfile"},
+		{input: []string{"thisfile", "thatfile"}, statResults: []error{os.ErrNotExist, os.ErrNotExist}, exp: ""},
+	}
+
+	statFunc = fakestat
+	for id, c := range cases {
+		t.Run(strconv.Itoa(id), func(t *testing.T) {
+			e = c.statResults
+			eIndex = 0
+			conf := findConfigFile(c.input)
+			if conf != c.exp {
+				t.Fatalf("Got %s expected %s", conf, c.exp)
+			}
+		})
+	}
+}
+
+func TestGetConfigFiles(t *testing.T) {
+	cases := []struct {
+		config      map[string]interface{}
+		exp         map[string]string
+		statResults []error
+	}{
+		{
+			config:      map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}}},
+			statResults: []error{os.ErrNotExist, nil},
+			exp:         map[string]string{"apiserver": "kube-apiserver"},
+		},
+		{
+			// Component "thing" isn't included in the list of components
+			config: map[string]interface{}{
+				"components": []string{"apiserver"},
+				"apiserver":  map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
+				"thing":      map[string]interface{}{"confs": []string{"/my/file/thing"}}},
+			statResults: []error{os.ErrNotExist, nil},
+			exp:         map[string]string{"apiserver": "kube-apiserver"},
+		},
+		{
+			// More than one component
+			config: map[string]interface{}{
+				"components": []string{"apiserver", "thing"},
+				"apiserver":  map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
+				"thing":      map[string]interface{}{"confs": []string{"/my/file/thing"}}},
+			statResults: []error{os.ErrNotExist, nil, nil},
+			exp:         map[string]string{"apiserver": "kube-apiserver", "thing": "/my/file/thing"},
+		},
+		{
+			// Default thing to specified default config
+			config: map[string]interface{}{
+				"components": []string{"apiserver", "thing"},
+				"apiserver":  map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
+				"thing":      map[string]interface{}{"confs": []string{"/my/file/thing"}, "defaultconf": "another/thing"}},
+			statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
+			exp:         map[string]string{"apiserver": "kube-apiserver", "thing": "another/thing"},
+		},
+		{
+			// Default thing to component name
+			config: map[string]interface{}{
+				"components": []string{"apiserver", "thing"},
+				"apiserver":  map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
+				"thing":      map[string]interface{}{"confs": []string{"/my/file/thing"}}},
+			statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
+			exp:         map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
+		},
+	}
+
+	v := viper.New()
+	statFunc = fakestat
+
+	for id, c := range cases {
+		t.Run(strconv.Itoa(id), func(t *testing.T) {
+			for k, val := range c.config {
+				v.Set(k, val)
+			}
+			e = c.statResults
+			eIndex = 0
+
+			m := getConfigFiles(v)
+			if !reflect.DeepEqual(m, c.exp) {
+				t.Fatalf("Got %v\nExpected %v", m, c.exp)
+			}
+		})
+	}
+}
+
+func TestMakeSubsitutions(t *testing.T) {
+	cases := []struct {
+		input string
+		subst map[string]string
+		exp   string
+	}{
+		{input: "Replace $thisbin", subst: map[string]string{"this": "that"}, exp: "Replace that"},
+		{input: "Replace $thisbin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that"},
+		{input: "Replace $thisbin and $herebin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that and there"},
+	}
+	for _, c := range cases {
+		t.Run(c.input, func(t *testing.T) {
+			s := makeSubstitutions(c.input, "bin", c.subst)
+			if s != c.exp {
+				t.Fatalf("Got %s expected %s", s, c.exp)
+			}
+		})
+	}
+}