diff --git a/.golangci.yml b/.golangci.yml index ef3d66c0..db4f783e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,6 +28,7 @@ linters: - misspell - nakedret - prealloc + - revive - staticcheck - typecheck - unconvert diff --git a/api/v1alpha1/validationresult_types.go b/api/v1alpha1/validationresult_types.go index 789bceff..44683b6b 100644 --- a/api/v1alpha1/validationresult_types.go +++ b/api/v1alpha1/validationresult_types.go @@ -26,65 +26,90 @@ import ( clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" ) -// ValidationResultSpec defines the desired state of ValidationResult +// ValidationResultSpec defines the desired state of ValidationResult. type ValidationResultSpec struct { + // Plugin is the plugin code of the validator plugin that was executed. Plugin string `json:"plugin"` + // The number of rules in the validator plugin spec, hence the number of expected ValidationResults. // +kubebuilder:validation:Minimum=1 ExpectedResults int `json:"expectedResults"` } +// ValidationState records the overall state of a validation result. type ValidationState string const ( - ValidationFailed ValidationState = "Failed" + // ValidationFailed indicates that the validation result has failed. + ValidationFailed ValidationState = "Failed" + + // ValidationInProgress indicates that the validation result is in progress. ValidationInProgress ValidationState = "InProgress" - ValidationSucceeded ValidationState = "Succeeded" + + // ValidationSucceeded indicates that the validation result has succeeded. + ValidationSucceeded ValidationState = "Succeeded" ) +// SinkEmission is the type of condition that is emitted to the sink. const SinkEmission clusterv1beta1.ConditionType = "SinkEmission" +// SinkEmitState is the state of the sink emission. type SinkEmitState string const ( - SinkEmitNA SinkEmitState = "SinkEmitNA" - SinkEmitFailed SinkEmitState = "SinkEmitFailed" + // SinkEmitNA indicates that the sink emission is not applicable. + SinkEmitNA SinkEmitState = "SinkEmitNA" + + // SinkEmitFailed indicates that the sink emission has failed. + SinkEmitFailed SinkEmitState = "SinkEmitFailed" + + // SinkEmitSucceeded indicates that the sink emission has succeeded. SinkEmitSucceeded SinkEmitState = "SinkEmitSucceeded" ) -// ValidationResultStatus defines the observed state of ValidationResult +// ValidationResultStatus defines the observed state of ValidationResult. type ValidationResultStatus struct { + // State is the overall state of the validation result. State ValidationState `json:"state"` + // Conditions is a list of conditions that describe the current state of the ValidationResult. // +optional // +patchMergeKey=type // +patchStrategy=merge Conditions []clusterv1beta1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // ValidationConditions is a list of conditions that describe the validation rules associated with the ValidationResult. // +optional // +patchMergeKey=type // +patchStrategy=merge ValidationConditions []ValidationCondition `json:"validationConditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } +// ValidationCondition describes the state of a validation rule. type ValidationCondition struct { // Unique, one-word description of the validation type associated with the condition. ValidationType string `json:"validationType"` + // Unique, one-word description of the validation rule associated with the condition. ValidationRule string `json:"validationRule"` + // Human-readable message indicating details about the last transition. Message string `json:"message,omitempty"` + // Human-readable messages indicating additional details for the last transition. Details []string `json:"details,omitempty"` + // Human-readable messages indicating additional failure details for the last transition. Failures []string `json:"failures,omitempty"` + // True if the validation rule succeeded, otherwise False. Status corev1.ConditionStatus `json:"status"` + // Timestamp of most recent execution of the validation rule associated with the condition. LastValidationTime metav1.Time `json:"lastValidationTime"` } -// DefaultValidationCondition returns a default ValidationCondition +// DefaultValidationCondition returns a default ValidationCondition. func DefaultValidationCondition() ValidationCondition { return ValidationCondition{ Details: make([]string, 0), @@ -99,7 +124,7 @@ func DefaultValidationCondition() ValidationCondition { //+kubebuilder:printcolumn:name="Plugin",type="string",JSONPath=".spec.plugin",description="Plugin" //+kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="State" -// ValidationResult is the Schema for the validationresults API +// ValidationResult is the Schema for the validationresults API. type ValidationResult struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -108,6 +133,7 @@ type ValidationResult struct { Status ValidationResultStatus `json:"status,omitempty"` } +// Hash returns a hash of the ValidationResult. func (r *ValidationResult) Hash() string { digester := crypto.MD5.New() @@ -127,7 +153,7 @@ func (r *ValidationResult) Hash() string { //+kubebuilder:object:root=true -// ValidationResultList contains a list of ValidationResult +// ValidationResultList contains a list of ValidationResult. type ValidationResultList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1alpha1/validatorconfig_types.go b/api/v1alpha1/validatorconfig_types.go index 8b456b21..e78fe9f5 100644 --- a/api/v1alpha1/validatorconfig_types.go +++ b/api/v1alpha1/validatorconfig_types.go @@ -21,31 +21,50 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// ValidatorConfigSpec defines the desired state of ValidatorConfig +// ValidatorConfigSpec defines the desired state of ValidatorConfig. type ValidatorConfigSpec struct { + // Plugins defines the configuration for the validator plugins. Plugins []HelmRelease `json:"plugins,omitempty" yaml:"plugins,omitempty"` - Sink *Sink `json:"sink,omitempty" yaml:"sink,omitempty"` + + // Sink defines the configuration for the notification sink. + Sink *Sink `json:"sink,omitempty" yaml:"sink,omitempty"` } +// Sink defines the configuration for a notification sink. type Sink struct { + // Type of the sink. // +kubebuilder:validation:Enum=alertmanager;slack Type string `json:"type" yaml:"type"` - // Name of a K8s secret containing configuration details for the sink + + // SecretName is the name of a K8s secret containing configuration details for the sink. SecretName string `json:"secretName" yaml:"secretName"` } +// HelmRelease defines the configuration for a Helm chart release. type HelmRelease struct { - Chart HelmChart `json:"chart" yaml:"chart"` - Values string `json:"values" yaml:"values"` + // Chart defines the Helm chart to be installed. + Chart HelmChart `json:"chart" yaml:"chart"` + + // Values defines the values to be passed to the Helm chart. + Values string `json:"values" yaml:"values"` } +// HelmChart defines the configuration for a Helm chart. type HelmChart struct { - Name string `json:"name" yaml:"name"` - Repository string `json:"repository" yaml:"repository"` - Version string `json:"version" yaml:"version"` - CaFile string `json:"caFile,omitempty" yaml:"caFile,omitempty"` - InsecureSkipTlsVerify bool `json:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty"` - AuthSecretName string `json:"authSecretName,omitempty" yaml:"authSecretName,omitempty"` + // Name of the Helm chart. + Name string `json:"name" yaml:"name"` + + // Repository URL of the Helm chart. + Repository string `json:"repository" yaml:"repository"` + + // Version of the Helm chart. + Version string `json:"version" yaml:"version"` + + // InsecureSkipTLSVerify skips the verification of the server's certificate chain and host name. + InsecureSkipTLSVerify bool `json:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty"` + + // AuthSecretName is the name of the K8s secret containing the authentication details for the Helm repository. + AuthSecretName string `json:"authSecretName,omitempty" yaml:"authSecretName,omitempty"` } // ValidatorConfigStatus defines the observed state of ValidatorConfig @@ -56,6 +75,7 @@ type ValidatorConfigStatus struct { Conditions []ValidatorPluginCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } +// ValidatorPluginCondition describes the state of a Validator plugin. type ValidatorPluginCondition struct { // Type of condition in CamelCase or in foo.example.com/CamelCase. // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions @@ -92,7 +112,7 @@ const HelmChartDeployedCondition ConditionType = "HelmChartDeployed" //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// ValidatorConfig is the Schema for the validatorconfigs API +// ValidatorConfig is the Schema for the validatorconfigs API. type ValidatorConfig struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -103,7 +123,7 @@ type ValidatorConfig struct { //+kubebuilder:object:root=true -// ValidatorConfigList contains a list of ValidatorConfig +// ValidatorConfigList contains a list of ValidatorConfig. type ValidatorConfigList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/cmd/main.go b/cmd/main.go index b1bfaee4..707ea69c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package main initializes the ValidationConfig and ValidationResult controllers. package main import ( @@ -37,6 +38,7 @@ import ( "github.com/validator-labs/validator/internal/kube" "github.com/validator-labs/validator/internal/sinks" "github.com/validator-labs/validator/pkg/helm" + "github.com/validator-labs/validator/pkg/helm/release" //+kubebuilder:scaffold:imports ) @@ -128,7 +130,7 @@ func main() { if err = (&controller.ValidatorConfigReconciler{ Client: mgr.GetClient(), HelmClient: helm.NewHelmClient(rawConfig), - HelmSecretsClient: helm.NewSecretsClient(mgr.GetClient()), + HelmReleaseClient: release.NewHelmReleaseClient(mgr.GetClient()), Log: ctrl.Log.WithName("controllers").WithName("ValidatorConfig"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { diff --git a/config/crd/bases/validation.spectrocloud.labs_validationresults.yaml b/config/crd/bases/validation.spectrocloud.labs_validationresults.yaml index 74e0be79..301d069a 100644 --- a/config/crd/bases/validation.spectrocloud.labs_validationresults.yaml +++ b/config/crd/bases/validation.spectrocloud.labs_validationresults.yaml @@ -30,7 +30,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: ValidationResult is the Schema for the validationresults API + description: ValidationResult is the Schema for the validationresults API. properties: apiVersion: description: |- @@ -50,7 +50,7 @@ spec: metadata: type: object spec: - description: ValidationResultSpec defines the desired state of ValidationResult + description: ValidationResultSpec defines the desired state of ValidationResult. properties: expectedResults: description: The number of rules in the validator plugin spec, hence @@ -58,15 +58,19 @@ spec: minimum: 1 type: integer plugin: + description: Plugin is the plugin code of the validator plugin that + was executed. type: string required: - expectedResults - plugin type: object status: - description: ValidationResultStatus defines the observed state of ValidationResult + description: ValidationResultStatus defines the observed state of ValidationResult. properties: conditions: + description: Conditions is a list of conditions that describe the + current state of the ValidationResult. items: description: Condition defines an observation of a Cluster API resource operational state. @@ -111,9 +115,14 @@ spec: type: object type: array state: + description: State is the overall state of the validation result. type: string validationConditions: + description: ValidationConditions is a list of conditions that describe + the validation rules associated with the ValidationResult. items: + description: ValidationCondition describes the state of a validation + rule. properties: details: description: Human-readable messages indicating additional details diff --git a/config/crd/bases/validation.spectrocloud.labs_validatorconfigs.yaml b/config/crd/bases/validation.spectrocloud.labs_validatorconfigs.yaml index 74cfad32..e732032f 100644 --- a/config/crd/bases/validation.spectrocloud.labs_validatorconfigs.yaml +++ b/config/crd/bases/validation.spectrocloud.labs_validatorconfigs.yaml @@ -17,7 +17,7 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: ValidatorConfig is the Schema for the validatorconfigs API + description: ValidatorConfig is the Schema for the validatorconfigs API. properties: apiVersion: description: |- @@ -37,24 +37,33 @@ spec: metadata: type: object spec: - description: ValidatorConfigSpec defines the desired state of ValidatorConfig + description: ValidatorConfigSpec defines the desired state of ValidatorConfig. properties: plugins: + description: Plugins defines the configuration for the validator plugins. items: + description: HelmRelease defines the configuration for a Helm chart + release. properties: chart: + description: Chart defines the Helm chart to be installed. properties: authSecretName: - type: string - caFile: + description: AuthSecretName is the name of the K8s secret + containing the authentication details for the Helm repository. type: string insecureSkipVerify: + description: InsecureSkipTLSVerify skips the verification + of the server's certificate chain and host name. type: boolean name: + description: Name of the Helm chart. type: string repository: + description: Repository URL of the Helm chart. type: string version: + description: Version of the Helm chart. type: string required: - name @@ -62,6 +71,8 @@ spec: - version type: object values: + description: Values defines the values to be passed to the Helm + chart. type: string required: - chart @@ -69,12 +80,14 @@ spec: type: object type: array sink: + description: Sink defines the configuration for the notification sink. properties: secretName: - description: Name of a K8s secret containing configuration details - for the sink + description: SecretName is the name of a K8s secret containing + configuration details for the sink. type: string type: + description: Type of the sink. enum: - alertmanager - slack @@ -89,6 +102,8 @@ spec: properties: conditions: items: + description: ValidatorPluginCondition describes the state of a Validator + plugin. properties: lastUpdatedTime: description: |- diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index cd99ee6f..2d2646ae 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -45,6 +45,7 @@ import ( "github.com/validator-labs/validator/internal/kube" "github.com/validator-labs/validator/internal/sinks" "github.com/validator-labs/validator/pkg/helm" + "github.com/validator-labs/validator/pkg/helm/release" "github.com/validator-labs/validator/pkg/util" //+kubebuilder:scaffold:imports ) @@ -145,7 +146,7 @@ var _ = BeforeSuite(func() { err = (&ValidatorConfigReconciler{ Client: k8sManager.GetClient(), HelmClient: helm.NewHelmClient(rawConfig), - HelmSecretsClient: helm.NewSecretsClient(k8sManager.GetClient()), + HelmReleaseClient: release.NewHelmReleaseClient(k8sManager.GetClient()), Log: ctrl.Log.WithName("controllers").WithName("ValidatorConfig"), Scheme: k8sManager.GetScheme(), }).SetupWithManager(k8sManager) diff --git a/internal/controller/validationresult_controller.go b/internal/controller/validationresult_controller.go index 3b0241f9..c55432f3 100644 --- a/internal/controller/validationresult_controller.go +++ b/internal/controller/validationresult_controller.go @@ -54,6 +54,7 @@ type ValidationResultReconciler struct { //+kubebuilder:rbac:groups=validation.spectrocloud.labs,resources=validationresults/status,verbs=get;update;patch //+kubebuilder:rbac:groups=validation.spectrocloud.labs,resources=validationresults/finalizers,verbs=update +// Reconcile reconciles a ValidationResult. func (r *ValidationResultReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { r.Log.V(0).Info("Reconciling ValidationResult", "name", req.Name, "namespace", req.Namespace) diff --git a/internal/controller/validatorconfig_controller.go b/internal/controller/validatorconfig_controller.go index e415cd05..e23185a4 100644 --- a/internal/controller/validatorconfig_controller.go +++ b/internal/controller/validatorconfig_controller.go @@ -45,21 +45,22 @@ import ( cleanv1 "buf.build/gen/go/spectrocloud/spectro-cleanup/protocolbuffers/go/cleanup/v1" v1alpha1 "github.com/validator-labs/validator/api/v1alpha1" "github.com/validator-labs/validator/pkg/helm" + helmrelease "github.com/validator-labs/validator/pkg/helm/release" ) const ( - // A finalizer added to a ValidatorConfig to ensure that plugin Helm releases are properly garbage collected + // CleanupFinalizer ensures that plugin Helm releases are properly garbage collected. CleanupFinalizer = "validator/cleanup" - // An annotation added to a ValidatorConfig to determine whether or not to update a plugin's Helm release + // PluginValuesHash is an annotation key added to a ValidatorConfig to determine whether to update a plugin's Helm release. PluginValuesHash = "validator/plugin-values" ) // ValidatorConfigReconciler reconciles a ValidatorConfig object type ValidatorConfigReconciler struct { client.Client - HelmClient helm.HelmClient - HelmSecretsClient helm.SecretsClient + HelmClient helm.Client + HelmReleaseClient helmrelease.Client Log logr.Logger Scheme *runtime.Scheme } @@ -68,6 +69,7 @@ type ValidatorConfigReconciler struct { //+kubebuilder:rbac:groups=validation.spectrocloud.labs,resources=validatorconfigs/status,verbs=get;update;patch //+kubebuilder:rbac:groups=validation.spectrocloud.labs,resources=validatorconfigs/finalizers,verbs=update +// Reconcile reconciles a ValidatorConfig. func (r *ValidatorConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { r.Log.V(0).Info("Reconciling ValidatorConfig", "name", req.Name, "namespace", req.Namespace) @@ -165,7 +167,7 @@ func (r *ValidatorConfigReconciler) redeployIfNeeded(ctx context.Context, vc *v1 Repo: p.Chart.Repository, Version: p.Chart.Version, Values: p.Values, - InsecureSkipTlsVerify: p.Chart.InsecureSkipTlsVerify, + InsecureSkipTLSVerify: p.Chart.InsecureSkipTLSVerify, } if p.Chart.AuthSecretName != "" { @@ -291,7 +293,7 @@ func getPluginHashKey(pluginName string) string { func (r *ValidatorConfigReconciler) deletePlugins(ctx context.Context, vc *v1alpha1.ValidatorConfig) error { var wg sync.WaitGroup for _, p := range vc.Spec.Plugins { - release, err := r.HelmSecretsClient.Get(ctx, p.Chart.Name, vc.Namespace) + release, err := r.HelmReleaseClient.Get(ctx, p.Chart.Name, vc.Namespace) if err != nil { if !apierrs.IsNotFound(err) { return err diff --git a/internal/kube/kube.go b/internal/kube/kube.go index c8fd6bf5..c9094b4b 100644 --- a/internal/kube/kube.go +++ b/internal/kube/kube.go @@ -1,3 +1,4 @@ +// Package kube contains Kubernetes utilities. package kube import ( @@ -6,6 +7,7 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) +// ConvertRestConfigToRawConfig converts a rest.Config to a clientcmdapi.Config. func ConvertRestConfigToRawConfig(config *rest.Config) (*clientcmdapi.Config, error) { raw, err := convertRestConfigToClientConfig(config).RawConfig() return &raw, err diff --git a/internal/sinks/alertmanager.go b/internal/sinks/alertmanager.go index f167d5be..439989e4 100644 --- a/internal/sinks/alertmanager.go +++ b/internal/sinks/alertmanager.go @@ -18,6 +18,7 @@ import ( "github.com/validator-labs/validator/api/v1alpha1" ) +// AlertmanagerSink is a sink for sending validation results to Alertmanager. type AlertmanagerSink struct { client Client log logr.Logger @@ -27,28 +28,36 @@ type AlertmanagerSink struct { password string } +// Alert is an Alertmanager alert. type Alert struct { + // Annotations are arbitrary key-value pairs. Annotations map[string]string `json:"annotations"` - Labels map[string]string `json:"labels"` + + // Labels are key-value pairs that can be used to group and filter alerts. + Labels map[string]string `json:"labels"` } var ( - InvalidEndpoint = errors.New("invalid Alertmanager config: endpoint scheme and host are required") - EndpointRequired = errors.New("invalid Alertmanager config: endpoint required") + // ErrInvalidEndpoint is returned when an Alertmanager endpoint is invalid. + ErrInvalidEndpoint = errors.New("invalid Alertmanager config: endpoint scheme and host are required") + + // ErrEndpointRequired is returned when the Alertmanager endpoint is not provided. + ErrEndpointRequired = errors.New("invalid Alertmanager config: endpoint required") ) +// Configure configures the AlertmanagerSink with the provided configuration. func (s *AlertmanagerSink) Configure(c Client, config map[string][]byte) error { // endpoint endpoint, ok := config["endpoint"] if !ok { - return EndpointRequired + return ErrEndpointRequired } u, err := url.Parse(string(endpoint)) if err != nil { return errors.Wrap(err, "invalid Alertmanager config: failed to parse endpoint") } if u.Scheme == "" || u.Host == "" { - return InvalidEndpoint + return ErrInvalidEndpoint } if u.Path != "" { s.log.V(1).Info("stripping path from Alertmanager endpoint", "path", u.Path) @@ -91,6 +100,7 @@ func (s *AlertmanagerSink) Configure(c Client, config map[string][]byte) error { return nil } +// Emit sends a ValidationResult to Alertmanager. func (s *AlertmanagerSink) Emit(r v1alpha1.ValidationResult) error { alerts := make([]Alert, 0, len(r.Status.ValidationConditions)) @@ -145,7 +155,7 @@ func (s *AlertmanagerSink) Emit(r v1alpha1.ValidationResult) error { } if resp.StatusCode != 200 { s.log.V(0).Info("failed to post alert", "endpoint", s.endpoint, "status", resp.Status, "code", resp.StatusCode) - return SinkEmissionFailed + return ErrSinkEmissionFailed } s.log.V(0).Info("Successfully posted alert to Alertmanager", "endpoint", s.endpoint, "status", resp.Status, "code", resp.StatusCode) diff --git a/internal/sinks/alertmanager_test.go b/internal/sinks/alertmanager_test.go index fe60deae..3b1e8163 100644 --- a/internal/sinks/alertmanager_test.go +++ b/internal/sinks/alertmanager_test.go @@ -36,7 +36,7 @@ func TestAlertmanagerConfigure(t *testing.T) { name: "Fail (no endpoint)", sink: AlertmanagerSink{}, config: map[string][]byte{}, - expected: EndpointRequired, + expected: ErrEndpointRequired, }, { name: "Fail (invalid endpoint)", @@ -44,7 +44,7 @@ func TestAlertmanagerConfigure(t *testing.T) { config: map[string][]byte{ "endpoint": []byte("_not_an_endpoint_"), }, - expected: InvalidEndpoint, + expected: ErrInvalidEndpoint, }, { name: "Fail (invalid insecureSkipVerify)", @@ -97,7 +97,7 @@ func TestAlertManagerEmit(t *testing.T) { server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "invalid auth", http.StatusUnauthorized) })), - expected: SinkEmissionFailed, + expected: ErrSinkEmissionFailed, }, } for _, c := range cs { diff --git a/internal/sinks/sink.go b/internal/sinks/sink.go index cd6d6706..347212ec 100644 --- a/internal/sinks/sink.go +++ b/internal/sinks/sink.go @@ -1,3 +1,4 @@ +// Package sinks contains sinks for emitting ValidationResults. package sinks import ( @@ -11,13 +12,16 @@ import ( "github.com/validator-labs/validator/pkg/types" ) -var SinkEmissionFailed = errors.New("sink emission failed") +// ErrSinkEmissionFailed is returned when emitting a validation result to a sink fails. +var ErrSinkEmissionFailed = errors.New("sink emission failed") +// Sink is an interface for sending validation results to a sink. type Sink interface { Configure(c Client, config map[string][]byte) error Emit(result v1alpha1.ValidationResult) error } +// NewSink returns a new Sink based on the provided SinkType. func NewSink(sinkType types.SinkType, log logr.Logger) Sink { switch sinkType { case types.SinkTypeAlertmanager: @@ -29,10 +33,12 @@ func NewSink(sinkType types.SinkType, log logr.Logger) Sink { } } +// Client is an HTTP client for a Sink. type Client struct { hclient *http.Client } +// NewClient returns a new Client with the provided timeout. func NewClient(timeout time.Duration) *Client { client := &http.Client{ Timeout: timeout, diff --git a/internal/sinks/slack.go b/internal/sinks/slack.go index 99283c9d..84f7e828 100644 --- a/internal/sinks/slack.go +++ b/internal/sinks/slack.go @@ -12,28 +12,31 @@ import ( "github.com/validator-labs/validator/api/v1alpha1" ) +// SlackSink is a sink for sending validation results to Slack. type SlackSink struct { apiToken string - channelId string + channelID string client Client log logr.Logger } +// Configure configures the SlackSink with the provided configuration. func (s *SlackSink) Configure(c Client, config map[string][]byte) error { apiToken, ok := config["apiToken"] if !ok { return errors.New("invalid Slack configuration: apiToken required") } - channelId, ok := config["channelId"] + channelID, ok := config["channelId"] if !ok { return errors.New("invalid Slack configuration: channelId required") } s.apiToken = string(apiToken) - s.channelId = string(channelId) + s.channelID = string(channelID) s.client = c return nil } +// Emit sends a ValidationResult to Slack. func (s *SlackSink) Emit(r v1alpha1.ValidationResult) error { api := slack.New( s.apiToken, @@ -46,16 +49,16 @@ func (s *SlackSink) Emit(r v1alpha1.ValidationResult) error { } _, timestamp, err := api.PostMessage( - s.channelId, + s.channelID, slack.MsgOptionBlocks(s.buildSlackBlocks(r)...), slack.MsgOptionUsername("Validator Bot"), slack.MsgOptionAsUser(true), ) if err != nil { - s.log.Error(err, "failed to post message", "channelId", s.channelId, "timestamp", timestamp) + s.log.Error(err, "failed to post message", "channelId", s.channelID, "timestamp", timestamp) return err } - s.log.V(0).Info("Successfully posted message to channel", "channelId", s.channelId, "timestamp", timestamp) + s.log.V(0).Info("Successfully posted message to channel", "channelId", s.channelID, "timestamp", timestamp) return nil } diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 7fb276c1..9aa0853a 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -1,6 +1,10 @@ +// Package constants contains validator constants. package constants const ( + // ValidationRulePrefix is the prefix for all validation rules. ValidationRulePrefix string = "validation" - ValidatorConfig string = "validator-config" + + // ValidatorConfig is the name of the default validator config. + ValidatorConfig string = "validator-config" ) diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 418b6c5a..e866bdc0 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -1,3 +1,4 @@ +// Package helm contains the helm CLI client interface and implementation. package helm import ( @@ -15,8 +16,11 @@ import ( ) var ( - CommandPath = "./helm" - preserveFiles = false // whether to preserve kubeconfig and Helm values files + // CommandPath is the location of the helm binary. + CommandPath = "./helm" + + // preserveFiles controls whether to preserve kubeconfig and Helm values files. + preserveFiles = false ) func init() { @@ -25,14 +29,15 @@ func init() { } } -// HelmClient is an interface for interacting with Helm -type HelmClient interface { +// Client is an interface for interacting with the Helm CLI. +type Client interface { Delete(name, namespace string) error Pull(options Options) error Upgrade(name, namespace string, options Options) error } -type helmClient struct { +// CLIClient is a Helm client that interacts with the Helm CLI. +type CLIClient struct { config *clientcmdapi.Config helmPath string @@ -41,14 +46,15 @@ type helmClient struct { } // NewHelmClient creates a new Helm client from the given config -func NewHelmClient(config *clientcmdapi.Config) *helmClient { - return &helmClient{ +func NewHelmClient(config *clientcmdapi.Config) *CLIClient { + return &CLIClient{ config: config, helmPath: CommandPath, } } -func (c *helmClient) Delete(name, namespace string) error { +// Delete deletes a Helm release. +func (c *CLIClient) Delete(name, namespace string) error { kubeConfig, err := writeKubeConfig(c.config) if err != nil { return err @@ -65,7 +71,8 @@ func (c *helmClient) Delete(name, namespace string) error { return c.exec(args) } -func (c *helmClient) Pull(options Options) error { +// Pull downloads a Helm chart. +func (c *CLIClient) Pull(options Options) error { if options.Repo == "" { return fmt.Errorf("chart repo cannot be null") } @@ -77,12 +84,13 @@ func (c *helmClient) Pull(options Options) error { return c.exec(args) } -func (c *helmClient) Upgrade(name, namespace string, options Options) error { +// Upgrade upgrades a Helm release. +func (c *CLIClient) Upgrade(name, namespace string, options Options) error { options.ExtraArgs = append(options.ExtraArgs, "--install") return c.run(name, namespace, options, "upgrade", options.ExtraArgs) } -func (c *helmClient) run(name, namespace string, options Options, command string, extraArgs []string) error { +func (c *CLIClient) run(name, namespace string, options Options, command string, extraArgs []string) error { kubeConfig, err := writeKubeConfig(c.config) if err != nil { return err @@ -190,7 +198,7 @@ func (c *helmClient) run(name, namespace string, options Options, command string return c.exec(args) } -func (c *helmClient) exec(args []string) error { +func (c *CLIClient) exec(args []string) error { if len(args) == 0 { return nil } diff --git a/pkg/helm/options.go b/pkg/helm/options.go new file mode 100644 index 00000000..e294c3b8 --- /dev/null +++ b/pkg/helm/options.go @@ -0,0 +1,75 @@ +package helm + +// Options holds all the options for installing/pulling/upgrading a chart. +type Options struct { + Chart string + Path string + + Repo string + Version string + Values string + SetValues map[string]string + SetStringValues map[string]string + + Username string + Password string + + Atomic bool + Force bool + CreateNamespace bool + + Untar bool + UntarDir string + + CaFile string + InsecureSkipTLSVerify bool + + ExtraArgs []string +} + +// ConfigureRepo adds the repo flag to the command. +func (o Options) ConfigureRepo(args []string) []string { + args = append(args, "--repo", o.Repo) + return args +} + +// ConfigureVersion adds the version flag to the command. +func (o Options) ConfigureVersion(args []string) []string { + if o.Version != "" { + args = append(args, "--version", o.Version) + } + return args +} + +// ConfigureArchive adds the archive flags to the command. +func (o Options) ConfigureArchive(args []string) []string { + if o.Untar { + args = append(args, "--untar") + } + if o.UntarDir != "" { + args = append(args, "--untardir", o.UntarDir) + } + return args +} + +// ConfigureAuth adds basic auth flags to the command. +func (o Options) ConfigureAuth(args []string) []string { + if o.Username != "" { + args = append(args, "--username", o.Username) + } + if o.Password != "" { + args = append(args, "--password", o.Password) + } + return args +} + +// ConfigureTLS adds TLS flags to the command. +func (o Options) ConfigureTLS(args []string) []string { + if o.CaFile != "" { + args = append(args, "--ca-file", o.CaFile) + } + if o.InsecureSkipTLSVerify { + args = append(args, "--insecure-skip-tls-verify") + } + return args +} diff --git a/pkg/helm/secrets.go b/pkg/helm/release/release.go similarity index 68% rename from pkg/helm/secrets.go rename to pkg/helm/release/release.go index 7c85e56a..46bbf5b8 100644 --- a/pkg/helm/secrets.go +++ b/pkg/helm/release/release.go @@ -1,18 +1,5 @@ -/* -Copyright The Helm Authors. -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 helm +// Package release includes a client for fetching Helm releases from Kubernetes secrets. +package release import ( "bytes" @@ -33,26 +20,26 @@ import ( var magicGzip = []byte{0x1f, 0x8b, 0x08} -// SecretsClient is a subset of the kubernetes SecretsInterface, geared towards handling Helm secrets. -type SecretsClient interface { +// Client is a subset of the kubernetes SecretsInterface, geared towards handling Helm secrets. +type Client interface { List(context.Context, klabels.Selector, string) ([]*Release, error) Get(context.Context, string, string) (*Release, error) } -// secretsClient implements the SecretsClient interface. -type secretsClient struct { +// HelmReleaseClient implements the Client interface. +type HelmReleaseClient struct { kubeClient client.Client } -// NewSecretsClient initializes a new secretsClient -func NewSecretsClient(client client.Client) *secretsClient { - return &secretsClient{ +// NewHelmReleaseClient initializes a new HelmReleaseClient. +func NewHelmReleaseClient(client client.Client) *HelmReleaseClient { + return &HelmReleaseClient{ kubeClient: client, } } -// List fetches all Helm releases in a namespace, filtered by a label selector -func (s *secretsClient) List(ctx context.Context, labels klabels.Selector, namespace string) ([]*Release, error) { +// List fetches all Helm releases in a namespace, filtered by a label selector. +func (s *HelmReleaseClient) List(ctx context.Context, labels klabels.Selector, namespace string) ([]*Release, error) { // ensure the label selector includes the 'owner: helm' label req, err := klabels.NewRequirement("owner", selection.Equals, []string{"helm"}) if err != nil { @@ -89,8 +76,8 @@ func (s *secretsClient) List(ctx context.Context, labels klabels.Selector, names return releases, nil } -// Get fetches the latest Helm release by name and namespace -func (s *secretsClient) Get(ctx context.Context, name string, namespace string) (*Release, error) { +// Get fetches the latest Helm release by name and namespace. +func (s *HelmReleaseClient) Get(ctx context.Context, name string, namespace string) (*Release, error) { ls := klabels.Set{} ls["name"] = name releaseList, err := s.List(ctx, ls.AsSelector(), namespace) @@ -109,7 +96,7 @@ func (s *secretsClient) Get(ctx context.Context, name string, namespace string) return latest, nil } -// decodeRelease decodes secret data into a Helm release +// decodeRelease decodes secret data into a Helm release. func decodeRelease(secret *corev1.Secret, data string) (*Release, error) { b, err := base64.StdEncoding.DecodeString(data) if err != nil { diff --git a/pkg/helm/release/time.go b/pkg/helm/release/time.go new file mode 100644 index 00000000..5b32e659 --- /dev/null +++ b/pkg/helm/release/time.go @@ -0,0 +1,96 @@ +package release + +import ( + "bytes" + "time" +) + +// emptyString contains an empty JSON string value to be used as output. +var emptyString = `""` + +// Time is a convenience wrapper around stdlib time, but with different +// marshalling and unmarshaling for zero values. +type Time struct { + time.Time +} + +// Now returns the current time. It is a convenience wrapper around time.Now(). +func Now() Time { + return Time{time.Now()} +} + +// MarshalJSON marshals a Time to JSON. +func (t Time) MarshalJSON() ([]byte, error) { + if t.Time.IsZero() { + return []byte(emptyString), nil + } + return t.Time.MarshalJSON() +} + +// UnmarshalJSON unmarshals a Time from JSON. +func (t *Time) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + return nil + } + // If it is empty, we don't have to set anything since time.Time is not a + // pointer and will be set to the zero value + if bytes.Equal([]byte(emptyString), b) { + return nil + } + return t.Time.UnmarshalJSON(b) +} + +// Parse parses a formatted string and returns the time value it represents. +func Parse(layout, value string) (Time, error) { + t, err := time.Parse(layout, value) + return Time{Time: t}, err +} + +// ParseInLocation is like Parse but with a location. +func ParseInLocation(layout, value string, loc *time.Location) (Time, error) { + t, err := time.ParseInLocation(layout, value, loc) + return Time{Time: t}, err +} + +// Date returns the Time corresponding to the given year, month, day, hour, min, sec, and nsec. +func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { + return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// Unix generates a Time from sec and nsec. +func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} } + +// Add returns the Time t+d. +func (t Time) Add(d time.Duration) Time { return Time{Time: t.Time.Add(d)} } + +// AddDate returns the Time corresponding to adding the given number of years, months, and days to t. +func (t Time) AddDate(years int, months int, days int) Time { + return Time{Time: t.Time.AddDate(years, months, days)} +} + +// After reports whether the time instant t is after u. +func (t Time) After(u Time) bool { return t.Time.After(u.Time) } + +// Before reports whether the time instant t is before u. +func (t Time) Before(u Time) bool { return t.Time.Before(u.Time) } + +// Equal reports whether Time t equals u. +func (t Time) Equal(u Time) bool { return t.Time.Equal(u.Time) } + +// In returns a copy of t representing the same time instant, but with a new location. +func (t Time) In(loc *time.Location) Time { return Time{Time: t.Time.In(loc)} } + +// Local returns t with the location set to local time. +func (t Time) Local() Time { return Time{Time: t.Time.Local()} } + +// Round returns the result of rounding t to the nearest multiple of d (since the zero time). +func (t Time) Round(d time.Duration) Time { return Time{Time: t.Time.Round(d)} } + +// Sub returns the duration t-u. +func (t Time) Sub(u Time) time.Duration { return t.Time.Sub(u.Time) } + +// Truncate returns t truncated to a multiple of d (since the zero time). +func (t Time) Truncate(d time.Duration) Time { return Time{Time: t.Time.Truncate(d)} } + +// UTC returns t with the location set to UTC. +func (t Time) UTC() Time { return Time{Time: t.Time.UTC()} } diff --git a/pkg/helm/types.go b/pkg/helm/release/types.go similarity index 61% rename from pkg/helm/types.go rename to pkg/helm/release/types.go index c3caecb2..f2773345 100644 --- a/pkg/helm/types.go +++ b/pkg/helm/release/types.go @@ -1,4 +1,4 @@ -package helm +package release import ( corev1 "k8s.io/api/core/v1" @@ -7,20 +7,26 @@ import ( // Release describes a deployment of a chart, together with the chart // and the variables used to deploy that chart. type Release struct { - // Name is the name of the release + // Name is the name of the release. Name string `json:"name,omitempty"` - // Info provides information about a release + + // Info provides information about a release. Info *Info `json:"info,omitempty"` + // Chart is the chart that was released. Chart *Chart `json:"chart,omitempty"` + // Config is the set of extra Values added to the chart. // These values override the default values inside of the chart. Config map[string]interface{} `json:"config,omitempty"` + // Version is an int which represents the version of the release. Version int `json:"version,omitempty"` + // Namespace is the kubernetes namespace of the release. Namespace string `json:"namespace,omitempty"` + // Secret is the secret containing the release's secret values. Secret *corev1.Secret `json:"-"` } @@ -29,160 +35,117 @@ type Info struct { // FirstDeployed is when the release was first deployed. // +optional FirstDeployed Time `json:"first_deployed,omitempty"` + // LastDeployed is when the release was last deployed. // +optional LastDeployed Time `json:"last_deployed,omitempty"` + // Deleted tracks when this object was deleted. // +optional Deleted Time `json:"deleted,omitempty"` + // Description is human-friendly "log entry" about this release. // +optional Description string `json:"description,omitempty"` - // Status is the current state of the release + + // Status is the current state of the release. // +optional Status string `json:"status,omitempty"` - // Contains the rendered templates/NOTES.txt if available + + // Contains the rendered templates/NOTES.txt if available. // +optional Notes string `json:"notes,omitempty"` } -// Chart holds the chart metadata +// Chart holds the chart metadata. type Chart struct { + // Metadata is the contents of the Chart.yaml file. Metadata *Metadata `json:"metadata,omitempty"` } -// Options holds all the options for installing/pulling/upgrading a chart -type Options struct { - Chart string - Path string - - Repo string - Version string - Values string - SetValues map[string]string - SetStringValues map[string]string - - Username string - Password string - - Atomic bool - Force bool - CreateNamespace bool - - Untar bool - UntarDir string - - CaFile string - InsecureSkipTlsVerify bool - - ExtraArgs []string -} - -func (o Options) ConfigureRepo(args []string) []string { - args = append(args, "--repo", o.Repo) - return args -} - -func (o Options) ConfigureVersion(args []string) []string { - if o.Version != "" { - args = append(args, "--version", o.Version) - } - return args -} - -func (o Options) ConfigureArchive(args []string) []string { - if o.Untar { - args = append(args, "--untar") - } - if o.UntarDir != "" { - args = append(args, "--untardir", o.UntarDir) - } - return args -} - -func (o Options) ConfigureAuth(args []string) []string { - if o.Username != "" { - args = append(args, "--username", o.Username) - } - if o.Password != "" { - args = append(args, "--password", o.Password) - } - return args -} - -func (o Options) ConfigureTLS(args []string) []string { - if o.CaFile != "" { - args = append(args, "--ca-file", o.CaFile) - } - if o.InsecureSkipTlsVerify { - args = append(args, "--insecure-skip-tls-verify") - } - return args -} - +// Maintainer describes a chart maintainer. type Maintainer struct { - // Name is a user name or organization name + // Name is a user name or organization name. // +optional Name string `json:"name,omitempty"` - // Email is an optional email address to contact the named maintainer + + // Email is an optional email address to contact the named maintainer. // +optional Email string `json:"email,omitempty"` - // URL is an optional URL to an address for the named maintainer + + // URL is an optional URL to an address for the named maintainer. // +optional URL string `json:"url,omitempty"` } +// Metadata describes a chart's metadata. type Metadata struct { - // The name of the chart + // The name of the chart. // +optional Name string `json:"name,omitempty"` - // The URL to a relevant project page, git repo, or contact person + + // The URL to a relevant project page, git repo, or contact person. // +optional Home string `json:"home,omitempty"` - // Source is the URL to the source code of this chart + + // Source is the URL to the source code of this chart. // +optional Sources []string `json:"sources,omitempty"` - // A SemVer 2 conformant version string of the chart + + // A SemVer 2 conformant version string of the chart. // +optional Version string `json:"version,omitempty"` - // A one-sentence description of the chart + + // A one-sentence description of the chart. // +optional Description string `json:"description,omitempty"` - // A list of string keywords + + // A list of string keywords. // +optional Keywords []string `json:"keywords,omitempty"` - // A list of name and URL/email address combinations for the maintainer(s) + + // A list of name and URL/email address combinations for the maintainer(s). // +optional Maintainers []*Maintainer `json:"maintainers,omitempty"` + // The URL to an icon file. // +optional Icon string `json:"icon,omitempty"` + // The API Version of this chart. // +optional APIVersion string `json:"apiVersion,omitempty"` - // The condition to check to enable chart + + // The condition to check to enable chart. // +optional Condition string `json:"condition,omitempty"` - // The tags to check to enable chart + + // The tags to check to enable chart. // +optional Tags string `json:"tags,omitempty"` + // The version of the application enclosed inside of this chart. // +optional AppVersion string `json:"appVersion,omitempty"` - // Whether or not this chart is deprecated + + // Whether or not this chart is deprecated. // +optional Deprecated bool `json:"deprecated,omitempty"` + // Annotations are additional mappings uninterpreted by Helm, // made available for inspection by other applications. // +optional Annotations map[string]string `json:"annotations,omitempty"` + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. // +optional KubeVersion string `json:"kubeVersion,omitempty"` - // Specifies the chart type: application or library + + // Specifies the chart type: application or library. // +optional Type string `json:"type,omitempty"` - // Urls where to find the chart contents + + // Urls where to find the chart contents. // +optional Urls []string `json:"urls,omitempty"` } diff --git a/pkg/helm/time.go b/pkg/helm/time.go deleted file mode 100644 index 8def713a..00000000 --- a/pkg/helm/time.go +++ /dev/null @@ -1,69 +0,0 @@ -package helm - -import ( - "bytes" - "time" -) - -// emptyString contains an empty JSON string value to be used as output -var emptyString = `""` - -// Time is a convenience wrapper around stdlib time, but with different -// marshalling and unmarshaling for zero values -type Time struct { - time.Time -} - -// Now returns the current time. It is a convenience wrapper around time.Now() -func Now() Time { - return Time{time.Now()} -} - -func (t Time) MarshalJSON() ([]byte, error) { - if t.Time.IsZero() { - return []byte(emptyString), nil - } - return t.Time.MarshalJSON() -} - -func (t *Time) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - return nil - } - // If it is empty, we don't have to set anything since time.Time is not a - // pointer and will be set to the zero value - if bytes.Equal([]byte(emptyString), b) { - return nil - } - return t.Time.UnmarshalJSON(b) -} - -func Parse(layout, value string) (Time, error) { - t, err := time.Parse(layout, value) - return Time{Time: t}, err -} - -func ParseInLocation(layout, value string, loc *time.Location) (Time, error) { - t, err := time.ParseInLocation(layout, value, loc) - return Time{Time: t}, err -} - -func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { - return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)} -} - -func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} } - -func (t Time) Add(d time.Duration) Time { return Time{Time: t.Time.Add(d)} } -func (t Time) AddDate(years int, months int, days int) Time { - return Time{Time: t.Time.AddDate(years, months, days)} -} -func (t Time) After(u Time) bool { return t.Time.After(u.Time) } -func (t Time) Before(u Time) bool { return t.Time.Before(u.Time) } -func (t Time) Equal(u Time) bool { return t.Time.Equal(u.Time) } -func (t Time) In(loc *time.Location) Time { return Time{Time: t.Time.In(loc)} } -func (t Time) Local() Time { return Time{Time: t.Time.Local()} } -func (t Time) Round(d time.Duration) Time { return Time{Time: t.Time.Round(d)} } -func (t Time) Sub(u Time) time.Duration { return t.Time.Sub(u.Time) } -func (t Time) Truncate(d time.Duration) Time { return Time{Time: t.Time.Truncate(d)} } -func (t Time) UTC() Time { return Time{Time: t.Time.UTC()} } diff --git a/pkg/types/types.go b/pkg/types/types.go index 828a1f27..93d0eefb 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,3 +1,4 @@ +// Package types contains structs used by to construct ValidationResults. package types import "github.com/validator-labs/validator/api/v1alpha1" @@ -20,9 +21,13 @@ func (v *ValidationResponse) AddResult(vrr *ValidationRuleResult, err error) { v.ValidationRuleErrors = append(v.ValidationRuleErrors, err) } +// SinkType is the type of sink to which a notification should be sent. type SinkType string const ( + // SinkTypeAlertmanager is an Alertmanager sink. SinkTypeAlertmanager SinkType = "alertmanager" - SinkTypeSlack SinkType = "slack" + + // SinkTypeSlack is a Slack sink. + SinkTypeSlack SinkType = "slack" ) diff --git a/pkg/util/ptr.go b/pkg/util/ptr.go index 994d51f7..5e3acba5 100644 --- a/pkg/util/ptr.go +++ b/pkg/util/ptr.go @@ -1,5 +1,7 @@ +// Package util contains utility functions. package util +// Ptr returns a pointer to any arbitrary variable. func Ptr[T any](x T) *T { return &x } diff --git a/pkg/util/test.go b/pkg/util/test.go index e48a4db0..f2e2630a 100644 --- a/pkg/util/test.go +++ b/pkg/util/test.go @@ -7,6 +7,7 @@ import ( "github.com/validator-labs/validator/pkg/types" ) +// CheckTestCase checks the result of a validation rule against the expected result. func CheckTestCase(t *testing.T, res *types.ValidationRuleResult, expectedResult types.ValidationRuleResult, err, expectedError error) { if !reflect.DeepEqual(res.State, expectedResult.State) { t.Errorf("expected state (%+v), got (%+v)", expectedResult.State, res.State) diff --git a/pkg/validationresult/validation_result.go b/pkg/validationresult/validation_result.go index 8c2d0747..858ee514 100644 --- a/pkg/validationresult/validation_result.go +++ b/pkg/validationresult/validation_result.go @@ -1,3 +1,4 @@ +// Package validationresult contains functions for handling ValidationResult objects. package validationresult import ( @@ -19,6 +20,7 @@ import ( const validationErrorMsg = "Validation failed with an unexpected error" +// Patcher is an interface for patching objects. type Patcher interface { Patch(ctx context.Context, obj client.Object, opts ...patch.Option) error } @@ -28,11 +30,9 @@ func HandleExistingValidationResult(vr *v1alpha1.ValidationResult, l logr.Logger l = l.WithValues("name", vr.Name, "namespace", vr.Namespace, "state", vr.Status.State) switch vr.Status.State { - case v1alpha1.ValidationInProgress: // validations are only left in progress if an unexpected error occurred l.V(0).Info("Previous validation failed with unexpected error") - case v1alpha1.ValidationFailed: // log validation failure, but continue and retry cs := getInvalidConditions(vr.Status.ValidationConditions) @@ -43,7 +43,6 @@ func HandleExistingValidationResult(vr *v1alpha1.ValidationResult, l logr.Logger ) } } - case v1alpha1.ValidationSucceeded: // log validation success, continue to re-validate l.V(0).Info("Previous validation succeeded. Re-validating.") @@ -104,7 +103,7 @@ func SafeUpdateValidationResult(ctx context.Context, p Patcher, vr *v1alpha1.Val return nil } -// updateValidationResultStatus updates a ValidationResult's status with the result of a single validation rule +// updateValidationResultStatus updates a ValidationResult's status with the result of a single validation rule. func updateValidationResultStatus(vr *v1alpha1.ValidationResult, vrr *types.ValidationRuleResult, vrrErr error, l logr.Logger) { // Finalize result State and Condition in the event of an unexpected error @@ -143,7 +142,7 @@ func updateValidationResultStatus(vr *v1alpha1.ValidationResult, vrr *types.Vali ) } -// getInvalidConditions filters a ValidationCondition array and returns all conditions corresponding to a failed validation +// getInvalidConditions filters a ValidationCondition array and returns all conditions corresponding to a failed validation. func getInvalidConditions(conditions []v1alpha1.ValidationCondition) []v1alpha1.ValidationCondition { invalidConditions := make([]v1alpha1.ValidationCondition, 0) for _, c := range conditions { @@ -154,7 +153,7 @@ func getInvalidConditions(conditions []v1alpha1.ValidationCondition) []v1alpha1. return invalidConditions } -// getConditionIndexByValidationRule retrieves the index of a condition from a ValidationCondition array matching a specific reason +// getConditionIndexByValidationRule retrieves the index of a condition from a ValidationCondition array matching a specific reason. func getConditionIndexByValidationRule(conditions []v1alpha1.ValidationCondition, validationRule string) int { for i, c := range conditions { if c.ValidationRule == validationRule {