package nodefirewall import ( "context" "errors" "fmt" "reflect" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "edge-infra.dev/pkg/k8s/meta/status" "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/k8s/runtime/controller/reconcile" "edge-infra.dev/pkg/k8s/runtime/patch" v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config" ) type Plugin struct { config config.Config name string owner string conditions reconcile.Conditions defaultInterface string } var nodeFirewallConditions = reconcile.Conditions{ Target: status.ReadyCondition, Owned: []string{ string(v1ien.NodeFirewallController), }, Summarize: []string{ string(v1ien.NodeFirewallController), }, NegativePolarity: []string{}, } func (fw Plugin) Reconcile(ctx context.Context, object client.Object, cfg config.Config) (recErr error) { log := ctrl.LoggerFrom(ctx) ctx = ctrl.LoggerInto(ctx, log) if reflect.DeepEqual(fw.conditions, reconcile.Conditions{}) { fw.conditions = nodeFirewallConditions } nodefirewall, ok := object.(*v1ien.NodeFirewall) if !ok { return nil } fw.config = cfg fw.name = nodefirewall.Name if len(nodefirewall.OwnerReferences) != 1 { return fmt.Errorf("invalid NodeFirewall - invalid OwnerReferences: %+v", nodefirewall.OwnerReferences) } fw.owner = nodefirewall.OwnerReferences[0].Name result := reconcile.ResultEmpty patcher := patch.NewSerialPatcher(nodefirewall, fw.config.GetClient()) defer func() { recErr = fw.summarizer(ctx, patcher, nodefirewall, result, recErr) }() conditions.MarkFalse(nodefirewall, status.ReadyCondition, string(v1ien.NfwProgressing), "%s", v1ien.Reconciling.String()) // Add finalizer if it doesn't exist if !controllerutil.ContainsFinalizer(nodefirewall, v1ien.NodeFirewallFinalizer) { controllerutil.AddFinalizer(nodefirewall, v1ien.NodeFirewallFinalizer) } // perform update or remove logic based on whether the object was deleted if !nodefirewall.ObjectMeta.DeletionTimestamp.IsZero() { recErr = fw.removeFiles(ctx, nodefirewall) return } if recErr = fw.updateFiles(ctx, nodefirewall); recErr != nil { return } conditions.MarkTrue(nodefirewall, status.ReadyCondition, string(v1ien.NfwSuccessful), "%s", v1ien.Succeeded.String()) result = reconcile.ResultSuccess return } func (fw Plugin) updateFiles(ctx context.Context, nodefirewall *v1ien.NodeFirewall) error { log := ctrl.LoggerFrom(ctx) // validate new NodeFirewall if valid, reason := fw.validateNodeFirewall(nodefirewall); !valid { err := errors.New("validation failed") log.Error(err, fmt.Sprintf(v1ien.Invalid, reason)) conditions.MarkTrue(nodefirewall, status.ReadyCondition, string(v1ien.CfwFailed), v1ien.Invalid, reason) return err } if err := fw.setDefaultInterface(ctx); err != nil { log.Error(err, v1ien.InterfaceRequeueing) conditions.MarkFalse(nodefirewall, status.ReadyCondition, string(v1ien.CfwFailed), "%s", v1ien.InterfaceRequeueing) return err } // write new rule files files, err := fw.writeFiles(nodefirewall.Spec.Rules) if err != nil { // attempt to remove anything written in case of failure _ = fw.deleteFiles(files) log.Error(err, v1ien.WriteFilesRequeueing) conditions.MarkFalse(nodefirewall, status.ReadyCondition, string(v1ien.CfwFailed), "%s", v1ien.WriteFilesRequeueing) return err } // remove stale files if err := fw.deleteStaleFiles(fw.getFileInventory(nodefirewall), files); err != nil { log.Error(err, v1ien.RemoveFilesRequeueing) conditions.MarkFalse(nodefirewall, status.ReadyCondition, string(v1ien.CfwFailed), "%s", v1ien.RemoveFilesRequeueing) return err } // udpate inventory fw.setFileInventory(nodefirewall, files) return nil } func (fw Plugin) validateNodeFirewall(nodefirewall *v1ien.NodeFirewall) (bool, string) { if valid, reason := nodefirewall.ValidateRules(); !valid { return valid, reason } for _, rule := range nodefirewall.Spec.Rules { if rule.InterfaceMAC == "" { continue } if _, err := fw.config.GetInterfaceFromHardwareAddress(rule.InterfaceMAC); err != nil { return false, fmt.Sprintf("couldn't get interface from provided hardware address %s: %v", rule.InterfaceMAC, err) } } return true, "" } func (fw Plugin) removeFiles(ctx context.Context, nodefirewall *v1ien.NodeFirewall) error { // delete rules files if err := fw.deleteFiles(fw.getFileInventory(nodefirewall)); err != nil { ctrl.LoggerFrom(ctx).Error(err, v1ien.RemoveFilesRequeueing) conditions.MarkFalse(nodefirewall, status.ReadyCondition, string(v1ien.NfwFailed), "%s", v1ien.RemoveFilesRequeueing) return err } conditions.MarkTrue(nodefirewall, status.ReadyCondition, string(v1ien.NfwSuccessful), "%s", v1ien.Succeeded.String()) controllerutil.RemoveFinalizer(nodefirewall, v1ien.NodeFirewallFinalizer) return nil } func (fw Plugin) getFileInventory(nodefirewall *v1ien.NodeFirewall) []string { if nodefirewall.Status != nil && nodefirewall.Status.Inventory != nil { return nodefirewall.Status.Inventory } return []string{} } func (fw Plugin) setFileInventory(nodefirewall *v1ien.NodeFirewall, files []string) { if nodefirewall.Status == nil { nodefirewall.Status = &v1ien.NodeFirewallStatus{} } nodefirewall.Status.Inventory = files } func (fw *Plugin) setDefaultInterface(ctx context.Context) error { ienode, err := fw.config.GetHostIENode(ctx) if err != nil { return err } iface := ienode.Status.Network.DefaultInterfaceName if iface == "" { return errors.New("default interface not set") } fw.defaultInterface = iface return nil } func (fw *Plugin) summarizer(ctx context.Context, patcher *patch.SerialPatcher, nodeFirewall *v1ien.NodeFirewall, result reconcile.Result, recErr error) error { s := reconcile.NewSummarizer(patcher) _, err := s.SummarizeAndPatch(ctx, nodeFirewall, reconcile.WithConditions(fw.conditions), reconcile.WithResult(result), reconcile.WithError(recErr), reconcile.WithIgnoreNotFound(), reconcile.WithProcessors( reconcile.RecordReconcileReq, reconcile.RecordResult, ), reconcile.WithFieldOwner(fw.name), ) return err }