Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tools 2715 add the validate command #23

Merged
merged 10 commits into from
Nov 14, 2023
78 changes: 70 additions & 8 deletions asconf/asconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package asconf

import (
"bytes"
"errors"
"fmt"
"sort"
"strings"

"github.com/aerospike/aerospike-management-lib/asconfig"
"github.com/go-logr/logr"
Expand Down Expand Up @@ -59,21 +62,80 @@ func NewAsconf(source []byte, srcFmt, outFmt Format, aerospikeVersion string, lo
return ac, err
}

func (ac *asconf) Validate() error {
type ValidationErr struct {
asconfig.ValidationErr
}

type VErrSlice []ValidationErr

func (a VErrSlice) Len() int { return len(a) }
func (a VErrSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a VErrSlice) Less(i, j int) bool { return strings.Compare(a[i].Error(), a[j].Error()) == -1 }

func (o ValidationErr) Error() string {
verrTemplate := "description: %s, error-type: %s"
return fmt.Sprintf(verrTemplate, o.Description, o.ErrType)
}

type ValidationErrors struct {
Errors VErrSlice
}

func (o ValidationErrors) Error() string {
errorsByContext := map[string]VErrSlice{}

sort.Sort(o.Errors)

for _, err := range o.Errors {
errorsByContext[err.Context] = append(errorsByContext[err.Context], err)
}

contexts := []string{}
for ctx := range errorsByContext {
contexts = append(contexts, ctx)
}

sort.Strings(contexts)

errString := ""

for _, ctx := range contexts {
errString += fmt.Sprintf("context: %s\n", ctx)

errList := errorsByContext[ctx]
for _, err := range errList {

// filter "Must validate one and only one schema " errors
// I have never seen a useful one and they seem to always be
// accompanied by another more useful error that will be displayed
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
if err.ErrType == "number_one_of" {
continue
}

errString += fmt.Sprintf("\t- %s\n", err.Error())
}
}

return errString
}

func (ac *asconf) Validate() (*ValidationErrors, error) {

valid, validationErrors, err := ac.cfg.IsValid(ac.managementLibLogger, ac.aerospikeVersion)
valid, tempVerrs, err := ac.cfg.IsValid(ac.managementLibLogger, ac.aerospikeVersion)

if len(validationErrors) > 0 {
for _, e := range validationErrors {
ac.logger.Errorf("Aerospike config validation error: %+v", e)
verrs := ValidationErrors{}
for _, v := range tempVerrs {
verr := ValidationErr{
ValidationErr: *v,
}
verrs.Errors = append(verrs.Errors, verr)
}

if !valid || err != nil || len(validationErrors) > 0 {
return fmt.Errorf("%w, %w", ErrConfigValidation, err)
if !valid || err != nil || len(verrs.Errors) > 0 {
return &verrs, errors.Join(ErrConfigValidation, err)
}

return err
return nil, nil
}

func (ac *asconf) MarshalText() (text []byte, err error) {
Expand Down
2 changes: 1 addition & 1 deletion asconf/asconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func Test_asconf_Validate(t *testing.T) {
src: tt.fields.src,
aerospikeVersion: tt.fields.aerospikeVersion,
}
if err := ac.Validate(); (err != nil) != tt.wantErr {
if _, err := ac.Validate(); (err != nil) != tt.wantErr {
t.Errorf("asconf.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
Expand Down
8 changes: 4 additions & 4 deletions cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var convertCmd = newConvertCmd()

func newConvertCmd() *cobra.Command {
res := &cobra.Command{
Use: "convert [flags] <path/to/config.yaml>",
Use: "convert [flags] <path/to/config_file>",
Short: "Convert between yaml and Aerospike config format.",
Long: `Convert is used to convert between yaml and aerospike configuration
files. Input files are converted to their opposite format, yaml -> conf, conf -> yaml.
Expand Down Expand Up @@ -113,9 +113,9 @@ func newConvertCmd() *cobra.Command {
}

if !force {
err = conf.Validate()
if err != nil {
return err
verrs, err := conf.Validate()
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
if err != nil || verrs != nil {
return errors.Join(err, verrs)
}
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type runTestDiff struct {
expectError bool
}

var testArgs = []runTestDiff{
var testDiffArgs = []runTestDiff{
{
flags: []string{},
arguments: []string{"not_enough_args"},
Expand Down Expand Up @@ -64,7 +64,7 @@ var testArgs = []runTestDiff{
func TestRunEDiff(t *testing.T) {
cmd := diffCmd

for i, test := range testArgs {
for i, test := range testDiffArgs {
cmd.ParseFlags(test.flags)
err := cmd.RunE(cmd, test.arguments)
if test.expectError == (err == nil) {
Expand Down
107 changes: 107 additions & 0 deletions cmd/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cmd

import (
"errors"
"fmt"
"os"

"github.com/aerospike/asconfig/asconf"
"github.com/spf13/cobra"
)

const (
validateArgMax = 1
)

var (
errValidateTooManyArguments = fmt.Errorf("expected a maximum of %d arguments", convertArgMax)
)

func init() {
rootCmd.AddCommand(validateCmd)
}

var validateCmd = newValidateCmd()

func newValidateCmd() *cobra.Command {
res := &cobra.Command{
Use: "validate [flags] <path/to/config_file>",
Short: "Validate an Aerospike configuration file.",
Long: `Validate an Aerospike configuration file in any supported format
against a versioned Aerospike configuration JSON schema.
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
If a file passes validation nothing is output, otherwise errors
indicating problems with the configuration file are shown.
If a file path is not provided, validate reads from stdin.
Ex: asconfig validate --aerospike-version 7.0.0 aerospike.conf`,
RunE: func(cmd *cobra.Command, args []string) error {
logger.Debug("Running validate command")

if len(args) > validateArgMax {
return errValidateTooManyArguments
}

// read stdin by default
var srcPath string
if len(args) == 0 {
srcPath = os.Stdin.Name()
} else {
srcPath = args[0]
}

version, err := cmd.Flags().GetString("aerospike-version")
if err != nil {
return err
}

logger.Debugf("Processing flag aerospike-version value=%s", version)

srcFormat, err := getConfFileFormat(srcPath, cmd)
if err != nil {
return err
}

logger.Debugf("Processing flag format value=%v", srcFormat)

fdata, err := os.ReadFile(srcPath)
if err != nil {
return err
}

conf, err := asconf.NewAsconf(
fdata,
srcFormat,
// we aren't converting to anything so set
// output format to Invalid as a place holder
asconf.Invalid,
version,
logger,
managementLibLogger,
)

if err != nil {
return err
}

verrs, err := conf.Validate()
if verrs != nil {
// force validation errors to be written to stdout
// so they can more easily be grepd etc.
cmd.Print(verrs.Error())
return errors.Join(asconf.ErrConfigValidation, SilentError)
}
if err != nil {
return err
}

return err
},
}

// flags and configuration settings
res.Flags().StringP("aerospike-version", "a", "", "Aerospike server version for the configuration file. Ex: 6.2.0.\nThe first 3 digits of the Aerospike version number are required.")
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
res.MarkFlagRequired("aerospike-version")

res.Version = VERSION

return res
}
66 changes: 66 additions & 0 deletions cmd/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//go:build unit
// +build unit

package cmd

import (
"testing"
)

type runTestValidate struct {
flags []string
arguments []string
expectError bool
}

var testValidateArgs = []runTestValidate{
{
flags: []string{},
arguments: []string{"too", "many", "args"},
expectError: true,
},
{
// missing arg to -a-aerospike-version
flags: []string{"--aerospike-version"},
arguments: []string{"../testdata/sources/all_flash_cluster_cr.yaml"},
expectError: true,
},
{
flags: []string{"--aerospike-version"},
arguments: []string{"./bad_extension.ymml"},
expectError: true,
},
{
flags: []string{"--aerospike-version", "bad_version"},
arguments: []string{"../testdata/sources/all_flash_cluster_cr.yaml"},
expectError: true,
},
{
flags: []string{"--aerospike-version", "6.4.0"},
arguments: []string{"./fake_file.yaml"},
expectError: true,
},
{
flags: []string{"--aerospike-version", "6.4.0"},
arguments: []string{"../testdata/cases/server64/server64.yaml"},
expectError: false,
},
{
flags: []string{"--log-level", "debug", "--aerospike-version", "7.0.0"},
arguments: []string{"../testdata/cases/server70/server70.conf"},
expectError: false,
},
}

func TestRunEValidate(t *testing.T) {
cmd := validateCmd

for i, test := range testValidateArgs {
cmd.Parent().ParseFlags(test.flags)
cmd.ParseFlags(test.flags)
err := cmd.RunE(cmd, test.arguments)
if test.expectError == (err == nil) {
t.Fatalf("case: %d, expectError: %v does not match err: %v", i, test.expectError, err)
}
}
}
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/aerospike/aerospike-management-lib v0.0.0-20231025222637-439d643badb8 h1:DhmnlRVu4CURbZhA6oTSyTUSKaG3XWYOviEnyxOzy0g=
github.com/aerospike/aerospike-management-lib v0.0.0-20231025222637-439d643badb8/go.mod h1:O4v2oGl4VjG9KwYJoSVEwZXv1PUB4ioKAsrm2tczJPQ=
github.com/aerospike/aerospike-management-lib v0.0.0-20231025224657-765f71b4994d h1:WqKtqOqdZ41/WvbPV/3tGa4KNXZOOTdhwz/K1+P2CuI=
github.com/aerospike/aerospike-management-lib v0.0.0-20231025224657-765f71b4994d/go.mod h1:O4v2oGl4VjG9KwYJoSVEwZXv1PUB4ioKAsrm2tczJPQ=
github.com/bombsimon/logrusr/v4 v4.0.0 h1:Pm0InGphX0wMhPqC02t31onlq9OVyJ98eP/Vh63t1Oo=
Expand Down
Loading