mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2024-11-26 18:08:15 +00:00
Move cmd/ source code up one level.
This commit is contained in:
parent
47fe0f38ef
commit
5dba85ca47
180
common.go
Normal file
180
common.go
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aquasecurity/bench-common/check"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
errmsgs string
|
||||
)
|
||||
|
||||
func runChecks(nodetype nodeType) {
|
||||
var summary check.Summary
|
||||
var file string
|
||||
var err error
|
||||
var typeConf *viper.Viper
|
||||
|
||||
switch nodetype {
|
||||
case MASTER:
|
||||
file = masterFile
|
||||
case NODE:
|
||||
file = nodeFile
|
||||
case FEDERATED:
|
||||
file = federatedFile
|
||||
}
|
||||
|
||||
runningVersion, err := getKubeVersion()
|
||||
if err != nil && kubeVersion == "" {
|
||||
exitWithError(fmt.Errorf("Version check failed: %s\nAlternatively, you can specify the version with --version", err))
|
||||
}
|
||||
path, err := getConfigFilePath(kubeVersion, runningVersion, file)
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("can't find %s controls file in %s: %v", nodetype, cfgDir, err))
|
||||
}
|
||||
|
||||
def := filepath.Join(path, file)
|
||||
in, err := ioutil.ReadFile(def)
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("error opening %s controls file: %v", nodetype, err))
|
||||
}
|
||||
|
||||
glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def))
|
||||
|
||||
// Merge kubernetes version specific config if any.
|
||||
viper.SetConfigFile(path + "/config.yaml")
|
||||
err = viper.MergeInConfig()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
glog.V(2).Info(fmt.Sprintf("No version-specific config.yaml file in %s", path))
|
||||
} else {
|
||||
exitWithError(fmt.Errorf("couldn't read config file %s: %v", path+"/config.yaml", err))
|
||||
}
|
||||
} else {
|
||||
glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
|
||||
}
|
||||
|
||||
// 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.
|
||||
typeConf = viper.Sub(string(nodetype))
|
||||
binmap := getBinaries(typeConf)
|
||||
confmap := getConfigFiles(typeConf)
|
||||
|
||||
// Variable substitutions. Replace all occurrences of variables in controls files.
|
||||
s := string(in)
|
||||
s = makeSubstitutions(s, "bin", binmap)
|
||||
s = makeSubstitutions(s, "conf", confmap)
|
||||
|
||||
controls, err := check.NewControls([]byte(s))
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("error setting up %s controls: %v", nodetype, err))
|
||||
}
|
||||
|
||||
if groupList != "" && checkList == "" {
|
||||
ids := cleanIDs(groupList)
|
||||
summary = controls.RunGroup(ids...)
|
||||
} else if checkList != "" && groupList == "" {
|
||||
ids := cleanIDs(checkList)
|
||||
summary = controls.RunChecks(ids...)
|
||||
} else if checkList != "" && groupList != "" {
|
||||
exitWithError(fmt.Errorf("group option and check option can't be used together"))
|
||||
} else {
|
||||
summary = controls.RunGroup()
|
||||
}
|
||||
|
||||
// if we successfully ran some tests and it's json format, ignore the warnings
|
||||
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0) && jsonFmt {
|
||||
out, err := controls.JSON()
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("failed to output in JSON format: %v", err))
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
} else {
|
||||
// if we want to store in PostgreSQL, convert to JSON and save it
|
||||
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0) && pgSQL {
|
||||
out, err := controls.JSON()
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("failed to output in JSON format: %v", err))
|
||||
}
|
||||
|
||||
savePgsql(string(out))
|
||||
} else {
|
||||
prettyPrint(controls, summary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
fmt.Printf("%s", s)
|
||||
}
|
||||
|
||||
// prettyPrint outputs the results to stdout in human-readable format
|
||||
func prettyPrint(r *check.Controls, summary check.Summary) {
|
||||
// Print check results.
|
||||
if !noResults {
|
||||
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Description))
|
||||
for _, g := range r.Groups {
|
||||
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Description))
|
||||
for _, c := range g.Checks {
|
||||
colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Description))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Print remediations.
|
||||
if !noRemediations {
|
||||
if summary.Fail > 0 || summary.Warn > 0 {
|
||||
colors[check.WARN].Printf("== Remediations ==\n")
|
||||
for _, g := range r.Groups {
|
||||
for _, c := range g.Checks {
|
||||
if c.State != check.PASS {
|
||||
fmt.Printf("%s %s\n", c.ID, c.Remediation)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// Print summary setting output color to highest severity.
|
||||
if !noSummary {
|
||||
var res check.State
|
||||
if summary.Fail > 0 {
|
||||
res = check.FAIL
|
||||
} else if summary.Warn > 0 {
|
||||
res = check.WARN
|
||||
} else {
|
||||
res = check.PASS
|
||||
}
|
||||
|
||||
colors[res].Printf("== Summary ==\n")
|
||||
fmt.Printf("%d checks PASS\n%d checks FAIL\n%d checks WARN\n",
|
||||
summary.Pass, summary.Fail, summary.Warn,
|
||||
)
|
||||
}
|
||||
}
|
60
database.go
Normal file
60
database.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres" // database packages get blank imports
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func savePgsql(jsonInfo string) {
|
||||
envVars := map[string]string{
|
||||
"PGSQL_HOST": viper.GetString("PGSQL_HOST"),
|
||||
"PGSQL_USER": viper.GetString("PGSQL_USER"),
|
||||
"PGSQL_DBNAME": viper.GetString("PGSQL_DBNAME"),
|
||||
"PGSQL_SSLMODE": viper.GetString("PGSQL_SSLMODE"),
|
||||
"PGSQL_PASSWORD": viper.GetString("PGSQL_PASSWORD"),
|
||||
}
|
||||
|
||||
for k, v := range envVars {
|
||||
if v == "" {
|
||||
exitWithError(fmt.Errorf("environment variable %s is missing", envVarsPrefix+"_"+k))
|
||||
}
|
||||
}
|
||||
|
||||
connInfo := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s password=%s",
|
||||
envVars["PGSQL_HOST"],
|
||||
envVars["PGSQL_USER"],
|
||||
envVars["PGSQL_DBNAME"],
|
||||
envVars["PGSQL_SSLMODE"],
|
||||
envVars["PGSQL_PASSWORD"],
|
||||
)
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("received error looking up hostname: %s", err))
|
||||
}
|
||||
|
||||
timestamp := time.Now()
|
||||
|
||||
type ScanResult struct {
|
||||
gorm.Model
|
||||
ScanHost string `gorm:"type:varchar(63) not null"` // https://www.ietf.org/rfc/rfc1035.txt
|
||||
ScanTime time.Time `gorm:"not null"`
|
||||
ScanInfo string `gorm:"type:jsonb not null"`
|
||||
}
|
||||
|
||||
db, err := gorm.Open("postgres", connInfo)
|
||||
defer db.Close()
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("received error connecting to database: %s", err))
|
||||
}
|
||||
|
||||
db.Debug().AutoMigrate(&ScanResult{})
|
||||
db.Save(&ScanResult{ScanHost: hostname, ScanTime: timestamp, ScanInfo: jsonInfo})
|
||||
glog.V(2).Info(fmt.Sprintf("successfully stored result to: %s", envVars["PGSQL_HOST"]))
|
||||
}
|
40
federated.go
Normal file
40
federated.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// nodeCmd represents the node command
|
||||
var federatedCmd = &cobra.Command{
|
||||
Use: "federated",
|
||||
Short: "Run benchmark checks for a Kubernetes federated deployment.",
|
||||
Long: `Run benchmark checks for a Kubernetes federated deployment.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runChecks(FEDERATED)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
federatedCmd.PersistentFlags().StringVarP(&federatedFile,
|
||||
"file",
|
||||
"f",
|
||||
"/federated.yaml",
|
||||
"Alternative YAML file for federated checks",
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(federatedCmd)
|
||||
}
|
6
main.go
6
main.go
@ -14,10 +14,6 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/kube-bench/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
Execute()
|
||||
}
|
||||
|
40
master.go
Normal file
40
master.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// masterCmd represents the master command
|
||||
var masterCmd = &cobra.Command{
|
||||
Use: "master",
|
||||
Short: "Run benchmark checks for a Kubernetes master node.",
|
||||
Long: `Run benchmark checks for a Kubernetes master node.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runChecks(MASTER)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
masterCmd.PersistentFlags().StringVarP(&masterFile,
|
||||
"file",
|
||||
"f",
|
||||
"/master.yaml",
|
||||
"Alternative YAML file for master checks",
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(masterCmd)
|
||||
}
|
40
node.go
Normal file
40
node.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// nodeCmd represents the node command
|
||||
var nodeCmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Run benchmark checks for a Kubernetes node.",
|
||||
Long: `Run benchmark checks for a Kubernetes node.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runChecks(NODE)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
nodeCmd.PersistentFlags().StringVarP(&nodeFile,
|
||||
"file",
|
||||
"f",
|
||||
"/node.yaml",
|
||||
"Alternative YAML file for node checks",
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(nodeCmd)
|
||||
}
|
115
root.go
Normal file
115
root.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aquasecurity/bench-common/check"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
envVarsPrefix = "KUBE_BENCH"
|
||||
defaultKubeVersion = "1.6"
|
||||
kubeVersion string
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
jsonFmt bool
|
||||
pgSQL bool
|
||||
checkList string
|
||||
groupList string
|
||||
masterFile string
|
||||
nodeFile string
|
||||
federatedFile string
|
||||
noResults bool
|
||||
noSummary bool
|
||||
noRemediations bool
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: os.Args[0],
|
||||
Short: "Run CIS Benchmarks checks against a Kubernetes deployment",
|
||||
Long: `This tool runs the CIS Kubernetes Benchmark (http://www.cisecurity.org/benchmark/kubernetes/)`,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
goflag.Set("logtostderr", "true")
|
||||
goflag.CommandLine.Parse([]string{})
|
||||
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Output control
|
||||
RootCmd.PersistentFlags().BoolVar(&noResults, "noresults", false, "Disable printing of results section")
|
||||
RootCmd.PersistentFlags().BoolVar(&noSummary, "nosummary", false, "Disable printing of summary section")
|
||||
RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section")
|
||||
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
|
||||
RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
|
||||
|
||||
RootCmd.PersistentFlags().StringVarP(
|
||||
&checkList,
|
||||
"check",
|
||||
"c",
|
||||
"",
|
||||
`A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"`,
|
||||
)
|
||||
RootCmd.PersistentFlags().StringVarP(
|
||||
&groupList,
|
||||
"group",
|
||||
"g",
|
||||
"",
|
||||
`Run all the checks under this comma-delimited list of groups. Example --group="1.1"`,
|
||||
)
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./cfg/config.yaml)")
|
||||
RootCmd.PersistentFlags().StringVarP(&cfgDir, "config-dir", "D", "./cfg/", "config directory")
|
||||
RootCmd.PersistentFlags().StringVar(&kubeVersion, "version", "", "Manually specify Kubernetes version, automatically detected if unset")
|
||||
|
||||
goflag.CommandLine.VisitAll(func(goflag *goflag.Flag) {
|
||||
RootCmd.PersistentFlags().AddGoFlag(goflag)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" { // enable ability to specify config file via flag
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
viper.SetConfigName("config") // name of config file (without extension)
|
||||
viper.AddConfigPath(cfgDir) // adding ./cfg as first search path
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix(envVarsPrefix)
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
colorPrint(check.FAIL, fmt.Sprintf("Failed to read config file: %v\n", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
12
types.go
Normal file
12
types.go
Normal file
@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
type nodeType string
|
||||
|
||||
const (
|
||||
// MASTER a master node
|
||||
MASTER nodeType = "master"
|
||||
// NODE a node
|
||||
NODE nodeType = "node"
|
||||
// FEDERATED a federated deployment.
|
||||
FEDERATED nodeType = "federated"
|
||||
)
|
338
util.go
Normal file
338
util.go
Normal file
@ -0,0 +1,338 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/bench-common/check"
|
||||
"github.com/fatih/color"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
// Print colors
|
||||
colors = map[check.State]*color.Color{
|
||||
check.PASS: color.New(color.FgGreen),
|
||||
check.FAIL: color.New(color.FgRed),
|
||||
check.WARN: color.New(color.FgYellow),
|
||||
check.INFO: color.New(color.FgBlue),
|
||||
}
|
||||
)
|
||||
|
||||
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),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
func sprintlnWarn(msg string) string {
|
||||
return fmt.Sprintf("[%s] %s",
|
||||
colors[check.WARN].Sprintf("%s", check.WARN),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
func exitWithError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func continueWithError(err error, msg string) string {
|
||||
if err != nil {
|
||||
glog.V(2).Info(err)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func cleanIDs(list string) []string {
|
||||
list = strings.Trim(list, ",")
|
||||
ids := strings.Split(list, ",")
|
||||
|
||||
for _, id := range ids {
|
||||
id = strings.Trim(id, " ")
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// ps execs out to the ps command; it's separated into a function so we can write tests
|
||||
func ps(proc string) string {
|
||||
cmd := exec.Command("ps", "-C", proc, "-o", "cmd", "--no-headers")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
continueWithError(fmt.Errorf("%s: %s", cmd.Args, err), "")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// getConfigFilePath locates the config files we should be using based on either the specified
|
||||
// version, or the running version of kubernetes if not specified
|
||||
func getConfigFilePath(specifiedVersion string, runningVersion string, filename string) (path string, err error) {
|
||||
var fileVersion string
|
||||
|
||||
if specifiedVersion != "" {
|
||||
fileVersion = specifiedVersion
|
||||
} else {
|
||||
fileVersion = runningVersion
|
||||
}
|
||||
|
||||
glog.V(2).Info(fmt.Sprintf("Looking for config for version %s", fileVersion))
|
||||
|
||||
for {
|
||||
path = filepath.Join(cfgDir, fileVersion)
|
||||
file := filepath.Join(path, string(filename))
|
||||
glog.V(2).Info(fmt.Sprintf("Looking for config file: %s\n", file))
|
||||
|
||||
if _, err = os.Stat(file); !os.IsNotExist(err) {
|
||||
if specifiedVersion == "" && fileVersion != runningVersion {
|
||||
glog.V(1).Info(fmt.Sprintf("No test file found for %s - using tests for Kubernetes %s\n", runningVersion, fileVersion))
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// If we were given an explicit version to look for, don't look for any others
|
||||
if specifiedVersion != "" {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fileVersion = decrementVersion(fileVersion)
|
||||
if fileVersion == "" {
|
||||
return "", fmt.Errorf("no test files found <= runningVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decrementVersion decrements the version number
|
||||
// We want to decrement individually even through versions where we don't supply test files
|
||||
// just in case someone wants to specify their own test files for that version
|
||||
func decrementVersion(version string) string {
|
||||
split := strings.Split(version, ".")
|
||||
minor, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if minor <= 1 {
|
||||
return ""
|
||||
}
|
||||
split[1] = strconv.Itoa(minor - 1)
|
||||
return strings.Join(split, ".")
|
||||
}
|
||||
|
||||
// getConfigFiles finds which of the set of candidate config files exist
|
||||
// accepts a string 't' which indicates the type of config file, conf,
|
||||
// podspec or untifile.
|
||||
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
|
||||
glog.V(2).Info(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) bool {
|
||||
|
||||
// Strip any quotes
|
||||
bin = strings.Trim(bin, "'\"")
|
||||
|
||||
// bin could consist of more than one word
|
||||
// We'll search for running processes with the first word, and then check the whole
|
||||
// proc as supplied is included in the results
|
||||
proc := strings.Fields(bin)[0]
|
||||
out := psFunc(proc)
|
||||
|
||||
// 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
|
||||
}
|
||||
glog.V(1).Info(fmt.Sprintf("executable '%s' not running", c))
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no candidates running")
|
||||
}
|
||||
|
||||
func multiWordReplace(s string, subname string, sub string) string {
|
||||
f := strings.Fields(sub)
|
||||
if len(f) > 1 {
|
||||
sub = "'" + sub + "'"
|
||||
}
|
||||
|
||||
return strings.Replace(s, subname, sub, -1)
|
||||
}
|
||||
|
||||
func getKubeVersion() (string, error) {
|
||||
// These executables might not be on the user's path.
|
||||
_, err := exec.LookPath("kubectl")
|
||||
|
||||
if err != nil {
|
||||
_, err = exec.LookPath("kubelet")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("need kubectl or kubelet binaries to get kubernetes version")
|
||||
}
|
||||
return getKubeVersionFromKubelet(), nil
|
||||
}
|
||||
|
||||
return getKubeVersionFromKubectl(), nil
|
||||
}
|
||||
|
||||
func getKubeVersionFromKubectl() string {
|
||||
cmd := exec.Command("kubectl", "version", "--short")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
continueWithError(fmt.Errorf("%s", out), "")
|
||||
}
|
||||
|
||||
return getVersionFromKubectlOutput(string(out))
|
||||
}
|
||||
|
||||
func getKubeVersionFromKubelet() string {
|
||||
cmd := exec.Command("kubelet", "--version")
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
continueWithError(fmt.Errorf("%s", out), "")
|
||||
}
|
||||
|
||||
return getVersionFromKubeletOutput(string(out))
|
||||
}
|
||||
|
||||
func getVersionFromKubectlOutput(s string) string {
|
||||
serverVersionRe := regexp.MustCompile(`Server Version: v(\d+.\d+)`)
|
||||
subs := serverVersionRe.FindStringSubmatch(s)
|
||||
if len(subs) < 2 {
|
||||
printlnWarn(fmt.Sprintf("Unable to get kubectl version, using default version: %s", defaultKubeVersion))
|
||||
return defaultKubeVersion
|
||||
}
|
||||
return subs[1]
|
||||
}
|
||||
|
||||
func getVersionFromKubeletOutput(s string) string {
|
||||
serverVersionRe := regexp.MustCompile(`Kubernetes v(\d+.\d+)`)
|
||||
subs := serverVersionRe.FindStringSubmatch(s)
|
||||
if len(subs) < 2 {
|
||||
printlnWarn(fmt.Sprintf("Unable to get kubelet version, using default version: %s", defaultKubeVersion))
|
||||
return defaultKubeVersion
|
||||
}
|
||||
return subs[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(2).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v))
|
||||
s = multiWordReplace(s, subst, v)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
352
util_test.go
Normal file
352
util_test.go
Normal file
@ -0,0 +1,352 @@
|
||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
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
|
||||
psOut string
|
||||
exp bool
|
||||
}{
|
||||
{proc: "single", psOut: "single", exp: true},
|
||||
{proc: "single", psOut: "", exp: false},
|
||||
{proc: "two words", psOut: "two words", exp: true},
|
||||
{proc: "two words", psOut: "", exp: false},
|
||||
{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)
|
||||
if v != c.exp {
|
||||
t.Fatalf("Expected %v got %v", c.exp, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
sub string
|
||||
subname string
|
||||
output string
|
||||
}{
|
||||
{input: "Here's a file with no substitutions", sub: "blah", subname: "blah", output: "Here's a file with no substitutions"},
|
||||
{input: "Here's a file with a substitution", sub: "blah", subname: "substitution", output: "Here's a file with a blah"},
|
||||
{input: "Here's a file with multi-word substitutions", sub: "multi word", subname: "multi-word", output: "Here's a file with 'multi word' substitutions"},
|
||||
{input: "Here's a file with several several substitutions several", sub: "blah", subname: "several", output: "Here's a file with blah blah substitutions blah"},
|
||||
}
|
||||
for id, c := range cases {
|
||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
||||
s := multiWordReplace(c.input, c.subname, c.sub)
|
||||
if s != c.output {
|
||||
t.Fatalf("Expected %s got %s", c.output, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubeVersionRegex(t *testing.T) {
|
||||
ver := getVersionFromKubectlOutput(`Client Version: v1.8.0
|
||||
Server Version: v1.8.12
|
||||
`)
|
||||
if ver != "1.8" {
|
||||
t.Fatalf("Expected 1.8 got %s", ver)
|
||||
}
|
||||
|
||||
ver = getVersionFromKubectlOutput("Something completely different")
|
||||
if ver != "1.6" {
|
||||
t.Fatalf("Expected 1.6 got %s", ver)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigFilePath(t *testing.T) {
|
||||
var err error
|
||||
cfgDir, err = ioutil.TempDir("", "kube-bench-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory")
|
||||
}
|
||||
defer os.RemoveAll(cfgDir)
|
||||
d := filepath.Join(cfgDir, "1.8")
|
||||
err = os.Mkdir(d, 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file")
|
||||
}
|
||||
ioutil.WriteFile(filepath.Join(d, "master.yaml"), []byte("hello world"), 0666)
|
||||
|
||||
cases := []struct {
|
||||
specifiedVersion string
|
||||
runningVersion string
|
||||
succeed bool
|
||||
exp string
|
||||
}{
|
||||
{runningVersion: "1.8", succeed: true, exp: d},
|
||||
{runningVersion: "1.9", succeed: true, exp: d},
|
||||
{runningVersion: "1.10", succeed: true, exp: d},
|
||||
{runningVersion: "1.1", succeed: false},
|
||||
{specifiedVersion: "1.8", succeed: true, exp: d},
|
||||
{specifiedVersion: "1.9", succeed: false},
|
||||
{specifiedVersion: "1.10", succeed: false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.specifiedVersion+"-"+c.runningVersion, func(t *testing.T) {
|
||||
path, err := getConfigFilePath(c.specifiedVersion, c.runningVersion, "/master.yaml")
|
||||
if err != nil && c.succeed {
|
||||
t.Fatalf("Error %v", err)
|
||||
}
|
||||
if path != c.exp {
|
||||
t.Fatalf("Got %s expected %s", path, c.exp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user