Repository: rancher/cluster-api-provider-rke2 (go)

Issue:
NTP API broken for Leap/SLEMicro
**What happened:**

We provide an [API for NTP configuration](https://github.com/rancher/cluster-api-provider-rke2/blob/main/bootstrap/api/v1beta1/rke2config_types.go#L88) which influences the firstboot configuration to configure/enable  a NTP client.

In the `format: ignition` case we can see the [relevant templating](https://github.com/rancher/cluster-api-provider-rke2/blob/main/bootstrap/internal/ignition/butane/butane.go#L65) e.g:

```
./butane/butane.go:    {{- if .NTPServers }}
./butane/butane.go:    - name: ntpd.service
./butane/butane.go:    {{- if .NTPServers }}
```

However our primary target for ignition support is Leap/SLEMicro, which doesn't include ntpd, it uses chrony instead, so we see an ignition error like:

`Failed to enable unit, unit ntpd.service does not exist.`

**What did you expect to happen:**
We should configure/enable chrony instead as described in [the SLEMicro docs](https://documentation.suse.com/sle-micro/6.0/html/Micro-ntp-time-synchronization/index.html#ntp-configure-configuration-file)

**How to reproduce it:**
Deploy with NTP servers defined/enabled - observe error and that the OS client is not configured

**Environment:**
- rke provider version: 0.6.1
- OS : Leap/SLEMicro 5.5/6.0


Pull request description:
bootstrap: switch to chrony for ignition config
Leap/SLEMicro don't include ntpd so the current approach doesn't work

kind/bug

**What this PR does / why we need it**:
Currently the `ntp.servers` API provided e.g by `RKE2ControlPlane` does not work for Leap/SLEMicro, because chrony is used not ntpd as currently configured.

Fixes #430

**Checklist**:
<!-- Put an "X" character inside the brackets of each completed task. Some may be optional depending on the PR in which case these can be deleted -->

- [ ] squashed commits into logical changes
- [ ] includes documentation
- [ ] adds unit tests
- [ ] adds or updates e2e tests


Patch:
diff --git a/bootstrap/internal/ignition/butane/butane.go b/bootstrap/internal/ignition/butane/butane.go
index c8f4e85..99f6685 100644
--- a/bootstrap/internal/ignition/butane/butane.go
+++ b/bootstrap/internal/ignition/butane/butane.go
@@ -34,12 +34,12 @@ import (
 )
 
 // The template contains configurations for two main sections: systemd units and storage files.
-// The first section defines two systemd units: rke2-install.service and ntpd.service.
+// The first section defines two systemd units: rke2-install.service and chronyd.service.
 // The rke2-install.service unit is enabled and is executed only once during the boot process to run the /etc/rke2-install.sh script.
 // This script installs and deploys RKE2, and performs pre and post-installation commands.
-// The ntpd.service unit is enabled only if NTP servers are specified.
+// The chronyd.service unit is enabled only if NTP servers are specified.
 // The second section defines storage files for the system. It creates a file at /etc/rke2-install.sh.
-// If NTP servers are specified, it creates an NTP configuration file at /etc/ntp.conf.
+// If NTP servers are specified, it creates an NTP configuration file at /etc/chrony.conf.
 const (
 	butaneTemplate = `
 variant: fcos
@@ -62,7 +62,7 @@ systemd:
         [Install]
         WantedBy=multi-user.target
     {{- if .NTPServers }}
-    - name: ntpd.service
+    - name: chronyd.service
       enabled: true
     {{- end }}
 storage:
@@ -129,28 +129,22 @@ storage:
           {{ . | Indent 10 }}
           {{- end }}
     {{- if .NTPServers }}
-    - path: /etc/ntp.conf
+    - path: /etc/chrony.conf
       mode: 0644
+      overwrite: true
       contents:
         inline: |
-          # Common pool
+          # Configured by RKE2 CAPI bootstrap provider
           {{- range  .NTPServers }}
           server {{ . }}
           {{- end }}
-
-          # Warning: Using default NTP settings will leave your NTP
-          # server accessible to all hosts on the Internet.
-
-          # If you want to deny all machines (including your own)
-          # from accessing the NTP server, uncomment:
-          #restrict default ignore
-
-          # Default configuration:
-          # - Allow only time queries, at a limited rate, sending KoD when in excess.
-          # - Allow all local queries (IPv4, IPv6)
-          restrict default nomodify nopeer noquery notrap limited kod
-          restrict 127.0.0.1
-          restrict [::1]
+          driftfile /var/lib/chrony/drift
+          makestep 1.0 3
+          rtcsync
+          ntsdumpdir /var/lib/chrony
+          logdir /var/log/chrony
+          include /etc/chrony.d/*.conf
+          sourcedir /run/chrony-dhcp
     {{- end }}
 `
 )


Test patch:
diff --git a/bootstrap/internal/ignition/butane/butane_test.go b/bootstrap/internal/ignition/butane/butane_test.go
index b64c106..1ac1878 100644
--- a/bootstrap/internal/ignition/butane/butane_test.go
+++ b/bootstrap/internal/ignition/butane/butane_test.go
@@ -115,14 +115,14 @@ var _ = Describe("Render", func() {
 
 		Expect(ign.Storage.Files[3].Path).To(Equal("/etc/rke2-install.sh"))
 
-		Expect(ign.Storage.Files[4].Path).To(Equal("/etc/ntp.conf"))
+		Expect(ign.Storage.Files[4].Path).To(Equal("/etc/chrony.conf"))
 
 		Expect(ign.Systemd.Units).To(HaveLen(3))
 		Expect(ign.Systemd.Units[0].Name).To(Equal("rke2-install.service"))
 		Expect(ign.Systemd.Units[0].Contents).To(Equal(pointer.String("[Unit]\nDescription=rke2-install\nWants=network-online.target\nAfter=network-online.target network.target\nConditionPathExists=!/etc/cluster-api/bootstrap-success.complete\n[Service]\nUser=root\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/rke2-install.sh\n[Install]\nWantedBy=multi-user.target\n")))
 		Expect(ign.Systemd.Units[0].Enabled).To(Equal(pointer.Bool(true)))
 
-		Expect(ign.Systemd.Units[1].Name).To(Equal("ntpd.service"))
+		Expect(ign.Systemd.Units[1].Name).To(Equal("chronyd.service"))
 		Expect(ign.Systemd.Units[1].Enabled).To(Equal(pointer.Bool(true)))
 
 		Expect(ign.Systemd.Units[2].Name).To(Equal("test.service"))