;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2022, 2023, 2024 Oleg Pykhalov <go.wigust@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (services kubernetes)
  #:use-module (gnu packages docker)
  #:use-module (gnu packages linux)
  #:use-module (gnu services)
  #:use-module (gnu services admin)
  #:use-module (gnu services shepherd)
  #:use-module (guix gexp)
  #:use-module (guix records)
  #:use-module (packages containers)
  #:use-module (packages kubernetes)
  #:use-module (srfi srfi-1)
  #:export (kubernetes-k3s-configuration
            kubernetes-k3s-configuration?
            kubernetes-k3s-service-type

            kubernetes-k3s-service

            containerd-service-type

            kubelet-configuration
            kubelet-configuration?
            kubelet-service-type

            edgecore-configuration
            edgecore-configuration?
            edgecore-service-type))

;;; Commentary:
;;;
;;; This module provides a service definition for the kubernetes service.
;;;
;;; Code:

(define-record-type* <kubernetes-k3s-configuration>
  kubernetes-k3s-configuration make-kubernetes-k3s-configuration
  kubernetes-k3s-configuration?
  (k3s       kubernetes-k3s-configuration-k3s       ;<package>
             (default k3s))
  (arguments kubernetes-k3s-configuration-arguments ;list of strings
             (default '()))
  (kubevirt? kubernetes-k3s-configuration-kubevirt? ;boolean
             (default #f))
  (cilium?   kubernetes-k3s-configuration-cilium?   ;boolean
             (default #f))
  (flux?     kubernetes-k3s-configuration-flux?     ;boolean
             (default #f))
  (log-file  kubernetes-k3s-configuration-log-file  ;string
             (default "/var/log/k3s.log"))
  (runtime   kubernetes-k3s-configuration-runtime   ;symbol
             (default 'docker))
  (server?   kubernetes-k3s-configuration-server?   ;boolean
             (default #f)))

(define (kubernetes-k3s-log-rotations config)
  (list (log-rotation
         (files (list (kubernetes-k3s-configuration-log-file config))))))

(define (cilium-requirements)
  (with-imported-modules '((guix build utils))
    #~(begin
        (use-modules (guix build utils))
        (unless (zero? (system* #$(file-append util-linux+udev "/bin/mountpoint")
                                "--quiet" "/sys/fs/bpf"))
          (invoke #$(file-append util-linux+udev "/bin/mount")
                  "-o" "rw,nosuid,nodev,noexec,relatime,mode=700"
                  "-t" "bpf"
                  "none"
                  "/sys/fs/bpf")
          (invoke #$(file-append util-linux+udev "/bin/mount")
                  "--make-shared" "/sys/fs/bpf"))
        (invoke #$(file-append util-linux+udev "/bin/mount")
                "--make-shared" "/sys/fs/cgroup")
        (invoke #$(file-append util-linux+udev "/bin/mount")
                "--make-shared" "/"))))

(define (k3s-wrapper args)
  (program-file
   "k3s-wrapper"
   (with-imported-modules '((guix build utils))
     #~(begin
         #$(cilium-requirements)
         #$args))))

(define (kubernetes-k3s-shepherd-service config)
  (list
   (shepherd-service
    (provision '(kubernetes-k3s))
    (documentation "Run kubernetes-k3s.")
    (requirement (append '(networking containerd)
                         (case (kubernetes-k3s-configuration-runtime config)
                           ((docker) '(dockerd))
                           (else '()))))
    (start
     #~(make-forkexec-constructor
        (list
         #$(k3s-wrapper
            #~(execl #$(file-append (kubernetes-k3s-configuration-k3s config)
                                    "/bin/k3s")
                     "k3s"
                     #$(if (kubernetes-k3s-configuration-server? config)
                           "server"
                           "agent")
                     "--log" #$(kubernetes-k3s-configuration-log-file config)
                     #$@(kubernetes-k3s-configuration-arguments config))))))
    (respawn? #t) ;XXX: Fix race condition with Docker
    (stop #~(make-kill-destructor)))))

(define kubernetes-k3s-service-type
  (service-type
   (name 'kubernetes-k3s)
   (extensions
    (list (service-extension profile-service-type
                             (lambda (config)
                               (append (list k3s
                                             kubectl
                                             kubernetes-helm
                                             nerdctl
                                             k9s)
                                       (if (kubernetes-k3s-configuration-cilium? config)
                                           (list cilium)
                                           '())
                                       (if (kubernetes-k3s-configuration-flux? config)
                                           (list flux)
                                           '())
                                       (if (kubernetes-k3s-configuration-kubevirt? config)
                                           (list virtctl)
                                           '()))))
          (service-extension shepherd-root-service-type
                             kubernetes-k3s-shepherd-service)
          (service-extension rottlog-service-type
                             kubernetes-k3s-log-rotations)))
   (default-value '())
   (description "Run the kubernetes-k3s.")))


;;;
;;; containerd
;;;

;; Same as containerd from gnu/services/docker.scm in Guix official repository
;; but does not require Docker.

(define (containerd-shepherd-service config)
  (let ((containerd containerd))
    (list
     (shepherd-service
      (documentation "containerd daemon.")
      (provision '(containerd))
      (start #~(make-forkexec-constructor
                (list (string-append #$containerd "/bin/containerd"))
                ;; For finding containerd-shim binary.
                #:environment-variables
                (list (string-append "PATH=" #$containerd "/bin"))
                #:pid-file "/run/containerd/containerd.pid"
                #:pid-file-timeout 300
                #:log-file "/var/log/containerd.log"))
      (stop #~(make-kill-destructor))))))

(define containerd-service-type
  (service-type
   (name 'containerd)
   (extensions
    (list (service-extension shepherd-root-service-type
                             containerd-shepherd-service)))
   (default-value '())
   (description "Run containerd service.")))


;;;
;;; kubelet
;;;

(define-record-type* <kubelet-configuration>
  kubelet-configuration make-kubelet-configuration
  kubelet-configuration?
  (kubelet kubelet-configuration-kubelet ;string
           (default #f))
  (log-file kubelet-configuration-log-file ;string
            (default "/var/log/kubelet.log"))
  (arguments kubelet-configuration-arguments ;list of strings
             (default '())))

(define (kubelet-log-rotations config)
  (list (log-rotation
         (files (list (kubelet-configuration-log-file config))))))

(define (kubelet-wrapper args)
  (program-file
   "kubelet-wrapper"
   #~(begin
       #$(cilium-requirements)
       #$args)))

(define (kubelet-shepherd-service config)
  (list
   (shepherd-service
    (documentation "kubelet daemon.")
    (provision '(kubelet))
    (requirement '(networking containerd))
    (start #~(make-forkexec-constructor
              (list #$(kubelet-wrapper
                       #~(execl #$(kubelet-configuration-kubelet config)
                                "kubelet"
                                #$@(kubelet-configuration-arguments config))))
              #:log-file #$(kubelet-configuration-log-file config)))
    (stop #~(make-kill-destructor)))))

(define kubelet-service-type
  (service-type
   (name 'kubelet)
   (extensions
    (list (service-extension shepherd-root-service-type
                             kubelet-shepherd-service)
          (service-extension rottlog-service-type
                             kubelet-log-rotations)))
   (default-value '())
   (description "Run the kubelet.")))


;;;
;;;
;;;

(define-record-type* <edgecore-configuration>
  edgecore-configuration make-edgecore-configuration
  edgecore-configuration?
  (edgecore edgecore-configuration-edgecore ;string
            (default (file-append edgecore "/bin/edgecore")))
  (log-file edgecore-configuration-log-file ;string
            (default "/var/log/edgecore.log"))
  (arguments edgecore-configuration-arguments ;list of strings
             (default '())))

(define (edgecore-log-rotations config)
  (list (log-rotation
         (files (list (edgecore-configuration-log-file config))))))

(define (edgecore-wrapper args)
  (program-file
   "edgecore-wrapper"
   #~(begin
       #$(cilium-requirements)
       #$args)))

(define (edgecore-shepherd-service config)
  (list
   (shepherd-service
    (documentation "edgecore daemon.")
    (provision '(edgecore))
    (requirement '(networking containerd))
    (start #~(make-forkexec-constructor
              (list #$(edgecore-wrapper
                       #~(execl #$(edgecore-configuration-edgecore config)
                                "edgecore"
                                #$@(edgecore-configuration-arguments config))))
              #:log-file #$(edgecore-configuration-log-file config)))
    (stop #~(make-kill-destructor)))))

(define edgecore-service-type
  (service-type
   (name 'edgecore)
   (extensions
    (list (service-extension shepherd-root-service-type
                             edgecore-shepherd-service)
          (service-extension rottlog-service-type
                             edgecore-log-rotations)))
   (default-value '())
   (description "Run the edgecore.")))

;;; kubernetes.scm ends here
