From 003e6b2050675f5ff629a03c99d6f9d013b575d9 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Tue, 23 Jul 2024 12:45:07 +0300 Subject: [PATCH] Initial implementation and integration of the OS upgrade script (#16) * Add OS data to release manifest * Add relevant roles for secret creation * Add initial logic around OS upgrade script secret creation * Remove leftover debug code --- config/rbac/role.yaml | 9 +++ internal/controller/reconcile_os.go | 26 ++++++++ internal/controller/upgradeplan_controller.go | 1 + internal/upgrade/os.go | 59 +++++++++++++++++ internal/upgrade/templates/os-upgrade.sh.tpl | 64 +++++++++++++++++++ manifests/release-3.0.1.yaml | 8 +++ pkg/release/release.go | 9 +++ 7 files changed, 176 insertions(+) create mode 100644 internal/controller/reconcile_os.go create mode 100644 internal/upgrade/os.go create mode 100644 internal/upgrade/templates/os-upgrade.sh.tpl diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 69423e5..c5212c6 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -11,6 +11,15 @@ rules: verbs: - list - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list - apiGroups: - "" resources: diff --git a/internal/controller/reconcile_os.go b/internal/controller/reconcile_os.go new file mode 100644 index 0000000..5155397 --- /dev/null +++ b/internal/controller/reconcile_os.go @@ -0,0 +1,26 @@ +package controller + +import ( + "context" + "fmt" + + "github.com/suse-edge/upgrade-controller/internal/upgrade" + "github.com/suse-edge/upgrade-controller/pkg/release" + ctrl "sigs.k8s.io/controller-runtime" +) + +//lint:ignore U1000 - Temporary ignore "unused" linter error. Will be removed when function is ready to be used. +func (r *UpgradePlanReconciler) reconcileOS(ctx context.Context, releaseOS *release.OperatingSystem) (ctrl.Result, error) { + secret, err := upgrade.OSUpgradeSecret(releaseOS) + if err != nil { + return ctrl.Result{}, fmt.Errorf("generating OS upgrade secret: %w", err) + } + + if err = r.Create(ctx, secret); err != nil { + return ctrl.Result{}, fmt.Errorf("creating OS upgrade secret: %w", err) + } + + // TODO: OS upgrade logic + + return ctrl.Result{Requeue: true}, nil +} diff --git a/internal/controller/upgradeplan_controller.go b/internal/controller/upgradeplan_controller.go index 87200f1..e44f865 100644 --- a/internal/controller/upgradeplan_controller.go +++ b/internal/controller/upgradeplan_controller.go @@ -51,6 +51,7 @@ type UpgradePlanReconciler struct { // +kubebuilder:rbac:groups=lifecycle.suse.com,resources=upgradeplans/finalizers,verbs=update // +kubebuilder:rbac:groups=upgrade.cattle.io,resources=plans,verbs=create;list;get;watch // +kubebuilder:rbac:groups="",resources=nodes,verbs=watch;list +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;delete;create // +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to diff --git a/internal/upgrade/os.go b/internal/upgrade/os.go new file mode 100644 index 0000000..4ae9b38 --- /dev/null +++ b/internal/upgrade/os.go @@ -0,0 +1,59 @@ +package upgrade + +import ( + "bytes" + _ "embed" + "fmt" + "text/template" + + "github.com/suse-edge/upgrade-controller/pkg/release" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +//go:embed templates/os-upgrade.sh.tpl +var osUpgradeScript string + +func OSUpgradeSecret(releaseOS *release.OperatingSystem) (*corev1.Secret, error) { + const ( + scriptName = "os-upgrade.sh" + secretName = "os-upgrade-secret" + ) + + tmpl, err := template.New(scriptName).Parse(osUpgradeScript) + if err != nil { + return nil, fmt.Errorf("parsing contents: %w", err) + } + + values := struct { + CPEScheme string + RepoGPGKey string + ZypperID string + Version string + SupportedArchs []string + }{ + CPEScheme: releaseOS.CPEScheme, + RepoGPGKey: releaseOS.RepoGPGPath, + ZypperID: releaseOS.ZypperID, + Version: releaseOS.Version, + SupportedArchs: releaseOS.SupportedArchs, + } + + var buff bytes.Buffer + if err = tmpl.Execute(&buff, values); err != nil { + return nil, fmt.Errorf("applying template: %w", err) + } + + secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: secretName, + Namespace: upgradeNamespace, + }, + Type: corev1.SecretTypeOpaque, + StringData: map[string]string{ + scriptName: buff.String(), + }, + } + + return secret, nil +} diff --git a/internal/upgrade/templates/os-upgrade.sh.tpl b/internal/upgrade/templates/os-upgrade.sh.tpl new file mode 100644 index 0000000..14351da --- /dev/null +++ b/internal/upgrade/templates/os-upgrade.sh.tpl @@ -0,0 +1,64 @@ +#!/bin/sh + +# Common Platform Enumeration (CPE) comming from the release manifest +RELEASE_CPE={{.CPEScheme}} +# Common Platform Enumeration (CPE) that the system is currently running with +CURRENT_CPE=`cat /etc/os-release | grep -w CPE_NAME | cut -d "=" -f 2 | tr -d '"'` + +# Determine whether architecture is supported +SYSTEM_ARCH=`arch` +IFS=' ' read -r -a SUPPORTED_ARCH_ARRAY <<< $(echo "{{.SupportedArchs}}" | tr -d '[]') + +found=false +for arch in "${SUPPORTED_ARCH_ARRAY[@]}"; do + if [ "${SYSTEM_ARCH}" == ${arch} ]; then + found=true + break + fi +done + +if [ ${found} == false ]; then + echo "Operating system is running an unsupported architecture. System arch: ${SYSTEM_ARCH}. Supported archs: ${SUPPORTED_ARCH_ARRAY[*]}" + exit 1 +fi + +# Determine whether this is a package update or a migration +if [ "${RELEASE_CPE}" == "${CURRENT_CPE}" ]; then + # Package update if both CPEs are the same + EXEC_START_PRE="" + EXEC_START="/usr/sbin/transactional-update cleanup up" + SERVICE_NAME="os-pkg-update.service" +else + # Migration if the CPEs are different + EXEC_START_PRE="/usr/sbin/transactional-update run rpm --import {{.RepoGPGKey}}" + EXEC_START="/usr/sbin/transactional-update --continue run zypper migration --non-interactive --product {{.ZypperID}}/{{.Version}}/${SYSTEM_ARCH} --root /" + SERVICE_NAME="os-migration.service" +fi + +UPDATE_SERVICE_PATH=/etc/systemd/system/${SERVICE_NAME} + +echo "Creating ${SERVICE_NAME}..." +cat < ${UPDATE_SERVICE_PATH} +[Unit] +Description=SUSE Edge Upgrade Service +ConditionACPower=true +Wants=network.target +After=network.target + +[Service] +Type=oneshot +ExecStartPre=${EXEC_START_PRE} +ExecStart=${EXEC_START} +ExecStartPost=-/bin/bash -c '[ -f /run/reboot-needed ] && shutdown -r +1' +IOSchedulingClass=best-effort +IOSchedulingPriority=7 +EOF + +echo "Starting ${SERVICE_NAME}..." +systemctl start ${SERVICE_NAME} & +tail --pid $! -f cat /var/log/transactional-update.log + +echo "Cleaning up..." +# Remove service after it has finished its work +rm ${UPDATE_SERVICE_PATH} +systemctl daemon-reload diff --git a/manifests/release-3.0.1.yaml b/manifests/release-3.0.1.yaml index 60d98ea..8e237dc 100644 --- a/manifests/release-3.0.1.yaml +++ b/manifests/release-3.0.1.yaml @@ -6,3 +6,11 @@ components: version: v1.28.9+k3s1 rke2: version: v1.28.9+rke2r1 + operatingSystem: + version: 6.0 + zypperID: SL-Micro + cpeScheme: cpe:/o:suse:sl-micro:6.0 + repoGPGPath: /usr/lib/rpm/gnupg/keys/gpg-pubkey-09d9ea69-645b99ce.asc + supportedArchs: + - x86_64 + # - aarch64 TODO: add when we start supporting it diff --git a/pkg/release/release.go b/pkg/release/release.go index 4bfb42b..0a2092c 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -12,5 +12,14 @@ type Release struct { Version string `yaml:"version"` } `yaml:"rke2"` } `yaml:"kubernetes"` + OperatingSystem OperatingSystem `yaml:"operatingSystem"` } `yaml:"components"` } + +type OperatingSystem struct { + Version string `yaml:"version"` + ZypperID string `yaml:"zypperID"` + CPEScheme string `yaml:"cpeScheme"` + RepoGPGPath string `yaml:"repoGPGPath"` + SupportedArchs []string `yaml:"supportedArchs"` +}