package server import ( "context" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "git.unkin.net/unkin/repospawner/internal/jobs" ) // Cluster is the slice of the Kubernetes API the server needs. Keeping it an // interface lets the reconciler and handlers be tested without a cluster. type Cluster interface { // CreateJob creates a Job, reporting AlreadyExists as nil so a repeated // reconcile is harmless. CreateJob(ctx context.Context, job *batchv1.Job) error // ListJobs returns every repospawner-owned Job in the namespace. ListJobs(ctx context.Context) ([]batchv1.Job, error) // ListPods returns every repospawner-owned Job pod in the namespace. ListPods(ctx context.Context) ([]corev1.Pod, error) // Ping reports whether the API server is reachable. Ping(ctx context.Context) error } // KubeCluster is the in-cluster Cluster implementation. type KubeCluster struct { client kubernetes.Interface namespace string } // NewKubeCluster builds a Cluster from the pod's in-cluster credentials. func NewKubeCluster(namespace string) (*KubeCluster, error) { cfg, err := rest.InClusterConfig() if err != nil { return nil, err } client, err := kubernetes.NewForConfig(cfg) if err != nil { return nil, err } return &KubeCluster{client: client, namespace: namespace}, nil } // ownedSelector matches everything repospawner creates. const ownedSelector = jobs.LabelApp + "=" + jobs.AppName + "," + jobs.LabelRequest func (k *KubeCluster) CreateJob(ctx context.Context, job *batchv1.Job) error { _, err := k.client.BatchV1().Jobs(k.namespace).Create(ctx, job, metav1.CreateOptions{}) if apierrors.IsAlreadyExists(err) { return nil } return err } func (k *KubeCluster) ListJobs(ctx context.Context) ([]batchv1.Job, error) { list, err := k.client.BatchV1().Jobs(k.namespace).List(ctx, metav1.ListOptions{LabelSelector: ownedSelector}) if err != nil { return nil, err } return list.Items, nil } func (k *KubeCluster) ListPods(ctx context.Context) ([]corev1.Pod, error) { list, err := k.client.CoreV1().Pods(k.namespace).List(ctx, metav1.ListOptions{LabelSelector: ownedSelector}) if err != nil { return nil, err } return list.Items, nil } func (k *KubeCluster) Ping(ctx context.Context) error { limit := int64(1) _, err := k.client.BatchV1().Jobs(k.namespace).List(ctx, metav1.ListOptions{Limit: limit}) return err }