github.com/kotalco/kotal@v0.3.0/controllers/graph/node_controller.go (about) 1 package graph 2 3 import ( 4 "context" 5 6 graphv1alpha1 "github.com/kotalco/kotal/apis/graph/v1alpha1" 7 graphClients "github.com/kotalco/kotal/clients/graph" 8 "github.com/kotalco/kotal/controllers/shared" 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 "k8s.io/apimachinery/pkg/api/resource" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/runtime" 14 ctrl "sigs.k8s.io/controller-runtime" 15 "sigs.k8s.io/controller-runtime/pkg/client" 16 ) 17 18 // NodeReconciler reconciles a Node object 19 type NodeReconciler struct { 20 client.Client 21 Scheme *runtime.Scheme 22 } 23 24 //+kubebuilder:rbac:groups=graph.kotal.io,resources=nodes,verbs=get;list;watch;create;update;patch;delete 25 //+kubebuilder:rbac:groups=graph.kotal.io,resources=nodes/status,verbs=get;update;patch 26 //+kubebuilder:rbac:groups=graph.kotal.io,resources=nodes/finalizers,verbs=update 27 //+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=watch;get;list;create;update;delete 28 29 func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { 30 defer shared.IgnoreConflicts(&err) 31 32 var node graphv1alpha1.Node 33 34 if err = r.Client.Get(ctx, req.NamespacedName, &node); err != nil { 35 err = client.IgnoreNotFound(err) 36 return 37 } 38 39 // TODO: default the node if webhooks are disabled 40 41 shared.UpdateLabels(&node, "graph-node", "") 42 43 if err = r.reconcileStatefulset(ctx, &node); err != nil { 44 return 45 } 46 47 return 48 } 49 50 // reconcileStatefulset reconciles node statefulset 51 func (r *NodeReconciler) reconcileStatefulset(ctx context.Context, node *graphv1alpha1.Node) error { 52 sts := &appsv1.StatefulSet{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: node.Name, 55 Namespace: node.Namespace, 56 }, 57 } 58 59 client := graphClients.NewClient(node) 60 61 homeDir := client.HomeDir() 62 cmd := client.Command() 63 args := client.Args() 64 env := client.Env() 65 66 _, err := ctrl.CreateOrUpdate(ctx, r.Client, sts, func() error { 67 if err := ctrl.SetControllerReference(node, sts, r.Scheme); err != nil { 68 return err 69 } 70 if err := r.specStatefulSet(node, sts, homeDir, env, cmd, args); err != nil { 71 return err 72 } 73 return nil 74 }) 75 76 return err 77 } 78 79 // specStatefulSet updates node statefulset spec 80 func (r *NodeReconciler) specStatefulSet(node *graphv1alpha1.Node, sts *appsv1.StatefulSet, homeDir string, env []corev1.EnvVar, cmd, args []string) error { 81 82 sts.ObjectMeta.Labels = node.Labels 83 84 sts.Spec = appsv1.StatefulSetSpec{ 85 Selector: &metav1.LabelSelector{ 86 MatchLabels: node.Labels, 87 }, 88 ServiceName: node.Name, 89 Template: corev1.PodTemplateSpec{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Labels: node.Labels, 92 }, 93 Spec: corev1.PodSpec{ 94 SecurityContext: shared.SecurityContext(), 95 Containers: []corev1.Container{ 96 { 97 Name: "node", 98 Image: node.Spec.Image, 99 Command: cmd, 100 Args: args, 101 Env: env, 102 Resources: corev1.ResourceRequirements{ 103 Requests: corev1.ResourceList{ 104 corev1.ResourceCPU: resource.MustParse("1"), 105 corev1.ResourceMemory: resource.MustParse("1Gi"), 106 }, 107 Limits: corev1.ResourceList{ 108 corev1.ResourceCPU: resource.MustParse("2"), 109 corev1.ResourceMemory: resource.MustParse("2Gi"), 110 }, 111 }, 112 VolumeMounts: []corev1.VolumeMount{ 113 { 114 Name: "data", 115 MountPath: shared.PathData(homeDir), 116 }, 117 }, 118 }, 119 }, 120 Volumes: []corev1.Volume{ 121 { 122 Name: "data", 123 VolumeSource: corev1.VolumeSource{ 124 EmptyDir: &corev1.EmptyDirVolumeSource{}, 125 }, 126 }, 127 }, 128 }, 129 }, 130 } 131 132 return nil 133 } 134 135 // SetupWithManager sets up the controller with the Manager. 136 func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { 137 return ctrl.NewControllerManagedBy(mgr). 138 For(&graphv1alpha1.Node{}). 139 Owns(&appsv1.StatefulSet{}). 140 Complete(r) 141 }