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

feat: add --custom-resources flag to validator rules check as an alternate to -f #218

Merged
merged 9 commits into from
Sep 9, 2024
Merged
2 changes: 1 addition & 1 deletion build
5 changes: 5 additions & 0 deletions cmd/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,16 @@ For more information about validator, see: https://github.com/validator-labs/val

flags := cmd.Flags()
flags.StringVarP(&tc.ConfigFile, "config-file", "f", "", "Validator configuration file.")
flags.StringVar(&tc.CRPath, "cr", "", "Path to a file or directory containing validator custom resource yaml documents.")
ahmad-ibra marked this conversation as resolved.
Show resolved Hide resolved
flags.BoolVarP(&tc.CreateConfigOnly, "config-only", "o", false, "Update configuration file only. Do not proceed with checks. Default: false.")
flags.BoolVarP(&tc.UpdatePasswords, "update-passwords", "p", false, "Update passwords only. Do not proceed with checks. Default: false.")
flags.BoolVarP(&tc.Reconfigure, "reconfigure", "r", false, "Re-configure plugin rules prior to running checks. Default: false.")

cmd.MarkFlagsMutuallyExclusive("update-passwords", "reconfigure")
cmd.MarkFlagsMutuallyExclusive("config-file", "cr")
cmd.MarkFlagsMutuallyExclusive("config-only", "cr")
cmd.MarkFlagsMutuallyExclusive("update-passwords", "cr")
cmd.MarkFlagsMutuallyExclusive("reconfigure", "cr")

return cmd
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/spectrocloud-labs/prompts-tui v0.1.2
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/validator-labs/validator v0.1.10
github.com/validator-labs/validator-plugin-aws v0.1.7
github.com/validator-labs/validator-plugin-azure v0.0.20
Expand Down Expand Up @@ -201,6 +202,7 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
Expand Down
121 changes: 118 additions & 3 deletions pkg/cmd/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
maasapi "github.com/validator-labs/validator-plugin-maas/api/v1alpha1"
maasconst "github.com/validator-labs/validator-plugin-maas/pkg/constants"
maasval "github.com/validator-labs/validator-plugin-maas/pkg/validate"

netapi "github.com/validator-labs/validator-plugin-network/api/v1alpha1"
netconst "github.com/validator-labs/validator-plugin-network/pkg/constants"
netval "github.com/validator-labs/validator-plugin-network/pkg/validate"
Expand All @@ -59,6 +58,7 @@
"github.com/validator-labs/validatorctl/pkg/utils/embed"
"github.com/validator-labs/validatorctl/pkg/utils/exec"
exec_utils "github.com/validator-labs/validatorctl/pkg/utils/exec"
file_utils "github.com/validator-labs/validatorctl/pkg/utils/file"
"github.com/validator-labs/validatorctl/pkg/utils/kind"
"github.com/validator-labs/validatorctl/pkg/utils/kube"
string_utils "github.com/validator-labs/validatorctl/pkg/utils/string"
Expand All @@ -72,6 +72,11 @@
return "one or more validation checks failed"
}

type basePluginSpec struct {
Kind string `yaml:"kind"`
Spec map[string]interface{} `yaml:"spec"`
}

