diff --git a/api/v1alpha1/nodereadinessrule_types.go b/api/v1alpha1/nodereadinessrule_types.go index 34fd203..b12e917 100644 --- a/api/v1alpha1/nodereadinessrule_types.go +++ b/api/v1alpha1/nodereadinessrule_types.go @@ -21,87 +21,268 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NodeReadinessRuleSpec defines the desired state of NodeReadinessRule +// EnforcementMode specifies how the controller maintains the desired state. +// +kubebuilder:validation:Enum=bootstrap-only;continuous +type EnforcementMode string + +const ( + // EnforcementModeBootstrapOnly applies configuration only during the first reconcile. + EnforcementModeBootstrapOnly EnforcementMode = "bootstrap-only" + + // EnforcementModeContinuous continuously monitors and enforces the configuration. + EnforcementModeContinuous EnforcementMode = "continuous" +) + +// TaintStatus specifies status of the Taint on Node. +// +kubebuilder:validation:Enum=Present;Absent +type TaintStatus string + +const ( + // TaintStatusPresent represent the taint present on the Node. + TaintStatusPresent TaintStatus = "Present" + + // TaintStatusAbsent represent the taint absent on the Node. + TaintStatusAbsent TaintStatus = "Absent" +) + +// NodeReadinessRuleSpec defines the desired state of NodeReadinessRule. type NodeReadinessRuleSpec struct { - // Replace single ConditionType with multiple conditions - Conditions []ConditionRequirement `json:"conditions"` + // conditions contains a list of the Node conditions that defines the specific + // criteria that must be met for taints to be managed on the target Node. + // The presence or status of these conditions directly triggers the application or removal of Node taints. + // + // +required + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=32 + Conditions []ConditionRequirement `json:"conditions"` //nolint:kubeapilinter - // Add enforcement mode - EnforcementMode EnforcementMode `json:"enforcementMode"` + // enforcementMode specifies how the controller maintains the desired state. + // enforcementMode is one of bootstrap-only, continuous. + // "bootstrap-only" applies the configuration once during initial setup. + // "continuous" ensures the state is monitored and corrected throughout the resource lifecycle. + // When omitted, default value will be "continuous". + // + // +optional + EnforcementMode EnforcementMode `json:"enforcementMode,omitempty"` - // Simplify taint specification (remove TaintKey, TaintEffect separation) - Taint TaintSpec `json:"taint"` + // taint defines the specific Taint (Key, Value, and Effect) to be managed + // on Nodes that meet the defined condition criteria. + // + // +required + Taint corev1.Taint `json:"taint,omitempty"` - // Keep existing fields + // nodeSelector limits the scope of this rule to a specific subset of Nodes. + // If unspecified, this rule applies to all Nodes in the cluster. + // + // +optional NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` - GracePeriod *metav1.Duration `json:"gracePeriod,omitempty"` - // Add dry run support - DryRun bool `json:"dryRun,omitempty"` + // dryRun when set to true, The controller will evaluate Node conditions and log intended taint modifications + // without persisting changes to the cluster. Proposed actions are reflected in the resource status. + // + // +optional + DryRun bool `json:"dryRun,omitempty"` //nolint:kubeapilinter } -// New types to add +// ConditionRequirement defines a specific Node condition and the status value +// required to trigger the controller's action. type ConditionRequirement struct { - Type string `json:"type"` - RequiredStatus corev1.ConditionStatus `json:"requiredStatus"` -} + // type of Node condition + // + // Following kubebuilder validation is referred from https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=316 + Type string `json:"type,omitempty"` -type TaintSpec struct { - Key string `json:"key"` - Effect corev1.TaintEffect `json:"effect"` - Value string `json:"value,omitempty"` + // requiredStatus is status of the condition, one of True, False, Unknown. + // + // +required + // +kubebuilder:validation:Enum=True;False;Unknown + RequiredStatus corev1.ConditionStatus `json:"requiredStatus,omitempty"` } -type EnforcementMode string - -const ( - EnforcementModeBootstrapOnly EnforcementMode = "bootstrap-only" - EnforcementModeContinuous EnforcementMode = "continuous" -) - // NodeReadinessRuleStatus defines the observed state of NodeReadinessRule. +// +kubebuilder:validation:MinProperties=1 type NodeReadinessRuleStatus struct { - // Keep existing - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - Conditions []metav1.Condition `json:"conditions,omitempty"` - AppliedNodes []string `json:"appliedNodes,omitempty"` + // observedGeneration reflects the generation of the most recently observed NodeReadinessRule by the controller. + // + // +optional + // +kubebuilder:validation:Minimum=1 + ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // Add new status tracking + // appliedNodes lists the names of Nodes where the taint has been successfully managed. + // This provides a quick reference to the scope of impact for this rule. + // + // +optional + // +listType=set + // +kubebuilder:validation:MaxItems=5000 + // +kubebuilder:validation:items:MaxLength=253 + AppliedNodes []string `json:"appliedNodes,omitempty"` + + // failedNodes lists the Nodes where the rule evaluation encountered an error. + // This is used for troubleshooting configuration issues, such as invalid selectors during node lookup. + // + // +optional + // +listType=map + // +listMapKey=nodeName + // +kubebuilder:validation:MaxItems=5000 + FailedNodes []NodeFailure `json:"failedNodes,omitempty"` + + // nodeEvaluations provides detailed insight into the rule's assessment for individual Nodes. + // This is primarily used for auditing and debugging why specific Nodes were or + // were not targeted by the rule. + // + // +optional + // +listType=map + // +listMapKey=nodeName + // +kubebuilder:validation:MaxItems=5000 NodeEvaluations []NodeEvaluation `json:"nodeEvaluations,omitempty"` - FailedNodes []NodeFailure `json:"failedNodes,omitempty"` - // Add dry run results - DryRunResults *DryRunResults `json:"dryRunResults,omitempty"` + // dryRunResults captures the outcome of the rule evaluation when DryRun is enabled. + // This field provides visibility into the actions the controller would have taken, + // allowing users to preview taint changes before they are committed. + // + // +optional + DryRunResults DryRunResults `json:"dryRunResults,omitempty,omitzero"` +} + +// NodeFailure provides diagnostic details for Nodes that could not be successfully evaluated by the rule. +type NodeFailure struct { + // nodeName is the name of the failed Node. + // + // Following kubebuilder validation is referred from + // https://github.com/kubernetes/apimachinery/blob/84d740c9e27f3ccc94c8bc4d13f1b17f60f7080b/pkg/util/validation/validation.go#L198 + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + NodeName string `json:"nodeName,omitempty"` + + // reason provides a brief explanation of the evaluation result. + // + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + Reason string `json:"reason,omitempty"` + + // message is a human-readable message indicating details about the evaluation. + // + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=10240 + Message string `json:"message,omitempty"` + + // lastEvaluationTime is the timestamp of the last rule check failed for this Node. + // + // +required + LastEvaluationTime metav1.Time `json:"lastEvaluationTime,omitempty,omitzero"` } +// NodeEvaluation provides a detailed audit of a single Node's compliance with the rule. type NodeEvaluation struct { - NodeName string `json:"nodeName"` - ConditionResults []ConditionEvaluationResult `json:"conditionResults"` - TaintStatus string `json:"taintStatus"` // "Present", "Absent", "Unknown" - LastEvaluated metav1.Time `json:"lastEvaluated"` + // nodeName is the name of the evaluated Node. + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + NodeName string `json:"nodeName,omitempty"` + + // conditionResults provides a detailed breakdown of each condition evaluation + // for this Node. This allows for granular auditing of which specific + // criteria passed or failed during the rule assessment. + // + // +required + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=5000 + ConditionResults []ConditionEvaluationResult `json:"conditionResults,omitempty"` + + // taintStatus represents the taint status on the Node, one of Present, Absent. + // + // +required + TaintStatus TaintStatus `json:"taintStatus,omitempty"` + + // lastEvaluationTime is the timestamp when the controller last assessed this Node. + // + // +required + LastEvaluationTime metav1.Time `json:"lastEvaluationTime,omitempty,omitzero"` } +// ConditionEvaluationResult provides a detailed report of the comparison between +// the Node's observed condition and the rule's requirement. type ConditionEvaluationResult struct { - Type string `json:"type"` - CurrentStatus corev1.ConditionStatus `json:"currentStatus"` - RequiredStatus corev1.ConditionStatus `json:"requiredStatus"` - Satisfied bool `json:"satisfied"` - Missing bool `json:"missing"` -} + // type corresponds to the Node condition type being evaluated. + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=316 + Type string `json:"type,omitempty"` -type NodeFailure struct { - NodeName string `json:"nodeName"` - Reason string `json:"reason"` - Message string `json:"message"` - LastUpdated metav1.Time `json:"lastUpdated"` + // currentStatus is the actual status value observed on the Node, one of True, False, Unknown. + // + // +required + // +kubebuilder:validation:Enum=True;False;Unknown + CurrentStatus corev1.ConditionStatus `json:"currentStatus,omitempty"` + + // requiredStatus is the status value defined in the rule that must be matched, one of True, False, Unknown. + // + // +required + // +kubebuilder:validation:Enum=True;False;Unknown + RequiredStatus corev1.ConditionStatus `json:"requiredStatus,omitempty"` + + // satisfied indicates whether the CurrentStatus matches the RequiredStatus. + // + // +required + Satisfied bool `json:"satisfied"` //nolint:kubeapilinter + + // missing indicates that the specified condition was not found on the Node. + // + // +required + Missing bool `json:"missing"` //nolint:kubeapilinter } +// DryRunResults provides a summary of the actions the controller would perform if DryRun mode is enabled. +// +kubebuilder:validation:MinProperties=1 type DryRunResults struct { - AffectedNodes int `json:"affectedNodes"` - TaintsToAdd int `json:"taintsToAdd"` - TaintsToRemove int `json:"taintsToRemove"` - RiskyOperations int `json:"riskyOperations"` - Summary string `json:"summary"` + // affectedNodes is the total count of Nodes that match the rule's criteria. + // + // +optional + // +kubebuilder:validation:Minimum=0 + AffectedNodes *int32 `json:"affectedNodes,omitempty"` + + // taintsToAdd is the number of Nodes that currently lack the specified taint and would have it applied. + // + // +optional + // +kubebuilder:validation:Minimum=0 + TaintsToAdd *int32 `json:"taintsToAdd,omitempty"` + + // taintsToRemove is the number of Nodes that currently possess the + // taint but no longer meet the criteria, leading to its removal. + // + // +optional + // +kubebuilder:validation:Minimum=0 + TaintsToRemove *int32 `json:"taintsToRemove,omitempty"` + + // riskyOperations represents the count of Nodes where required conditions + // are missing entirely, potentially indicating an ambiguous node state. + // + // +optional + // +kubebuilder:validation:Minimum=0 + RiskyOperations *int32 `json:"riskyOperations,omitempty"` + + // summary provides a human-readable overview of the dry run evaluation, + // highlighting key findings or warnings. + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=4096 + Summary string `json:"summary,omitempty"` } // +kubebuilder:object:root=true @@ -111,30 +292,39 @@ type DryRunResults struct { // +kubebuilder:printcolumn:name="Taint",type=string,JSONPath=`.spec.taint.key`,description="The readiness taint applied by this rule." // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="The age of this resource" -// NodeReadinessRule is the Schema for the NodeReadinessRules API +// NodeReadinessRule is the Schema for the NodeReadinessRules API. type NodeReadinessRule struct { metav1.TypeMeta `json:",inline"` // metadata is a standard object metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // // +optional metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` // spec defines the desired state of NodeReadinessRule + // // +required - Spec NodeReadinessRuleSpec `json:"spec"` + Spec NodeReadinessRuleSpec `json:"spec,omitempty,omitzero"` // status defines the observed state of NodeReadinessRule + // // +optional Status NodeReadinessRuleStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// NodeReadinessRuleList contains a list of NodeReadinessRule +// NodeReadinessRuleList contains a list of NodeReadinessRule. type NodeReadinessRuleList struct { metav1.TypeMeta `json:",inline"` + // metadata is the standard list's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#lists-and-simple-kinds + // + // +optional metav1.ListMeta `json:"metadata,omitempty"` - Items []NodeReadinessRule `json:"items"` + // items is the list of NodeReadinessRule. + Items []NodeReadinessRule `json:"items"` } func init() { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3c53e16..20e6a7a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -58,6 +58,26 @@ func (in *ConditionRequirement) DeepCopy() *ConditionRequirement { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DryRunResults) DeepCopyInto(out *DryRunResults) { *out = *in + if in.AffectedNodes != nil { + in, out := &in.AffectedNodes, &out.AffectedNodes + *out = new(int32) + **out = **in + } + if in.TaintsToAdd != nil { + in, out := &in.TaintsToAdd, &out.TaintsToAdd + *out = new(int32) + **out = **in + } + if in.TaintsToRemove != nil { + in, out := &in.TaintsToRemove, &out.TaintsToRemove + *out = new(int32) + **out = **in + } + if in.RiskyOperations != nil { + in, out := &in.RiskyOperations, &out.RiskyOperations + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DryRunResults. @@ -78,7 +98,7 @@ func (in *NodeEvaluation) DeepCopyInto(out *NodeEvaluation) { *out = make([]ConditionEvaluationResult, len(*in)) copy(*out, *in) } - in.LastEvaluated.DeepCopyInto(&out.LastEvaluated) + in.LastEvaluationTime.DeepCopyInto(&out.LastEvaluationTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeEvaluation. @@ -94,7 +114,7 @@ func (in *NodeEvaluation) DeepCopy() *NodeEvaluation { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeFailure) DeepCopyInto(out *NodeFailure) { *out = *in - in.LastUpdated.DeepCopyInto(&out.LastUpdated) + in.LastEvaluationTime.DeepCopyInto(&out.LastEvaluationTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFailure. @@ -174,17 +194,12 @@ func (in *NodeReadinessRuleSpec) DeepCopyInto(out *NodeReadinessRuleSpec) { *out = make([]ConditionRequirement, len(*in)) copy(*out, *in) } - out.Taint = in.Taint + in.Taint.DeepCopyInto(&out.Taint) if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } - if in.GracePeriod != nil { - in, out := &in.GracePeriod, &out.GracePeriod - *out = new(v1.Duration) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeReadinessRuleSpec. @@ -200,25 +215,11 @@ func (in *NodeReadinessRuleSpec) DeepCopy() *NodeReadinessRuleSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeReadinessRuleStatus) DeepCopyInto(out *NodeReadinessRuleStatus) { *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } if in.AppliedNodes != nil { in, out := &in.AppliedNodes, &out.AppliedNodes *out = make([]string, len(*in)) copy(*out, *in) } - if in.NodeEvaluations != nil { - in, out := &in.NodeEvaluations, &out.NodeEvaluations - *out = make([]NodeEvaluation, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } if in.FailedNodes != nil { in, out := &in.FailedNodes, &out.FailedNodes *out = make([]NodeFailure, len(*in)) @@ -226,11 +227,14 @@ func (in *NodeReadinessRuleStatus) DeepCopyInto(out *NodeReadinessRuleStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.DryRunResults != nil { - in, out := &in.DryRunResults, &out.DryRunResults - *out = new(DryRunResults) - **out = **in + if in.NodeEvaluations != nil { + in, out := &in.NodeEvaluations, &out.NodeEvaluations + *out = make([]NodeEvaluation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } + in.DryRunResults.DeepCopyInto(&out.DryRunResults) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeReadinessRuleStatus. @@ -242,18 +246,3 @@ func (in *NodeReadinessRuleStatus) DeepCopy() *NodeReadinessRuleStatus { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TaintSpec) DeepCopyInto(out *TaintSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaintSpec. -func (in *TaintSpec) DeepCopy() *TaintSpec { - if in == nil { - return nil - } - out := new(TaintSpec) - in.DeepCopyInto(out) - return out -} diff --git a/cmd/main.go b/cmd/main.go index 5a065ad..0b48d51 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -54,7 +54,7 @@ func init() { // +kubebuilder:scaffold:scheme } -// nolint:gocyclo +//nolint:gocyclo func main() { var metricsAddr string var enableLeaderElection bool diff --git a/config/crd/bases/readiness.node.x-k8s.io_nodereadinessrules.yaml b/config/crd/bases/readiness.node.x-k8s.io_nodereadinessrules.yaml index 2bcef9d..b64502c 100644 --- a/config/crd/bases/readiness.node.x-k8s.io_nodereadinessrules.yaml +++ b/config/crd/bases/readiness.node.x-k8s.io_nodereadinessrules.yaml @@ -33,7 +33,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: NodeReadinessRule is the Schema for the NodeReadinessRules API + description: NodeReadinessRule is the Schema for the NodeReadinessRules API. properties: apiVersion: description: |- @@ -56,29 +56,61 @@ spec: description: spec defines the desired state of NodeReadinessRule properties: conditions: - description: Replace single ConditionType with multiple conditions + description: |- + conditions contains a list of the Node conditions that defines the specific + criteria that must be met for taints to be managed on the target Node. + The presence or status of these conditions directly triggers the application or removal of Node taints. items: - description: New types to add + description: |- + ConditionRequirement defines a specific Node condition and the status value + required to trigger the controller's action. properties: requiredStatus: + description: requiredStatus is status of the condition, one + of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown type: string type: + description: |- + type of Node condition + + Following kubebuilder validation is referred from https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition + maxLength: 316 + minLength: 1 type: string required: - requiredStatus - type type: object + maxItems: 32 + minItems: 1 type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map dryRun: - description: Add dry run support + description: |- + dryRun when set to true, The controller will evaluate Node conditions and log intended taint modifications + without persisting changes to the cluster. Proposed actions are reflected in the resource status. type: boolean enforcementMode: - description: Add enforcement mode - type: string - gracePeriod: + description: |- + enforcementMode specifies how the controller maintains the desired state. + enforcementMode is one of bootstrap-only, continuous. + "bootstrap-only" applies the configuration once during initial setup. + "continuous" ensures the state is monitored and corrected throughout the resource lifecycle. + When omitted, default value will be "continuous". + enum: + - bootstrap-only + - continuous type: string nodeSelector: - description: Keep existing fields + description: |- + nodeSelector limits the scope of this rule to a specific subset of Nodes. + If unspecified, this rule applies to all Nodes in the cluster. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -124,14 +156,26 @@ spec: type: object x-kubernetes-map-type: atomic taint: - description: Simplify taint specification (remove TaintKey, TaintEffect - separation) + description: |- + taint defines the specific Taint (Key, Value, and Effect) to be managed + on Nodes that meet the defined condition criteria. properties: effect: + description: |- + Required. The effect of the taint on pods + that do not tolerate the taint. + Valid effects are NoSchedule, PreferNoSchedule and NoExecute. type: string key: + description: Required. The taint key to be applied to a node. + type: string + timeAdded: + description: TimeAdded represents the time at which the taint + was added. + format: date-time type: string value: + description: The taint value corresponding to the taint key. type: string required: - effect @@ -139,127 +183,158 @@ spec: type: object required: - conditions - - enforcementMode - taint type: object status: description: status defines the observed state of NodeReadinessRule + minProperties: 1 properties: appliedNodes: + description: |- + appliedNodes lists the names of Nodes where the taint has been successfully managed. + This provides a quick reference to the scope of impact for this rule. items: + maxLength: 253 type: string + maxItems: 5000 type: array - conditions: - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array + x-kubernetes-list-type: set dryRunResults: - description: Add dry run results + description: |- + dryRunResults captures the outcome of the rule evaluation when DryRun is enabled. + This field provides visibility into the actions the controller would have taken, + allowing users to preview taint changes before they are committed. + minProperties: 1 properties: affectedNodes: + description: affectedNodes is the total count of Nodes that match + the rule's criteria. + format: int32 + minimum: 0 type: integer riskyOperations: + description: |- + riskyOperations represents the count of Nodes where required conditions + are missing entirely, potentially indicating an ambiguous node state. + format: int32 + minimum: 0 type: integer summary: + description: |- + summary provides a human-readable overview of the dry run evaluation, + highlighting key findings or warnings. + maxLength: 4096 + minLength: 1 type: string taintsToAdd: + description: taintsToAdd is the number of Nodes that currently + lack the specified taint and would have it applied. + format: int32 + minimum: 0 type: integer taintsToRemove: + description: |- + taintsToRemove is the number of Nodes that currently possess the + taint but no longer meet the criteria, leading to its removal. + format: int32 + minimum: 0 type: integer required: - - affectedNodes - - riskyOperations - summary - - taintsToAdd - - taintsToRemove type: object failedNodes: + description: |- + failedNodes lists the Nodes where the rule evaluation encountered an error. + This is used for troubleshooting configuration issues, such as invalid selectors during node lookup. items: + description: NodeFailure provides diagnostic details for Nodes that + could not be successfully evaluated by the rule. properties: - lastUpdated: + lastEvaluationTime: + description: lastEvaluationTime is the timestamp of the last + rule check failed for this Node. format: date-time type: string message: + description: message is a human-readable message indicating + details about the evaluation. + maxLength: 10240 + minLength: 1 type: string nodeName: + description: |- + nodeName is the name of the failed Node. + + Following kubebuilder validation is referred from + https://github.com/kubernetes/apimachinery/blob/84d740c9e27f3ccc94c8bc4d13f1b17f60f7080b/pkg/util/validation/validation.go#L198 + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string reason: + description: reason provides a brief explanation of the evaluation + result. + maxLength: 256 + minLength: 1 type: string required: - - lastUpdated - - message + - lastEvaluationTime - nodeName - - reason type: object + maxItems: 5000 type: array + x-kubernetes-list-map-keys: + - nodeName + x-kubernetes-list-type: map nodeEvaluations: - description: Add new status tracking + description: |- + nodeEvaluations provides detailed insight into the rule's assessment for individual Nodes. + This is primarily used for auditing and debugging why specific Nodes were or + were not targeted by the rule. items: + description: NodeEvaluation provides a detailed audit of a single + Node's compliance with the rule. properties: conditionResults: + description: |- + conditionResults provides a detailed breakdown of each condition evaluation + for this Node. This allows for granular auditing of which specific + criteria passed or failed during the rule assessment. items: + description: |- + ConditionEvaluationResult provides a detailed report of the comparison between + the Node's observed condition and the rule's requirement. properties: currentStatus: + description: currentStatus is the actual status value + observed on the Node, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown type: string missing: + description: missing indicates that the specified condition + was not found on the Node. type: boolean requiredStatus: + description: requiredStatus is the status value defined + in the rule that must be matched, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown type: string satisfied: + description: satisfied indicates whether the CurrentStatus + matches the RequiredStatus. type: boolean type: + description: type corresponds to the Node condition type + being evaluated. + maxLength: 316 + minLength: 1 type: string required: - currentStatus @@ -268,24 +343,45 @@ spec: - satisfied - type type: object + maxItems: 5000 type: array - lastEvaluated: + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastEvaluationTime: + description: lastEvaluationTime is the timestamp when the controller + last assessed this Node. format: date-time type: string nodeName: + description: nodeName is the name of the evaluated Node. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string taintStatus: + description: taintStatus represents the taint status on the + Node, one of Present, Absent. + enum: + - Present + - Absent type: string required: - conditionResults - - lastEvaluated + - lastEvaluationTime - nodeName - taintStatus type: object + maxItems: 5000 type: array + x-kubernetes-list-map-keys: + - nodeName + x-kubernetes-list-type: map observedGeneration: - description: Keep existing + description: observedGeneration reflects the generation of the most + recently observed NodeReadinessRule by the controller. format: int64 + minimum: 1 type: integer type: object required: diff --git a/internal/controller/node_controller.go b/internal/controller/node_controller.go index 1cd79db..94b8ad4 100644 --- a/internal/controller/node_controller.go +++ b/internal/controller/node_controller.go @@ -32,7 +32,7 @@ import ( readinessv1alpha1 "sigs.k8s.io/node-readiness-controller/api/v1alpha1" ) -// NodeReconciler reconciles a Node object +// NodeReconciler reconciles a Node object. type NodeReconciler struct { client.Client Scheme *runtime.Scheme @@ -61,7 +61,7 @@ func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. return ctrl.Result{}, nil } -// processNodeAgainstAllRules processes a single node against all applicable rules +// processNodeAgainstAllRules processes a single node against all applicable rules. func (r *ReadinessGateController) processNodeAgainstAllRules(ctx context.Context, node *corev1.Node) { log := ctrl.LoggerFrom(ctx) @@ -123,7 +123,7 @@ func (r *ReadinessGateController) processNodeAgainstAllRules(ctx context.Context } } -// getConditionStatus gets the status of a condition on a node +// getConditionStatus gets the status of a condition on a node. func (r *ReadinessGateController) getConditionStatus(node *corev1.Node, conditionType string) corev1.ConditionStatus { for _, condition := range node.Status.Conditions { if string(condition.Type) == conditionType { @@ -133,8 +133,8 @@ func (r *ReadinessGateController) getConditionStatus(node *corev1.Node, conditio return corev1.ConditionUnknown } -// hasTaintBySpec checks if a node has a specific taint -func (r *ReadinessGateController) hasTaintBySpec(node *corev1.Node, taintSpec readinessv1alpha1.TaintSpec) bool { +// hasTaintBySpec checks if a node has a specific taint. +func (r *ReadinessGateController) hasTaintBySpec(node *corev1.Node, taintSpec corev1.Taint) bool { for _, taint := range node.Spec.Taints { if taint.Key == taintSpec.Key && taint.Effect == taintSpec.Effect { return true @@ -143,8 +143,8 @@ func (r *ReadinessGateController) hasTaintBySpec(node *corev1.Node, taintSpec re return false } -// addTaintBySpec adds a taint to a node -func (r *ReadinessGateController) addTaintBySpec(ctx context.Context, node *corev1.Node, taintSpec readinessv1alpha1.TaintSpec) error { +// addTaintBySpec adds a taint to a node. +func (r *ReadinessGateController) addTaintBySpec(ctx context.Context, node *corev1.Node, taintSpec corev1.Taint) error { patch := client.StrategicMergeFrom(node.DeepCopy()) node.Spec.Taints = append(node.Spec.Taints, corev1.Taint{ Key: taintSpec.Key, @@ -154,8 +154,8 @@ func (r *ReadinessGateController) addTaintBySpec(ctx context.Context, node *core return r.Patch(ctx, node, patch) } -// removeTaintBySpec removes a taint from a node -func (r *ReadinessGateController) removeTaintBySpec(ctx context.Context, node *corev1.Node, taintSpec readinessv1alpha1.TaintSpec) error { +// removeTaintBySpec removes a taint from a node. +func (r *ReadinessGateController) removeTaintBySpec(ctx context.Context, node *corev1.Node, taintSpec corev1.Taint) error { patch := client.StrategicMergeFrom(node.DeepCopy()) var newTaints []corev1.Taint for _, taint := range node.Spec.Taints { @@ -167,7 +167,7 @@ func (r *ReadinessGateController) removeTaintBySpec(ctx context.Context, node *c return r.Patch(ctx, node, patch) } -// Bootstrap completion tracking +// Bootstrap completion tracking. func (r *ReadinessGateController) isBootstrapCompleted(nodeName, ruleName string) bool { // Check node annotation node := &corev1.Node{} @@ -215,7 +215,7 @@ func (r *ReadinessGateController) markBootstrapCompleted(ctx context.Context, no } } -// recordNodeFailure records a failure for a specific node +// recordNodeFailure records a failure for a specific node. func (r *ReadinessGateController) recordNodeFailure( rule *readinessv1alpha1.NodeReadinessRule, nodeName, reason, message string, @@ -230,10 +230,10 @@ func (r *ReadinessGateController) recordNodeFailure( // Add new failure failedNodes = append(failedNodes, readinessv1alpha1.NodeFailure{ - NodeName: nodeName, - Reason: reason, - Message: message, - LastUpdated: metav1.Now(), + NodeName: nodeName, + Reason: reason, + Message: message, + LastEvaluationTime: metav1.Now(), }) rule.Status.FailedNodes = failedNodes @@ -279,7 +279,7 @@ func (r *NodeReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) Complete(r) } -// conditionsEqual checks if two condition slices are equal +// conditionsEqual checks if two condition slices are equal. func conditionsEqual(a, b []corev1.NodeCondition) bool { if len(a) != len(b) { return false @@ -300,7 +300,7 @@ func conditionsEqual(a, b []corev1.NodeCondition) bool { return true } -// taintsEqual checks if two taint slices are equal +// taintsEqual checks if two taint slices are equal. func taintsEqual(a, b []corev1.Taint) bool { if len(a) != len(b) { return false @@ -324,7 +324,7 @@ func taintsEqual(a, b []corev1.Taint) bool { return true } -// labelsEqual checks if two label maps are equal +// labelsEqual checks if two label maps are equal. func labelsEqual(a, b map[string]string) bool { if len(a) != len(b) { return false diff --git a/internal/controller/node_controller_test.go b/internal/controller/node_controller_test.go index f98fa32..5d444a3 100644 --- a/internal/controller/node_controller_test.go +++ b/internal/controller/node_controller_test.go @@ -152,7 +152,7 @@ var _ = Describe("Node Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: conditionType, RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: taintKey, Effect: corev1.TaintEffectNoSchedule, }, diff --git a/internal/controller/nodereadinessrule_controller.go b/internal/controller/nodereadinessrule_controller.go index ee30a5e..51f9cd7 100644 --- a/internal/controller/nodereadinessrule_controller.go +++ b/internal/controller/nodereadinessrule_controller.go @@ -38,11 +38,11 @@ import ( ) const ( - // finalizerName is the finalizer added to NodeReadinessRule to ensure cleanup + // finalizerName is the finalizer added to NodeReadinessRule to ensure cleanup. finalizerName = "readiness.node.x-k8s.io/cleanup-taints" ) -// ReadinessGateController manages node taints based on readiness gate rules +// ReadinessGateController manages node taints based on readiness gate rules. type ReadinessGateController struct { client.Client Scheme *runtime.Scheme @@ -56,7 +56,7 @@ type ReadinessGateController struct { globalDryRun bool } -// NewReadinessGateController creates a new controller +// NewReadinessGateController creates a new controller. func NewReadinessGateController(mgr ctrl.Manager, clientset kubernetes.Interface) *ReadinessGateController { return &ReadinessGateController{ Client: mgr.GetClient(), @@ -66,7 +66,7 @@ func NewReadinessGateController(mgr ctrl.Manager, clientset kubernetes.Interface } } -// RuleReconciler handles NodeReadinessRule reconciliation +// RuleReconciler handles NodeReadinessRule reconciliation. type RuleReconciler struct { client.Client Scheme *runtime.Scheme @@ -157,7 +157,7 @@ func (r *RuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. } } else { // Clear previous dry run results - rule.Status.DryRunResults = nil + rule.Status.DryRunResults = readinessv1alpha1.DryRunResults{} // Process all applicable nodes for this rule if err := r.Controller.processAllNodesForRule(ctx, rule); err != nil { @@ -181,7 +181,7 @@ func (r *RuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. return ctrl.Result{}, nil } -// cleanupDeletedNodes removes status entries for nodes that no longer exist +// cleanupDeletedNodes removes status entries for nodes that no longer exist. func (r *ReadinessGateController) cleanupDeletedNodes(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule) error { log := ctrl.LoggerFrom(ctx) @@ -236,7 +236,7 @@ func (r *ReadinessGateController) cleanupDeletedNodes(ctx context.Context, rule }) } -// processAllNodesForRule processes all nodes when a rule changes +// processAllNodesForRule processes all nodes when a rule changes. func (r *ReadinessGateController) processAllNodesForRule(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule) error { log := ctrl.LoggerFrom(ctx) @@ -268,7 +268,7 @@ func (r *ReadinessGateController) processAllNodesForRule(ctx context.Context, ru return nil } -// evaluateRuleForNode evaluates a single rule against a single node +// evaluateRuleForNode evaluates a single rule against a single node. func (r *ReadinessGateController) evaluateRuleForNode(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule, node *corev1.Node) error { log := ctrl.LoggerFrom(ctx) @@ -307,7 +307,8 @@ func (r *ReadinessGateController) evaluateRuleForNode(ctx context.Context, rule var err error - if shouldRemoveTaint && currentlyHasTaint { + switch { + case shouldRemoveTaint && currentlyHasTaint: log.Info("Removing taint", "node", node.Name, "rule", rule.Name, "taint", rule.Spec.Taint.Key) if err = r.removeTaintBySpec(ctx, node, rule.Spec.Taint); err != nil { @@ -319,23 +320,24 @@ func (r *ReadinessGateController) evaluateRuleForNode(ctx context.Context, rule r.markBootstrapCompleted(ctx, node.Name, rule.Name) } - } else if !shouldRemoveTaint && !currentlyHasTaint { + case !shouldRemoveTaint && !currentlyHasTaint: log.Info("Adding taint", "node", node.Name, "rule", rule.Name, "taint", rule.Spec.Taint.Key) if err = r.addTaintBySpec(ctx, node, rule.Spec.Taint); err != nil { return fmt.Errorf("failed to add taint: %w", err) } - } else { + + default: log.Info("No taint action needed", "node", node.Name, "rule", rule.Name, "shouldRemove", shouldRemoveTaint, "hasTaint", currentlyHasTaint) } // Determine observed taint status after any actions - var taintStatus string + var taintStatus readinessv1alpha1.TaintStatus if r.hasTaintBySpec(node, rule.Spec.Taint) { - taintStatus = "Present" + taintStatus = readinessv1alpha1.TaintStatusPresent } else { - taintStatus = "Absent" + taintStatus = readinessv1alpha1.TaintStatusAbsent } // Update evaluation status @@ -344,12 +346,12 @@ func (r *ReadinessGateController) evaluateRuleForNode(ctx context.Context, rule return nil } -// updateNodeEvaluationStatus updates the evaluation status for a specific node +// updateNodeEvaluationStatus updates the evaluation status for a specific node. func (r *ReadinessGateController) updateNodeEvaluationStatus( rule *readinessv1alpha1.NodeReadinessRule, nodeName string, conditionResults []readinessv1alpha1.ConditionEvaluationResult, - taintStatus string, + taintStatus readinessv1alpha1.TaintStatus, ) { // Find existing evaluation or create new var nodeEval *readinessv1alpha1.NodeEvaluation @@ -370,10 +372,10 @@ func (r *ReadinessGateController) updateNodeEvaluationStatus( // Update evaluation nodeEval.ConditionResults = conditionResults nodeEval.TaintStatus = taintStatus - nodeEval.LastEvaluated = metav1.Now() + nodeEval.LastEvaluationTime = metav1.Now() } -// getApplicableRulesForNode returns all rules applicable to a node +// getApplicableRulesForNode returns all rules applicable to a node. func (r *ReadinessGateController) getApplicableRulesForNode(ctx context.Context, node *corev1.Node) []*readinessv1alpha1.NodeReadinessRule { r.ruleCacheMutex.RLock() defer r.ruleCacheMutex.RUnlock() @@ -389,7 +391,7 @@ func (r *ReadinessGateController) getApplicableRulesForNode(ctx context.Context, return applicableRules } -// ruleAppliesTo checks if a rule applies to a node +// ruleAppliesTo checks if a rule applies to a node. func (r *ReadinessGateController) ruleAppliesTo(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule, node *corev1.Node) bool { log := ctrl.LoggerFrom(ctx) @@ -406,7 +408,7 @@ func (r *ReadinessGateController) ruleAppliesTo(ctx context.Context, rule *readi return selector.Matches(labels.Set(node.Labels)) } -// updateRuleCache updates the rule cache +// updateRuleCache updates the rule cache. func (r *ReadinessGateController) updateRuleCache(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule) { log := ctrl.LoggerFrom(ctx) r.ruleCacheMutex.Lock() @@ -420,7 +422,7 @@ func (r *ReadinessGateController) updateRuleCache(ctx context.Context, rule *rea "resourceVersion", ruleCopy.ResourceVersion) } -// getCachedRule retrieves a rule from cache +// getCachedRule retrieves a rule from cache. func (r *ReadinessGateController) getCachedRule(ruleName string) *readinessv1alpha1.NodeReadinessRule { r.ruleCacheMutex.RLock() defer r.ruleCacheMutex.RUnlock() @@ -432,7 +434,7 @@ func (r *ReadinessGateController) getCachedRule(ruleName string) *readinessv1alp return rule.DeepCopy() } -// removeRuleFromCache removes a rule from cache +// removeRuleFromCache removes a rule from cache. func (r *ReadinessGateController) removeRuleFromCache(ctx context.Context, ruleName string) { log := ctrl.LoggerFrom(ctx) r.ruleCacheMutex.Lock() @@ -442,7 +444,7 @@ func (r *ReadinessGateController) removeRuleFromCache(ctx context.Context, ruleN log.Info("Removed rule from cache", "rule", ruleName, "totalRules", len(r.ruleCache)) } -// updateRuleStatus updates the status of a NodeReadinessRule +// updateRuleStatus updates the status of a NodeReadinessRule. func (r *ReadinessGateController) updateRuleStatus(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule) error { log := ctrl.LoggerFrom(ctx) @@ -477,14 +479,14 @@ func (r *ReadinessGateController) updateRuleStatus(ctx context.Context, rule *re }) } -// processDryRun processes dry run for a rule +// processDryRun processes dry run for a rule. func (r *ReadinessGateController) processDryRun(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule) error { nodeList := &corev1.NodeList{} if err := r.List(ctx, nodeList); err != nil { return err } - var affectedNodes, taintsToAdd, taintsToRemove, riskyOps int + var affectedNodes, taintsToAdd, taintsToRemove, riskyOps int32 var summaryParts []string for _, node := range nodeList.Items { @@ -539,23 +541,23 @@ func (r *ReadinessGateController) processDryRun(ctx context.Context, rule *readi } // Update rule status with dry run results - rule.Status.DryRunResults = &readinessv1alpha1.DryRunResults{ - AffectedNodes: affectedNodes, - TaintsToAdd: taintsToAdd, - TaintsToRemove: taintsToRemove, - RiskyOperations: riskyOps, + rule.Status.DryRunResults = readinessv1alpha1.DryRunResults{ + AffectedNodes: &affectedNodes, + TaintsToAdd: &taintsToAdd, + TaintsToRemove: &taintsToRemove, + RiskyOperations: &riskyOps, Summary: summary, } return nil } -// SetGlobalDryRun sets the global dry run mode (emergency off-switch) +// SetGlobalDryRun sets the global dry run mode (emergency off-switch). func (r *ReadinessGateController) SetGlobalDryRun(dryRun bool) { r.globalDryRun = dryRun } -// cleanupTaintsForRule removes taints managed by this rule from all applicable nodes +// cleanupTaintsForRule removes taints managed by this rule from all applicable nodes. func (r *ReadinessGateController) cleanupTaintsForRule(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule) error { log := ctrl.LoggerFrom(ctx) @@ -591,7 +593,7 @@ func (r *ReadinessGateController) cleanupTaintsForRule(ctx context.Context, rule return nil } -// cleanupNodesAfterSelectorChange cleans up nodes that matched old selector but not new one +// cleanupNodesAfterSelectorChange cleans up nodes that matched old selector but not new one. func (r *ReadinessGateController) cleanupNodesAfterSelectorChange(ctx context.Context, oldRule, newRule *readinessv1alpha1.NodeReadinessRule) error { log := ctrl.LoggerFrom(ctx) @@ -648,7 +650,7 @@ func (r *ReadinessGateController) cleanupNodesAfterSelectorChange(ctx context.Co return nil } -// nodeSelectorChanged checks if nodeSelector has changed +// nodeSelectorChanged checks if nodeSelector has changed. func nodeSelectorChanged(current, previous *metav1.LabelSelector) bool { // Both nil - no change if current == nil && previous == nil { @@ -692,7 +694,7 @@ func nodeSelectorChanged(current, previous *metav1.LabelSelector) bool { return false } -// stringMapEqual checks if two string maps are equal +// stringMapEqual checks if two string maps are equal. func stringMapEqual(a, b map[string]string) bool { if len(a) != len(b) { return false @@ -707,7 +709,7 @@ func stringMapEqual(a, b map[string]string) bool { return true } -// containsFinalizer checks if a finalizer exists in the rule +// containsFinalizer checks if a finalizer exists in the rule. func containsFinalizer(rule *readinessv1alpha1.NodeReadinessRule, finalizer string) bool { for _, f := range rule.Finalizers { if f == finalizer { @@ -717,14 +719,14 @@ func containsFinalizer(rule *readinessv1alpha1.NodeReadinessRule, finalizer stri return false } -// addFinalizer adds a finalizer to the rule +// addFinalizer adds a finalizer to the rule. func addFinalizer(rule *readinessv1alpha1.NodeReadinessRule, finalizer string) { if !containsFinalizer(rule, finalizer) { rule.Finalizers = append(rule.Finalizers, finalizer) } } -// removeFinalizer removes a finalizer from the rule +// removeFinalizer removes a finalizer from the rule. func removeFinalizer(rule *readinessv1alpha1.NodeReadinessRule, finalizer string) { var newFinalizers []string for _, f := range rule.Finalizers { diff --git a/internal/controller/nodereadinessrule_controller_test.go b/internal/controller/nodereadinessrule_controller_test.go index bd226d4..6569072 100644 --- a/internal/controller/nodereadinessrule_controller_test.go +++ b/internal/controller/nodereadinessrule_controller_test.go @@ -84,7 +84,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "test-taint", Effect: corev1.TaintEffectNoSchedule, }, @@ -121,7 +121,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "test-taint", Effect: corev1.TaintEffectNoSchedule, }, @@ -181,7 +181,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: "TestCondition", RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "immediate-test-taint", Effect: corev1.TaintEffectNoSchedule, }, @@ -237,7 +237,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "dry-run-taint", Effect: corev1.TaintEffectNoSchedule, }, @@ -254,14 +254,14 @@ var _ = Describe("NodeReadinessRule Controller", func() { Expect(err).NotTo(HaveOccurred()) // Verify dry run results are populated - Eventually(func() *nodereadinessiov1alpha1.DryRunResults { + Eventually(func() nodereadinessiov1alpha1.DryRunResults { updatedRule := &nodereadinessiov1alpha1.NodeReadinessRule{} err := k8sClient.Get(ctx, types.NamespacedName{Name: "dry-run-rule"}, updatedRule) if err != nil { - return nil + return nodereadinessiov1alpha1.DryRunResults{} } return updatedRule.Status.DryRunResults - }).ShouldNot(BeNil()) + }).ShouldNot(BeZero()) // Cleanup Expect(k8sClient.Delete(ctx, rule)).To(Succeed()) @@ -306,7 +306,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "node-test-taint", Effect: corev1.TaintEffectNoSchedule, }, @@ -367,7 +367,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { }, } - taintSpec := nodereadinessiov1alpha1.TaintSpec{ + taintSpec := corev1.Taint{ Key: "test-key", Effect: corev1.TaintEffectNoSchedule, } @@ -376,7 +376,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Expect(hasTaint).To(BeTrue()) // Test non-existent taint - nonExistentTaint := nodereadinessiov1alpha1.TaintSpec{ + nonExistentTaint := corev1.Taint{ Key: "missing-key", Effect: corev1.TaintEffectNoSchedule, } @@ -469,7 +469,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { ObjectMeta: metav1.ObjectMeta{Name: "db-rule"}, Spec: nodereadinessiov1alpha1.NodeReadinessRuleSpec{ Conditions: []nodereadinessiov1alpha1.ConditionRequirement{{Type: "DBReady", RequiredStatus: corev1.ConditionTrue}}, - Taint: nodereadinessiov1alpha1.TaintSpec{Key: "db-unready", Effect: corev1.TaintEffectNoSchedule}, + Taint: corev1.Taint{Key: "db-unready", Effect: corev1.TaintEffectNoSchedule}, EnforcementMode: nodereadinessiov1alpha1.EnforcementModeContinuous, NodeSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "backend"}}, }, @@ -522,7 +522,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { ObjectMeta: metav1.ObjectMeta{Name: "new-node-rule"}, Spec: nodereadinessiov1alpha1.NodeReadinessRuleSpec{ Conditions: []nodereadinessiov1alpha1.ConditionRequirement{{Type: "TestReady", RequiredStatus: corev1.ConditionTrue}}, - Taint: nodereadinessiov1alpha1.TaintSpec{Key: "test-unready", Effect: corev1.TaintEffectNoSchedule}, + Taint: corev1.Taint{Key: "test-unready", Effect: corev1.TaintEffectNoSchedule}, EnforcementMode: nodereadinessiov1alpha1.EnforcementModeContinuous, NodeSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"node-group": "new-workers"}}, }, @@ -602,7 +602,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { ObjectMeta: metav1.ObjectMeta{Name: "cleanup-rule"}, Spec: nodereadinessiov1alpha1.NodeReadinessRuleSpec{ Conditions: []nodereadinessiov1alpha1.ConditionRequirement{{Type: "TestReady", RequiredStatus: corev1.ConditionTrue}}, - Taint: nodereadinessiov1alpha1.TaintSpec{Key: "cleanup-taint", Effect: corev1.TaintEffectNoSchedule}, + Taint: corev1.Taint{Key: "cleanup-taint", Effect: corev1.TaintEffectNoSchedule}, EnforcementMode: nodereadinessiov1alpha1.EnforcementModeContinuous, }, } @@ -679,7 +679,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { ObjectMeta: metav1.ObjectMeta{Name: "delete-node-rule"}, Spec: nodereadinessiov1alpha1.NodeReadinessRuleSpec{ Conditions: []nodereadinessiov1alpha1.ConditionRequirement{{Type: "Ready", RequiredStatus: corev1.ConditionTrue}}, - Taint: nodereadinessiov1alpha1.TaintSpec{Key: "unready", Effect: corev1.TaintEffectNoSchedule}, + Taint: corev1.Taint{Key: "unready", Effect: corev1.TaintEffectNoSchedule}, EnforcementMode: nodereadinessiov1alpha1.EnforcementModeContinuous, }, } @@ -777,7 +777,7 @@ var _ = Describe("NodeReadinessRule Controller", func() { Conditions: []nodereadinessiov1alpha1.ConditionRequirement{ {Type: "TestReady", RequiredStatus: corev1.ConditionTrue}, }, - Taint: nodereadinessiov1alpha1.TaintSpec{Key: selectorChangeTaintKey, Effect: corev1.TaintEffectNoSchedule}, + Taint: corev1.Taint{Key: selectorChangeTaintKey, Effect: corev1.TaintEffectNoSchedule}, EnforcementMode: nodereadinessiov1alpha1.EnforcementModeContinuous, NodeSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"env": "prod"}, diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 1b1be50..60eba69 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -177,7 +177,7 @@ func getFirstFoundEnvTestBinaryDir() string { return "" } -// hasRequiredBinaries checks if the directory contains the essential envtest binaries +// hasRequiredBinaries checks if the directory contains the essential envtest binaries. func hasRequiredBinaries(dir string) bool { requiredBinaries := []string{"kube-apiserver", "etcd", "kubectl"} for _, binary := range requiredBinaries { diff --git a/internal/info/version.go b/internal/info/version.go index b712ead..3be7b98 100644 --- a/internal/info/version.go +++ b/internal/info/version.go @@ -22,10 +22,10 @@ import "strings" var version = "unknown" // gitCommit will be the hash that the binary was built from -// and will be populated by the Makefile +// and will be populated by the Makefile. var gitCommit = "" -// GetVersionParts returns the different version components +// GetVersionParts returns the different version components. func GetVersionParts() []string { v := []string{version} @@ -36,7 +36,7 @@ func GetVersionParts() []string { return v } -// GetVersionString returns the string representation of the version +// GetVersionString returns the string representation of the version. func GetVersionString(more ...string) string { v := append(GetVersionParts(), more...) return strings.Join(v, ", ") diff --git a/internal/webhook/nodereadinessgaterule_webhook.go b/internal/webhook/nodereadinessgaterule_webhook.go index f6d3147..35281f3 100644 --- a/internal/webhook/nodereadinessgaterule_webhook.go +++ b/internal/webhook/nodereadinessgaterule_webhook.go @@ -31,12 +31,12 @@ import ( readinessv1alpha1 "sigs.k8s.io/node-readiness-controller/api/v1alpha1" ) -// NodeReadinessRuleWebhook validates NodeReadinessRule resources +// NodeReadinessRuleWebhook validates NodeReadinessRule resources. type NodeReadinessRuleWebhook struct { client.Client } -// NewNodeReadinessRuleWebhook creates a new webhook +// NewNodeReadinessRuleWebhook creates a new webhook. func NewNodeReadinessRuleWebhook(c client.Client) *NodeReadinessRuleWebhook { return &NodeReadinessRuleWebhook{ Client: c, @@ -45,7 +45,7 @@ func NewNodeReadinessRuleWebhook(c client.Client) *NodeReadinessRuleWebhook { // +kubebuilder:webhook:path=/validate-nodereadiness-io-v1alpha1-nodereadinessrule,mutating=false,failurePolicy=fail,sideEffects=None,groups=readiness.node.x-k8s.io,resources=nodereadinessrules,verbs=create;update,versions=v1alpha1,name=vnodereadinessrule.kb.io,admissionReviewVersions=v1 -// validateNodeReadinessRule performs validation logic +// validateNodeReadinessRule performs validation logic. func (w *NodeReadinessRuleWebhook) validateNodeReadinessRule(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule, isUpdate bool) field.ErrorList { var allErrs field.ErrorList @@ -58,7 +58,7 @@ func (w *NodeReadinessRuleWebhook) validateNodeReadinessRule(ctx context.Context return allErrs } -// validateSpec validates the spec fields +// validateSpec validates the spec fields. func (w *NodeReadinessRuleWebhook) validateSpec(spec readinessv1alpha1.NodeReadinessRuleSpec) field.ErrorList { var allErrs field.ErrorList specField := field.NewPath("spec") @@ -100,7 +100,7 @@ func (w *NodeReadinessRuleWebhook) validateSpec(spec readinessv1alpha1.NodeReadi return allErrs } -// validateTaintConflicts checks for conflicting rules with the same taint key +// validateTaintConflicts checks for conflicting rules with the same taint key. func (w *NodeReadinessRuleWebhook) validateTaintConflicts(ctx context.Context, rule *readinessv1alpha1.NodeReadinessRule, isUpdate bool) field.ErrorList { var allErrs field.ErrorList @@ -123,7 +123,6 @@ func (w *NodeReadinessRuleWebhook) validateTaintConflicts(ctx context.Context, r // Check for same taint key and effect if existingRule.Spec.Taint.Key == rule.Spec.Taint.Key && existingRule.Spec.Taint.Effect == rule.Spec.Taint.Effect { - // Check if node selectors overlap if w.nodSelectorsOverlap(rule.Spec.NodeSelector, existingRule.Spec.NodeSelector) { allErrs = append(allErrs, field.Invalid( @@ -139,7 +138,7 @@ func (w *NodeReadinessRuleWebhook) validateTaintConflicts(ctx context.Context, r return allErrs } -// nodeSelectorsOverlap checks if two node selectors overlap +// nodeSelectorsOverlap checks if two node selectors overlap. func (w *NodeReadinessRuleWebhook) nodSelectorsOverlap(selector1, selector2 *metav1.LabelSelector) bool { // If either selector is nil, it matches all nodes - so they overlap if selector1 == nil || selector2 == nil { @@ -160,7 +159,7 @@ func (w *NodeReadinessRuleWebhook) nodSelectorsOverlap(selector1, selector2 *met return sel1.String() == sel2.String() } -// SetupWithManager sets up the webhook with the manager +// SetupWithManager sets up the webhook with the manager. func (w *NodeReadinessRuleWebhook) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(&readinessv1alpha1.NodeReadinessRule{}). @@ -168,7 +167,7 @@ func (w *NodeReadinessRuleWebhook) SetupWithManager(mgr ctrl.Manager) error { Complete() } -// Implement the admission.CustomValidator interface +// Implement the admission.CustomValidator interface. var _ webhook.CustomValidator = &NodeReadinessRuleWebhook{} func (w *NodeReadinessRuleWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { diff --git a/internal/webhook/nodereadinessgaterule_webhook_test.go b/internal/webhook/nodereadinessgaterule_webhook_test.go index 0ffbc7a..7082d42 100644 --- a/internal/webhook/nodereadinessgaterule_webhook_test.go +++ b/internal/webhook/nodereadinessgaterule_webhook_test.go @@ -84,7 +84,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { // Missing type and requiredStatus }, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "test-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -110,7 +110,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "test-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -131,7 +131,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, {Type: "NetworkReady", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "test-key", Effect: corev1.TaintEffectNoSchedule, Value: "pending", @@ -159,7 +159,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "conflict-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -181,7 +181,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "NetworkReady", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "conflict-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -204,7 +204,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "same-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -226,7 +226,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "NetworkReady", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "same-key", Effect: corev1.TaintEffectNoExecute, // Different effect }, @@ -246,7 +246,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "update-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -335,7 +335,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "create-test-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -369,7 +369,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "update-test-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -416,7 +416,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "Ready", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "comprehensive-key", Effect: corev1.TaintEffectNoSchedule, }, @@ -438,7 +438,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "NetworkReady", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "different-key", // No conflict Effect: corev1.TaintEffectNoSchedule, }, @@ -456,7 +456,7 @@ var _ = Describe("NodeReadinessRule Validation Webhook", func() { Conditions: []readinessv1alpha1.ConditionRequirement{ {Type: "StorageReady", RequiredStatus: corev1.ConditionTrue}, }, - Taint: readinessv1alpha1.TaintSpec{ + Taint: corev1.Taint{ Key: "comprehensive-key", // Conflicts with existing Effect: corev1.TaintEffectNoSchedule, }, diff --git a/test/utils/utils.go b/test/utils/utils.go index 57d80b6..0ea7805 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -24,7 +24,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck + . "github.com/onsi/ginkgo/v2" //nolint:staticcheck ) const ( @@ -39,7 +39,7 @@ func warnError(err error) { _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) } -// Run executes the provided command within this context +// Run executes the provided command within this context. func Run(cmd *exec.Cmd) (string, error) { dir, _ := GetProjectDir() cmd.Dir = dir @@ -59,7 +59,7 @@ func Run(cmd *exec.Cmd) (string, error) { return string(output), nil } -// UninstallCertManager uninstalls the cert manager +// UninstallCertManager uninstalls the cert manager. func UninstallCertManager() { url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) cmd := exec.Command("kubectl", "delete", "-f", url) @@ -133,7 +133,7 @@ func IsCertManagerCRDsInstalled() bool { return false } -// LoadImageToKindClusterWithName loads a local docker image to the kind cluster +// LoadImageToKindClusterWithName loads a local docker image to the kind cluster. func LoadImageToKindClusterWithName(name string) error { cluster := defaultKindCluster if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { @@ -163,7 +163,7 @@ func GetNonEmptyLines(output string) []string { return res } -// GetProjectDir will return the directory where the project is +// GetProjectDir will return the directory where the project is. func GetProjectDir() (string, error) { wd, err := os.Getwd() if err != nil { @@ -177,7 +177,7 @@ func GetProjectDir() (string, error) { // of the target content. The target content may span multiple lines. func UncommentCode(filename, target, prefix string) error { // false positive - // nolint:gosec + //nolint:gosec content, err := os.ReadFile(filename) if err != nil { return fmt.Errorf("failed to read file %q: %w", filename, err) @@ -217,7 +217,7 @@ func UncommentCode(filename, target, prefix string) error { } // false positive - // nolint:gosec + //nolint:gosec if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { return fmt.Errorf("failed to write file %q: %w", filename, err) }