Skip to content

Commit

Permalink
feat: add helpers to easily configure validator plugins (#62)
Browse files Browse the repository at this point in the history
* feat: implement configureBaseValidator and configureVspherePlugin functions

* feat: implement configureNetworkPlugin functions

* feat: implement configureOciPlugin functions

* refactor: pass in the file we want to save directly to SaveValidatorConfig

* fix: ensure we have different names for our validatorctl config and our validator config

* chore: ensure bin directory is not created in workspace because no bins are needed

* feat: add support for watching validation results

* feat: update WatchValidationResult to work with a taskConfig

* chore: fix method signatures to not have unnecesary returns
  • Loading branch information
ahmad-ibra authored Jun 27, 2024
1 parent 9698478 commit ae596d3
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 24 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ require (
github.com/vmware/govmomi v0.38.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
k8s.io/client-go v0.30.2
Expand Down Expand Up @@ -56,6 +55,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
Expand Down Expand Up @@ -101,6 +101,7 @@ require (
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.30.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect
Expand Down
94 changes: 85 additions & 9 deletions pkg/cmd/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import (
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/cache"
toolsWatch "k8s.io/client-go/tools/watch"

vapi "github.com/validator-labs/validator/api/v1alpha1"
"github.com/validator-labs/validator/pkg/helm"
Expand Down Expand Up @@ -100,8 +103,8 @@ func DeployValidatorCommand(c *cfg.Config, tc *cfg.TaskConfig, reconfigure bool)
return applyValidatorAndPlugins(c, vc)
}

func UpgradeValidatorCommand(c *cfg.Config, taskConfig *cfg.TaskConfig) error {
vc, err := components.NewValidatorFromConfig(taskConfig)
func UpgradeValidatorCommand(c *cfg.Config, tc *cfg.TaskConfig) error {
vc, err := components.NewValidatorFromConfig(tc)
if err != nil {
return errors.Wrap(err, "failed to load validator configuration file")
}
Expand All @@ -111,8 +114,8 @@ func UpgradeValidatorCommand(c *cfg.Config, taskConfig *cfg.TaskConfig) error {
return applyValidatorAndPlugins(c, vc)
}

func UndeployValidatorCommand(taskConfig *cfg.TaskConfig, deleteCluster bool) error {
vc, err := components.NewValidatorFromConfig(taskConfig)
func UndeployValidatorCommand(tc *cfg.TaskConfig, deleteCluster bool) error {
vc, err := components.NewValidatorFromConfig(tc)
if err != nil {
return errors.Wrap(err, "failed to load validator configuration file")
}
Expand All @@ -134,8 +137,8 @@ func UndeployValidatorCommand(taskConfig *cfg.TaskConfig, deleteCluster bool) er
return nil
}

func DescribeValidationResultsCommand(taskConfig *cfg.TaskConfig) error {
kClient, err := getValidationResultsCRDClient(taskConfig)
func DescribeValidationResultsCommand(tc *cfg.TaskConfig) error {
kClient, err := getValidationResultsCRDClient(tc)
if err != nil {
return errors.Wrap(err, "failed to get validation result client")
}
Expand All @@ -151,9 +154,82 @@ func DescribeValidationResultsCommand(taskConfig *cfg.TaskConfig) error {

return nil
}
func getValidationResultsCRDClient(taskConfig *cfg.TaskConfig) (dynamic.NamespaceableResourceInterface, error) {
if taskConfig.ConfigFile != "" {
vc, err := components.NewValidatorFromConfig(taskConfig)

func WatchValidationResults(tc *cfg.TaskConfig) (bool, error) {
log.InfoCLI("\nWatching validation results, waiting for all to succeed")
kClient, err := getValidationResultsCRDClient(tc)
if err != nil {
return false, errors.Wrap(err, "failed to get validation result client")
}

watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
return kClient.Watch(context.Background(), metav1.ListOptions{})
}

watcher, err := toolsWatch.NewRetryWatcher("1", &cache.ListWatch{WatchFunc: watchFunc})
if err != nil {
return false, errors.Wrap(err, "failed to create retry watcher for validation results")
}

var hasValidationSucceeded bool
validationStates := make(map[string]vapi.ValidationState)

if os.Getenv("IS_TEST") == "true" {
return true, nil
}

for event := range watcher.ResultChan() {
vrObj := event.Object.(*unstructured.Unstructured)

vr := &vapi.ValidationResult{}
bytes, err := vrObj.MarshalJSON()
if err != nil {
return false, err
}
if err := json.Unmarshal(bytes, vr); err != nil {
return false, err
}

prevValidationState := validationStates[vr.Name]
validationStates[vr.Name] = vr.Status.State
if event.Type != watch.Modified {
continue
}

hasValidationSucceeded = true
if prevValidationState != vr.Status.State {
log.InfoCLI("\nValidation result for %s updated:", vr.Name)
err = printValidationResults([]unstructured.Unstructured{*vrObj})
if err != nil {
return false, err
}

finished := true
vrWaiting := make([]string, 0)
for vName, state := range validationStates {
if state == vapi.ValidationFailed {
hasValidationSucceeded = false
}
if state != vapi.ValidationSucceeded && state != vapi.ValidationFailed {
vrWaiting = append(vrWaiting, vName)
finished = false
break
}
}
if finished {
break
}

log.InfoCLI("\nWatching for updates to validation results for %s...", vrWaiting)
}
}
log.InfoCLI("\nAll validations have completed.")
return hasValidationSucceeded, nil
}

func getValidationResultsCRDClient(tc *cfg.TaskConfig) (dynamic.NamespaceableResourceInterface, error) {
if tc.ConfigFile != "" {
vc, err := components.NewValidatorFromConfig(tc)
if err != nil {
return nil, errors.Wrap(err, "failed to load validator configuration file")
}
Expand Down
37 changes: 37 additions & 0 deletions pkg/components/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package components

import (
"fmt"

network_api "github.com/validator-labs/validator-plugin-network/api/v1alpha1"
vapi "github.com/validator-labs/validator/api/v1alpha1"

cfg "github.com/validator-labs/validatorctl/pkg/config"
)

type NetworkConfig struct {
VcenterServer string
IPRangeRules []network_api.IPRangeRule
TCPConnRules []network_api.TCPConnRule
}

func ConfigureNetworkPlugin(vc *ValidatorConfig, config NetworkConfig) {
vc.NetworkPlugin = &NetworkPluginConfig{
Enabled: true,
Release: &vapi.HelmRelease{
Chart: vapi.HelmChart{
Name: cfg.ValidatorPluginNetwork,
Repository: fmt.Sprintf("%s/%s", cfg.ValidatorHelmRepository, cfg.ValidatorPluginNetwork),
Version: cfg.ValidatorChartVersions[cfg.ValidatorPluginNetwork],
InsecureSkipTlsVerify: true,
},
},
ReleaseSecret: &Secret{
Name: fmt.Sprintf("validator-helm-release-%s", cfg.ValidatorPluginNetwork),
},
Validator: &network_api.NetworkValidatorSpec{
IPRangeRules: config.IPRangeRules,
TCPConnRules: config.TCPConnRules,
},
}
}
57 changes: 57 additions & 0 deletions pkg/components/oci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package components

import (
"fmt"

oci_api "github.com/validator-labs/validator-plugin-oci/api/v1alpha1"
vapi "github.com/validator-labs/validator/api/v1alpha1"

cfg "github.com/validator-labs/validatorctl/pkg/config"
)

type OciConfig struct {
// HostRefs is a map of hostnames to a list of artifact references
HostRefs map[string][]string
}

func ConfigureOciPlugin(vc *ValidatorConfig, config OciConfig) {
vc.OCIPlugin = &OCIPluginConfig{
Enabled: true,
Release: &vapi.HelmRelease{
Chart: vapi.HelmChart{
Name: cfg.ValidatorPluginOci,
Repository: fmt.Sprintf("%s/%s", cfg.ValidatorHelmRepository, cfg.ValidatorPluginOci),
Version: cfg.ValidatorChartVersions[cfg.ValidatorPluginOci],
InsecureSkipTlsVerify: true,
},
},
ReleaseSecret: &Secret{
Name: fmt.Sprintf("validator-helm-release-%s", cfg.ValidatorPluginOci),
},
Validator: &oci_api.OciValidatorSpec{
OciRegistryRules: generateOciRegistryRules(config.HostRefs),
},
}
}

func generateOciRegistryRules(hostRefs map[string][]string) []oci_api.OciRegistryRule {
var rules []oci_api.OciRegistryRule
for host, refs := range hostRefs {
rule := oci_api.OciRegistryRule{
RuleName: fmt.Sprintf("artifacts on %s", host),
Host: host,
}

artifacts := []oci_api.Artifact{}
for _, ref := range refs {
artifacts = append(artifacts, oci_api.Artifact{
Ref: ref,
LayerValidation: true,
})
}
rule.Artifacts = artifacts

rules = append(rules, rule)
}
return rules
}
39 changes: 32 additions & 7 deletions pkg/components/validator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package components

import (
"fmt"
"os"

"emperror.dev/errors"
Expand Down Expand Up @@ -502,8 +503,8 @@ func (s *Secret) decrypt() error {
}

// NewValidatorFromConfig loads a validator configuration file from disk and decrypts it
func NewValidatorFromConfig(taskConfig *cfg.TaskConfig) (*ValidatorConfig, error) {
c, err := LoadValidatorConfig(taskConfig)
func NewValidatorFromConfig(tc *cfg.TaskConfig) (*ValidatorConfig, error) {
c, err := LoadValidatorConfig(tc)
if err != nil {
return nil, err
}
Expand All @@ -514,8 +515,8 @@ func NewValidatorFromConfig(taskConfig *cfg.TaskConfig) (*ValidatorConfig, error
}

// LoadValidatorConfig loads a validator configuration file from disk
func LoadValidatorConfig(taskConfig *cfg.TaskConfig) (*ValidatorConfig, error) {
bytes, err := os.ReadFile(taskConfig.ConfigFile)
func LoadValidatorConfig(tc *cfg.TaskConfig) (*ValidatorConfig, error) {
bytes, err := os.ReadFile(tc.ConfigFile)
if err != nil {
return nil, errors.Wrap(err, "failed to read validator config file")
}
Expand All @@ -527,7 +528,7 @@ func LoadValidatorConfig(taskConfig *cfg.TaskConfig) (*ValidatorConfig, error) {
}

// SaveValidatorConfig saves a validator configuration file to disk
func SaveValidatorConfig(c *ValidatorConfig, taskConfig *cfg.TaskConfig) error {
func SaveValidatorConfig(c *ValidatorConfig, tc *cfg.TaskConfig) error {
if err := c.encrypt(); err != nil {
return err
}
Expand All @@ -538,9 +539,33 @@ func SaveValidatorConfig(c *ValidatorConfig, taskConfig *cfg.TaskConfig) error {
if err := c.decrypt(); err != nil {
return err
}
if err = os.WriteFile(taskConfig.ConfigFile, b, 0600); err != nil {
if err = os.WriteFile(tc.ConfigFile, b, 0600); err != nil {
return errors.Wrap(err, "failed to create validator config file")
}
log.InfoCLI("validator configuration file saved: %s", taskConfig.ConfigFile)
log.InfoCLI("validator configuration file saved: %s", tc.ConfigFile)
return nil
}

func ConfigureBaseValidator(vc *ValidatorConfig, kubeconfig string) {
vc.Release = &validator.HelmRelease{
Chart: validator.HelmChart{
Name: cfg.Validator,
Repository: fmt.Sprintf("%s/%s", cfg.ValidatorHelmRepository, cfg.Validator),
Version: cfg.ValidatorChartVersions[cfg.Validator],
InsecureSkipTlsVerify: true,
},
}
vc.ReleaseSecret = &Secret{
Name: fmt.Sprintf("validator-helm-release-%s", cfg.Validator),
}
vc.KindConfig.UseKindCluster = true
vc.Kubeconfig = kubeconfig
vc.ImageRegistry = cfg.ValidatorImageRegistry
vc.ProxyConfig = &ProxyConfig{
Env: &env.Env{
PodCIDR: &cfg.DefaultPodCIDR,
ServiceIPRange: &cfg.DefaultServiceIPRange,
},
}
vc.UseFixedVersions = true
}
Loading

0 comments on commit ae596d3

Please sign in to comment.