// InitWorkspace initializes a workspace directory with subdirectories
func InitWorkspace(c *cfg.Config, workspaceDir string, subdirs []string, timestamped bool) error {
if err := c.CreateWorkspace(workspaceDir, subdirs, timestamped); err != nil {
Expand Down Expand Up @@ -198,6 +203,20 @@
var err error
var saveConfig bool

if tc.CRPath != "" && tc.Direct {
pluginSpecs, err := readPluginSpecs(tc.CRPath)
if err != nil {
return err

Check warning on line 209 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L209

Added line #L209 was not covered by tests
}

if len(pluginSpecs) == 0 {
log.InfoCLI("No plugin rule CRs found in %s", tc.CRPath)
return nil

Check warning on line 214 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L213-L214

Added lines #L213 - L214 were not covered by tests
}

return executePlugins(c, pluginSpecs, nil)
}

if !tc.Reconfigure {
// Silent Mode
vc, err = components.NewValidatorFromConfig(tc)
Expand Down Expand Up @@ -249,7 +268,7 @@
}

if tc.Direct {
return executePlugins(c, pluginSpecs(vc), vc.SinkConfig)
return executePlugins(c, toPluginSpecs(vc), vc.SinkConfig)
}

// upgrade the validator helm release so that plugin rule secrets
Expand All @@ -268,7 +287,103 @@
return nil
}

func pluginSpecs(vc *components.ValidatorConfig) []plugins.PluginSpec {
func readPluginSpecs(path string) ([]plugins.PluginSpec, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err

Check warning on line 293 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L293

Added line #L293 was not covered by tests
}

files := make([]string, 0)
if !fi.IsDir() {
files = append(files, path)

Check warning on line 298 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L298

Added line #L298 was not covered by tests
} else {
files, err = file_utils.GetFilesInDir(path)
if err != nil {
return nil, err

Check warning on line 302 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L302

Added line #L302 was not covered by tests
}
}

if len(files) == 0 {
return nil, fmt.Errorf("no files found in %s", path)

Check warning on line 307 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L307

Added line #L307 was not covered by tests
}

ps := make([]plugins.PluginSpec, 0)
for _, f := range files {
pSpecs, err := readPluginSpecsInFile(f)
if err != nil {
return nil, err

Check warning on line 314 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L314

Added line #L314 was not covered by tests
}
ps = append(ps, pSpecs...)
}

return ps, nil
}

func readPluginSpecsInFile(file string) ([]plugins.PluginSpec, error) {
log.InfoCLI("Reading plugin specs from file: %s", file)

data, err := os.ReadFile(file) // #nosec
if err != nil {
return nil, err

Check warning on line 327 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L327

Added line #L327 was not covered by tests
}

ps := make([]plugins.PluginSpec, 0)
parts := bytes.Split(data, []byte("---"))
for _, p := range parts {
if len(p) == 0 {
continue

Check warning on line 334 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L334

Added line #L334 was not covered by tests
}

spec, err := unmarshalPluginSpec(p)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal plugin spec")

Check warning on line 339 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L339

Added line #L339 was not covered by tests
}
ps = append(ps, spec)
}

return ps, nil
}

func unmarshalPluginSpec(data []byte) (plugins.PluginSpec, error) {
var bps basePluginSpec
if err := yaml.Unmarshal(data, &bps); err != nil {
return nil, err
}

specBytes, err := yaml.Marshal(bps.Spec)
if err != nil {
return nil, err

Check warning on line 355 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L355

Added line #L355 was not covered by tests
}

var spec plugins.PluginSpec
switch bps.Kind {
case cfg.ValidatorPluginAwsKind:
spec = &awsapi.AwsValidatorSpec{}
case cfg.ValidatorPluginAzureKind:
spec = &azureapi.AzureValidatorSpec{}
case cfg.ValidatorPluginMaasKind:
spec = &maasapi.MaasValidatorSpec{}

Check warning on line 365 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L360-L365

Added lines #L360 - L365 were not covered by tests
case cfg.ValidatorPluginNetworkKind:
spec = &netapi.NetworkValidatorSpec{}
case cfg.ValidatorPluginOciKind:
spec = &ociapi.OciValidatorSpec{}
case cfg.ValidatorPluginVsphereKind:
spec = &vsphereapi.VsphereValidatorSpec{}

Check warning on line 371 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L370-L371

Added lines #L370 - L371 were not covered by tests
default:
if bps.Kind == "" {
return nil, errors.New("plugin kind is not set")
}
return nil, fmt.Errorf("unknown plugin kind: %s", bps.Kind)
}

if err := yaml.Unmarshal(specBytes, spec); err != nil {
return nil, err

Check warning on line 380 in pkg/cmd/validator/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/validator/validator.go#L380

Added line #L380 was not covered by tests
}

return spec, nil
}

