1
0
Fork 0
mirror of https://github.com/kastenhq/kubestr.git synced 2024-12-14 11:57:56 +00:00

Separate JSON output, emit non-zero exit code (#82)

* Allow for printing FIO and CSICheck JSON results to file

Fixes #79

* Use the file output function in Baseline too, remove double printing

* Emit non-zero exit code when error has occurred

Fixes #80

* Don't output twice in error case

* Added godoc and a note in command help text

* Eliminate weird variable names, make godoc clearer

* Error handling for file output
This commit is contained in:
Markus Vuorio 2021-10-07 20:55:17 +03:00 committed by GitHub
parent 798242c5bd
commit 4d3cb00b76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 39 deletions

View file

@ -18,6 +18,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"time" "time"
"github.com/kastenhq/kubestr/pkg/csi" "github.com/kastenhq/kubestr/pkg/csi"
@ -29,16 +30,18 @@ import (
var ( var (
output string output string
outfile string
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "kubestr", Use: "kubestr",
Short: "A tool to validate kubernetes storage", Short: "A tool to validate kubernetes storage",
Long: `kubestr is a tool that will scan your k8s cluster Long: `kubestr is a tool that will scan your k8s cluster
and validate that the storage systems in place as well as run and validate that the storage systems in place as well as run
performance tests.`, performance tests.`,
Run: func(cmd *cobra.Command, args []string) { SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel() defer cancel()
Baseline(ctx, output) return Baseline(ctx, output)
}, },
} }
@ -53,10 +56,10 @@ var (
Use: "fio", Use: "fio",
Short: "Runs an fio test", Short: "Runs an fio test",
Long: `Run an fio test`, Long: `Run an fio test`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel() defer cancel()
Fio(ctx, output, storageClass, fioCheckerSize, namespace, fioCheckerTestName, fioCheckerFilePath, containerImage) return Fio(ctx, output, outfile, storageClass, fioCheckerSize, namespace, fioCheckerTestName, fioCheckerFilePath, containerImage)
}, },
} }
@ -68,21 +71,22 @@ var (
Use: "csicheck", Use: "csicheck",
Short: "Runs the CSI snapshot restore check", Short: "Runs the CSI snapshot restore check",
Long: "Validates a CSI provisioners ability to take a snapshot of an application and restore it", Long: "Validates a CSI provisioners ability to take a snapshot of an application and restore it",
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel() defer cancel()
CSICheck(ctx, output, namespace, storageClass, csiCheckVolumeSnapshotClass, csiCheckRunAsUser, containerImage, csiCheckCleanup, csiCheckSkipCFSCheck) return CSICheck(ctx, output, outfile, namespace, storageClass, csiCheckVolumeSnapshotClass, csiCheckRunAsUser, containerImage, csiCheckCleanup, csiCheckSkipCFSCheck)
}, },
} }
) )
func init() { func init() {
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Options(json)") rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Options(json)")
rootCmd.PersistentFlags().StringVarP(&outfile, "outfile", "e", "", "The file where test results will be written")
rootCmd.AddCommand(fioCmd) rootCmd.AddCommand(fioCmd)
fioCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a Storageclass. (Required)") fioCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a Storageclass. (Required)")
_ = fioCmd.MarkFlagRequired("storageclass") _ = fioCmd.MarkFlagRequired("storageclass")
fioCmd.Flags().StringVarP(&fioCheckerSize, "size", "z", fio.DefaultPVCSize, "The size of the volume used to run FIO.") fioCmd.Flags().StringVarP(&fioCheckerSize, "size", "z", fio.DefaultPVCSize, "The size of the volume used to run FIO. Note that the FIO job definition is not scaled accordingly.")
fioCmd.Flags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace used to run FIO.") fioCmd.Flags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace used to run FIO.")
fioCmd.Flags().StringVarP(&fioCheckerFilePath, "fiofile", "f", "", "The path to a an fio config file.") fioCmd.Flags().StringVarP(&fioCheckerFilePath, "fiofile", "f", "", "The path to a an fio config file.")
fioCmd.Flags().StringVarP(&fioCheckerTestName, "testname", "t", "", "The Name of a predefined kubestr fio test. Options(default-fio)") fioCmd.Flags().StringVarP(&fioCheckerTestName, "testname", "t", "", "The Name of a predefined kubestr fio test. Options(default-fio)")
@ -106,19 +110,19 @@ func Execute() error {
} }
// Baseline executes the baseline check // Baseline executes the baseline check
func Baseline(ctx context.Context, output string) { func Baseline(ctx context.Context, output string) error {
p, err := kubestr.NewKubestr() p, err := kubestr.NewKubestr()
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return err
} }
fmt.Print(kubestr.Logo) fmt.Print(kubestr.Logo)
result := p.KubernetesChecks() result := p.KubernetesChecks()
if output == "json" {
jsonRes, _ := json.MarshalIndent(result, "", " ") if PrintAndJsonOutput(result, output, outfile) {
fmt.Println(string(jsonRes)) return err
return
} }
for _, retval := range result { for _, retval := range result {
retval.Print() retval.Print()
fmt.Println() fmt.Println()
@ -128,13 +132,9 @@ func Baseline(ctx context.Context, output string) {
provisionerList, err := p.ValidateProvisioners(ctx) provisionerList, err := p.ValidateProvisioners(ctx)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return err
}
if output == "json" {
jsonRes, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(jsonRes))
return
} }
fmt.Println("Available Storage Provisioners:") fmt.Println("Available Storage Provisioners:")
fmt.Println() fmt.Println()
time.Sleep(500 * time.Millisecond) // Added to introduce lag. time.Sleep(500 * time.Millisecond) // Added to introduce lag.
@ -143,42 +143,61 @@ func Baseline(ctx context.Context, output string) {
fmt.Println() fmt.Println()
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
return err
}
// PrintAndJsonOutput Print JSON output to stdout and to file if arguments say so
// Returns whether we have generated output or JSON
func PrintAndJsonOutput(result []*kubestr.TestOutput, output string, outfile string) bool {
if output == "json" {
jsonRes, _ := json.MarshalIndent(result, "", " ")
if len(outfile) > 0 {
err := os.WriteFile(outfile, jsonRes, 0666)
if err != nil {
fmt.Println("Error writing output:", err.Error())
os.Exit(2)
}
} else {
fmt.Println(string(jsonRes))
}
return true
}
return false
} }
// Fio executes the FIO test. // Fio executes the FIO test.
func Fio(ctx context.Context, output, storageclass, size, namespace, jobName, fioFilePath string, containerImage string) { func Fio(ctx context.Context, output, outfile, storageclass, size, namespace, jobName, fioFilePath string, containerImage string) error {
cli, err := kubestr.LoadKubeCli() cli, err := kubestr.LoadKubeCli()
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return err
} }
fioRunner := &fio.FIOrunner{ fioRunner := &fio.FIOrunner{
Cli: cli, Cli: cli,
} }
testName := "FIO test results" testName := "FIO test results"
var result *kubestr.TestOutput var result *kubestr.TestOutput
if fioResult, err := fioRunner.RunFio(ctx, &fio.RunFIOArgs{ fioResult, err := fioRunner.RunFio(ctx, &fio.RunFIOArgs{
StorageClass: storageclass, StorageClass: storageclass,
Size: size, Size: size,
Namespace: namespace, Namespace: namespace,
FIOJobName: jobName, FIOJobName: jobName,
FIOJobFilepath: fioFilePath, FIOJobFilepath: fioFilePath,
Image: containerImage, Image: containerImage,
}); err != nil { })
if err != nil {
result = kubestr.MakeTestOutput(testName, kubestr.StatusError, err.Error(), fioResult) result = kubestr.MakeTestOutput(testName, kubestr.StatusError, err.Error(), fioResult)
} else { } else {
result = kubestr.MakeTestOutput(testName, kubestr.StatusOK, fmt.Sprintf("\n%s", fioResult.Result.Print()), fioResult) result = kubestr.MakeTestOutput(testName, kubestr.StatusOK, fmt.Sprintf("\n%s", fioResult.Result.Print()), fioResult)
} }
var wrappedResult = []*kubestr.TestOutput{result}
if output == "json" { if !PrintAndJsonOutput(wrappedResult, output, outfile) {
jsonRes, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(jsonRes))
return
}
result.Print() result.Print()
} }
return err
}
func CSICheck(ctx context.Context, output, func CSICheck(ctx context.Context, output, outfile,
namespace string, namespace string,
storageclass string, storageclass string,
volumesnapshotclass string, volumesnapshotclass string,
@ -186,17 +205,17 @@ func CSICheck(ctx context.Context, output,
containerImage string, containerImage string,
cleanup bool, cleanup bool,
skipCFScheck bool, skipCFScheck bool,
) { ) error {
testName := "CSI checker test" testName := "CSI checker test"
kubecli, err := kubestr.LoadKubeCli() kubecli, err := kubestr.LoadKubeCli()
if err != nil { if err != nil {
fmt.Printf("Failed to load kubeCLi (%s)", err.Error()) fmt.Printf("Failed to load kubeCLi (%s)", err.Error())
return return err
} }
dyncli, err := kubestr.LoadDynCli() dyncli, err := kubestr.LoadDynCli()
if err != nil { if err != nil {
fmt.Printf("Failed to load kubeCLi (%s)", err.Error()) fmt.Printf("Failed to load kubeCLi (%s)", err.Error())
return return err
} }
csiCheckRunner := &csi.SnapshotRestoreRunner{ csiCheckRunner := &csi.SnapshotRestoreRunner{
KubeCli: kubecli, KubeCli: kubecli,
@ -218,10 +237,9 @@ func CSICheck(ctx context.Context, output,
result = kubestr.MakeTestOutput(testName, kubestr.StatusOK, "CSI application successfully snapshotted and restored.", csiCheckResult) result = kubestr.MakeTestOutput(testName, kubestr.StatusOK, "CSI application successfully snapshotted and restored.", csiCheckResult)
} }
if output == "json" { var wrappedResult = []*kubestr.TestOutput{result}
jsonRes, _ := json.MarshalIndent(result, "", " ") if !PrintAndJsonOutput(wrappedResult, output, outfile) {
fmt.Println(string(jsonRes))
return
}
result.Print() result.Print()
} }
return err
}

View file

@ -18,10 +18,13 @@ package main
import ( import (
"github.com/kastenhq/kubestr/cmd" "github.com/kastenhq/kubestr/cmd"
"os"
) )
func main() { func main() {
_ = Execute() if err := Execute(); err != nil {
os.Exit(1)
}
} }
// Execute executes the main command // Execute executes the main command