Subject: [bug#74389] [PATCH] services: add cloud-init service

From: Alexander Joss <[email-masked]>
Date: Sun, 17 Nov 2024 06:26:05 +0000

* gnu/services/cloud-init.scm: add cloud-init service
* gnu/system/examples: add cloud-init-image.tmpl

Change-Id: I28fe295c1dbbab7ea7df65f6b764c7d795e58d77
---
 gnu/services/cloud-init.scm               | 137 ++++++++++++++++++++++
 gnu/system/examples/cloud-init-image.tmpl |  63 ++++++++++
 2 files changed, 200 insertions(+)
 create mode 100644 gnu/services/cloud-init.scm
 create mode 100644 gnu/system/examples/cloud-init-image.tmpl

diff --git a/gnu/services/cloud-init.scm b/gnu/services/cloud-init.scm
new file mode 100644
index 0000000000..d6362f70a7
--- /dev/null
+++ b/gnu/services/cloud-init.scm
@@ -0,0 +1,137 @@
+(define-module (gnu services cloud-init)
+  #:use-module (gnu packages bash)
+  #:use-module (gnu packages python-web)
+  #:use-module (gnu services)
+  #:use-module (gnu services shepherd)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:export (cloud-init-configuration cloud-init-service
+                                     cloud-init-service-type))
+
+(define-record-type* <cloud-init-configuration> cloud-init-configuration
+                     make-cloud-init-configuration
+  cloud-init-configuration?
+
+  (cloud-init cloud-init-configuration-cloud-init ;file-like
+              (default python-cloud-init))
+  (init-modules cloud-init-configuration-init-modules ;list of symbols
+                (default '(seed_random growpart
+                                       resizefs
+                                       disk_setup
+                                       mounts
+                                       set_hostname
+                                       update_hostname
+                                       users_groups
+                                       ssh
+                                       set_passwords)))
+  (config-modules cloud-init-configuration-config-modules ;list of symbols
+                  (default '()))
+  (final-modules cloud-init-configuration-final-modules ;list of symbols
+                 (default '(ssh_authkey_fingerprints)))
+  (extra-configuration-files
+   cloud-init-configuration-extra-configuration-files ;list of file-likes
+   (default '())))
+
+(define %cloud-dir
+  "/etc/cloud")
+
+(define %cloud-cfg
+  (string-append %cloud-dir "/cloud.cfg"))
+
+(define %cloud-run
+  (mixed-text-file "run.sh"
+                   "#!"
+                   (file-append bash "/bin/bash")
+                   "\n\nset -euo pipefail\n\n"
+                   (file-append python-cloud-init "/bin/cloud-init")
+                   " init --local\n"
+                   (file-append python-cloud-init "/bin/cloud-init")
+                   " init\n"
+                   (file-append python-cloud-init "/bin/cloud-init")
+                   " modules --mode config\n"
+                   (file-append python-cloud-init "/bin/cloud-init")
+                   " modules --mode final\n"))
+
+(define %cloud-cfg-d
+  (string-append %cloud-dir "/cloud.cfg.d"))
+
+(define (cloud-init-initialization init-modules config-modules final-modules
+                                   extra)
+  "Return the gexp to initialize the cloud-init configuration files"
+  #~(begin
+      (use-modules (srfi srfi-1)
+                   (srfi srfi-2)
+                   (guix build utils))
+
+      (define reduce-modules
+        (lambda (mods)
+          (string-join (map (lambda (mod)
+                              (string-append "\n  - "
+                                             (symbol->string mod))) mods))))
+
+      (mkdir-p #$%cloud-cfg-d)
+
+      (copy-file #$%cloud-run
+                 (string-append #$%cloud-dir "/run.sh"))
+      (chmod (string-append #$%cloud-dir "/run.sh") #o755)
+
+      (unless (null? '(#$@extra))
+        (for-each (lambda (file)
+                    (symlink (cadr file)
+                             (string-append #$%cloud-cfg-d "/"
+                                            (car file))))
+                  '(#$@extra)))
+
+      (call-with-output-file #$%cloud-cfg
+        (lambda (p)
+          (unless (null? '(#$@init-modules))
+            (display (string-append "cloud_init_modules:"
+                                    (reduce-modules '(#$@init-modules)) "\n\n")
+                     p))
+          (unless (null? '(#$@config-modules))
+            (display (string-append "cloud_config_modules:"
+                                    (reduce-modules '(#$@config-modules))
+                                    "\n\n") p))
+          (unless (null? '(#$@final-modules))
+            (display (string-append "cloud_final_modules:"
+                                    (reduce-modules '(#$@final-modules))
+                                    "\n\n") p))))))
+
+(define (cloud-init-activation config)
+  "Return the activation gexp for CONFIG."
+  #~(begin
+      (use-modules (guix build utils))
+      #$(cloud-init-initialization (cloud-init-configuration-init-modules
+                                    config)
+                                   (cloud-init-configuration-config-modules
+                                    config)
+                                   (cloud-init-configuration-final-modules
+                                    config)
+                                   (cloud-init-configuration-extra-configuration-files
+                                    config))))
+
+(define (cloud-init-service config)
+  "Return a <cloud-init-service> for cloud-init with CONFIG."
+  (define cloud-init
+    (cloud-init-configuration-cloud-init config))
+
+  (list (shepherd-service (documentation "cloud-init service")
+                          (provision '(cloud-init))
+                          (requirement '(networking))
+                          (one-shot? #t)
+                          (start #~(fork+exec-command (list (string-append #$%cloud-dir
+                                                             "/run.sh"))
+                                                      #:log-file (string-append
+                                                                  "/var/log/cloud-init.log")
+                                                      #:environment-variables '
+                                                      ("PATH=/run/current-system/profile/bin:/run/current-system/profile/sbin:"))))))
+
+(define cloud-init-service-type
+  (service-type (name 'cloud-init)
+                (default-value (cloud-init-configuration))
+                (description "cloud init")
+                (extensions (list (service-extension
+                                   shepherd-root-service-type
+                                   cloud-init-service)
+                                  (service-extension activation-service-type
+                                                     cloud-init-activation)))))
diff --git a/gnu/system/examples/cloud-init-image.tmpl b/gnu/system/examples/cloud-init-image.tmpl
new file mode 100644
index 0000000000..e2e69e8691
--- /dev/null
+++ b/gnu/system/examples/cloud-init-image.tmpl
@@ -0,0 +1,63 @@
+;; This vm image is meant to be used as an image template
+;; to be deployed on cloud providers that use cloud-init.
+
+(use-modules (gnu)
+             (guix)
+             (guix gexp)
+             (srfi srfi-1))
+(use-service-modules cloud-init base networking ssh)
+(use-package-modules admin bootloaders package-management python-web ssh)
+
+(operating-system
+  (host-name "gnu")
+  (timezone "Etc/UTC")
+  (locale "en_US.utf8")
+  (keyboard-layout (keyboard-layout "us"))
+
+  (firmware '())
+
+  ;; Below we assume /dev/vda is the VM's hard disk.
+  ;; Adjust as needed.
+  (bootloader (bootloader-configuration
+                (bootloader grub-bootloader)
+                (targets '("/dev/vda"))
+                (terminal-outputs '(console))))
+  (file-systems (cons (file-system
+                        (mount-point "/")
+                        (device "/dev/vda1")
+                        (type "ext4")) %base-file-systems))
+
+  ;; The cloud-utils packages provides some utilities to allow
+  ;; us to piggyback off ubuntu's cloud-init modules/integrations
+  ;; without having to write guix specific functionality.
+  ;;
+  ;; The python-cloud-init package is not strictly required to be
+  ;; in system-wide packages.
+  (packages (append (list cloud-utils python-cloud-init) %base-packages))
+
+  (services
+   (append (list (service cloud-init-service-type)
+                 ;; An example of extra configuration files. This specific
+                 ;; file is required for properly running cloud-init on DigitalOcean
+                 ;; (cloud-init-configuration (extra-configuration-files `
+                 ;; (("99-digitalocean.cfg" ,
+                 ;; (plain-file
+                 ;; "99-digitalocean.cfg"
+                 ;; "datasource_list: [ ConfigDrive, DigitalOcean, NoCloud, None ]"))))))
+                 
+                 (service network-manager-service-type)
+                 (service wpa-supplicant-service-type)
+                 (service openssh-service-type
+                          (openssh-configuration (openssh openssh-sans-x)
+                                                 (permit-root-login #t))))
+           %base-services
+           ;; Uncomment the following and replace the above to automatically add your guix
+           ;; signing key to the vm for easy reconfiguration.
+           ;; (modify-services %base-services
+           ;; (guix-service-type config =>
+           ;; (guix-configuration (inherit config)
+           ;; (authorized-keys (append
+           ;; (list (local-file
+           ;; "/etc/guix/signing-key.pub"))
+           ;; %default-authorized-guix-keys)))))))
+           )))

base-commit: 0e1ffbc7f5f060f89c890472377a6102f27f6e9b
-- 
2.46.0