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  }