func toPluginSpecs(vc *components.ValidatorConfig) []plugins.PluginSpec {
pluginSpecs := make([]plugins.PluginSpec, 0)
if vc.AWSPlugin != nil && vc.AWSPlugin.Enabled {
pluginSpecs = append(pluginSpecs, vc.AWSPlugin.Validator)
Expand Down
71 changes: 71 additions & 0 deletions pkg/cmd/validator/validator_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package validator

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

netapi "github.com/validator-labs/validator-plugin-network/api/v1alpha1"
vapi "github.com/validator-labs/validator/api/v1alpha1"
"github.com/validator-labs/validator/pkg/plugins"
)

func TestBuildValidationResultString(t *testing.T) {
Expand Down Expand Up @@ -113,3 +117,70 @@ Failures
})
}
}

func TestUnmarshalPluginSpec(t *testing.T) {
tests := []struct {
name string
input []byte
expectedSpec plugins.PluginSpec
expectedErr error
}{
{
name: "Valid NetworkValidator spec",
input: []byte(
`apiVersion: validation.spectrocloud.labs/v1alpha1
kind: NetworkValidator
metadata:
name: network-validator-combined-network-rules
spec:
dnsRules:
- name: Resolve Google
host: google.com
`),
expectedSpec: &netapi.NetworkValidatorSpec{
DNSRules: []netapi.DNSRule{
{RuleName: "Resolve Google", Host: "google.com"},
},
},
expectedErr: nil,
},
{
name: "Unknown plugin kind",
input: []byte(`kind: SomeRandomKind`),
expectedSpec: nil,
expectedErr: errors.New("unknown plugin kind"),
},
{
name: "Kind not set",
input: []byte(
`spec:
dnsRules:
- name: Resolve Google
host: google.com
`),
expectedSpec: nil,
expectedErr: errors.New("plugin kind is not set"),
},
{
name: "Invalid YAML format",
input: []byte("hello"),
expectedSpec: nil,
expectedErr: errors.New("cannot unmarshal"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
spec, err := unmarshalPluginSpec(tt.input)

// If an error is expected
if tt.expectedErr != nil {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedErr.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedSpec, spec)
}
})
}
}
1 change: 1 addition & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Config struct {
type TaskConfig struct {
CliVersion string
ConfigFile string
CRPath string
Apply bool
CreateConfigOnly bool
DeleteCluster bool
Expand Down
7 changes: 7 additions & 0 deletions pkg/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ const (
ValidatorPluginOci = "validator-plugin-oci"
ValidatorPluginVsphere = "validator-plugin-vsphere"

ValidatorPluginAwsKind = "AwsValidator"
ValidatorPluginAzureKind = "AzureValidator"
ValidatorPluginMaasKind = "MaasValidator"
ValidatorPluginNetworkKind = "NetworkValidator"
ValidatorPluginOciKind = "OciValidator"
ValidatorPluginVsphereKind = "VsphereValidator"

ValidatorPluginAwsTemplate = "validator-rules-aws.tmpl"
ValidatorPluginAzureTemplate = "validator-rules-azure.tmpl"
ValidatorPluginMaasTemplate = "validator-rules-maas.tmpl"
Expand Down
28 changes: 28 additions & 0 deletions pkg/utils/file/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Package file provides utility functions for working with a filesystem.
package file

import (
"io/fs"
"path/filepath"
)

// GetFilesInDir walks the file tree from dir and returns a list of all files found in lexical order.
func GetFilesInDir(dir string) ([]string, error) {
files := make([]string, 0)
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err

Check warning on line 14 in pkg/utils/file/file.go

View check run for this annotation

Codecov / codecov/patch

pkg/utils/file/file.go#L14

Added line #L14 was not covered by tests
}

if !d.IsDir() {
files = append(files, path)
}

return nil
})
if err != nil {
return nil, err

Check warning on line 24 in pkg/utils/file/file.go

View check run for this annotation

Codecov / codecov/patch

pkg/utils/file/file.go#L24

Added line #L24 was not covered by tests
}

return files, nil
}
Loading