1- From 971fc5f34ec13656fe12ae4d698272f8c23a6387 Mon Sep 17 00:00:00 2001
2- From: Jan Larwig <jan.larwig@ionos .com>
1+ From 71503fcd472dcec66bf56576b8907e5fd81b9493 Mon Sep 17 00:00:00 2001
2+ From: Mohamed Chiheb Ben Jemaa <mc.benjemaa@gmail .com>
33Date: Thu, 17 Oct 2024 15:43:10 +0200
4- Subject: [PATCH] providers: add support for ionos cloud
4+ Subject: [PATCH 21/21 ] providers: add support for ionos cloud
55
66Add support for IONOS Cloud
7-
87Add check to ignore cloud-config
8+ Add mounting of root partition
9+ Add better documentation
10+
11+ Co-authored-by: Jan Larwig <
[email protected] >
912---
1013 docs/release-notes.md | 2 +
1114 docs/supported-platforms.md | 2 +
12- internal/providers/ionoscloud/ionoscloud.go | 149 ++++++++++++++++++++
15+ internal/providers/ionoscloud/ionoscloud.go | 200 ++++++++++++++++++++
1316 internal/providers/proxmoxve/proxmoxve.go | 4 +-
14- internal/providers/util/cloudconfig.go | 13 ++
17+ internal/providers/util/cloudconfig.go | 27 + ++
1518 internal/register/providers.go | 1 +
16- 6 files changed, 168 insertions(+), 3 deletions(-)
19+ 6 files changed, 233 insertions(+), 3 deletions(-)
1720 create mode 100644 internal/providers/ionoscloud/ionoscloud.go
1821 create mode 100644 internal/providers/util/cloudconfig.go
1922
@@ -31,18 +34,18 @@ index 342fb1aa..2f25b609 100644
3134
3235 ### Bug fixes
3336diff --git a/docs/supported-platforms.md b/docs/supported-platforms.md
34- index eef319b2..c6846087 100644
37+ index 232482e6..696c9604 100644
3538--- a/docs/supported-platforms.md
3639+++ b/docs/supported-platforms.md
37- @@ -20 ,6 +20 ,7 @@ Ignition is currently supported for the following platforms:
40+ @@ -22 ,6 +22 ,7 @@ Ignition is currently supported for the following platforms:
3841 * [Hetzner Cloud] (`hetzner`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
3942 * [Microsoft Hyper-V] (`hyperv`) - Ignition will read its configuration from the `ignition.config` key in pool 0 of the Hyper-V Data Exchange Service (KVP). Values are limited to approximately 1 KiB of text, so Ignition can also read and concatenate multiple keys named `ignition.config.0`, `ignition.config.1`, and so on.
4043 * [IBM Cloud] (`ibmcloud`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
41- + * [IONOS Cloud] (`ionoscloud`) - Ignition will read its configuration from the instance user-data. Per default the user-data is retrieved from a partition or disk with the label `OEM-CONFIG ` which can be customized using the environment variable `IGNITION_CONFIG_DISK_LABEL`.
44+ + * [IONOS Cloud] (`ionoscloud`) - Ignition will read its configuration from the instance user-data. Per default the user-data are injected on a disk or partition with the label `OEM` which can be customized using the environment variable `IGNITION_CONFIG_DEVICE_LABEL`.
4245 * [KubeVirt] (`kubevirt`) - Ignition will read its configuration from the instance userdata via config drive. Cloud SSH keys are handled separately.
4346 * Bare Metal (`metal`) - Use the `ignition.config.url` kernel parameter to provide a URL to the configuration. The URL can use the `http://`, `https://`, `tftp://`, `s3://`, `arn:`, or `gs://` schemes to specify a remote config.
4447 * [Nutanix] (`nutanix`) - Ignition will read its configuration from the instance userdata via config drive. Cloud SSH keys are handled separately.
45- @@ -52 ,6 +53 ,7 @@ For most cloud providers, cloud SSH keys and custom network configuration are ha
48+ @@ -57 ,6 +58 ,7 @@ For most cloud providers, cloud SSH keys and custom network configuration are ha
4649 [Hetzner Cloud]: https://www.hetzner.com/cloud
4750 [Microsoft Hyper-V]: https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/
4851 [IBM Cloud]: https://www.ibm.com/cloud/vpc
@@ -52,10 +55,10 @@ index eef319b2..c6846087 100644
5255 [OpenStack]: https://www.openstack.org/
5356diff --git a/internal/providers/ionoscloud/ionoscloud.go b/internal/providers/ionoscloud/ionoscloud.go
5457new file mode 100644
55- index 00000000..cc660998
58+ index 00000000..69f3ccd5
5659--- /dev/null
5760+++ b/internal/providers/ionoscloud/ionoscloud.go
58- @@ -0,0 +1,149 @@
61+ @@ -0,0 +1,200 @@
5962+ // Copyright 2024 Red Hat, Inc.
6063+ //
6164+ // Licensed under the Apache License, Version 2.0 (the "License");
@@ -83,130 +86,181 @@ index 00000000..cc660998
8386+ package ionoscloud
8487+
8588+ import (
86- + "context"
87- + "fmt"
88- + "os"
89- + "os/exec"
90- + "path/filepath"
91- + "time"
92- +
93- + "github.com/flatcar/ignition/v2/config/v3_6_experimental/types"
94- + "github.com/flatcar/ignition/v2/internal/distro"
95- + "github.com/flatcar/ignition/v2/internal/log"
96- + "github.com/flatcar/ignition/v2/internal/platform"
97- + "github.com/flatcar/ignition/v2/internal/providers/util"
98- + "github.com/flatcar/ignition/v2/internal/resource"
99- + ut "github.com/flatcar/ignition/v2/internal/util"
100- +
101- + "github.com/coreos/vcontext/report"
89+ + "context"
90+ + "fmt"
91+ + "os"
92+ + "os/exec"
93+ + "path/filepath"
94+ + "strings"
95+ + "time"
96+ +
97+ + "github.com/flatcar/ignition/v2/config/v3_6_experimental/types"
98+ + "github.com/flatcar/ignition/v2/internal/distro"
99+ + "github.com/flatcar/ignition/v2/internal/log"
100+ + "github.com/flatcar/ignition/v2/internal/platform"
101+ + "github.com/flatcar/ignition/v2/internal/providers/util"
102+ + "github.com/flatcar/ignition/v2/internal/resource"
103+ + ut "github.com/flatcar/ignition/v2/internal/util"
104+ +
105+ + "github.com/coreos/vcontext/report"
102106+ )
103107+
104108+ const (
105- + deviceLabelEnvVar = "IGNITION_CONFIG_DEVICE_LABEL"
106- + defaultDeviceLabel = "OEM"
107- + userDataPath = "/config/user-data"
109+ + deviceLabelKernelFlag = "ignition.config.device"
110+ + defaultDeviceLabel = "OEM"
111+ + userDataKernelFlag = "ignition.config.path"
112+ + defaultUserDataPath = "config.ign"
108113+ )
109114+
110115+ func init() {
111- + platform.Register(platform.Provider{
112- + Name: "ionoscloud",
113- + Fetch: fetchConfig,
114- + })
116+ + platform.Register(platform.Provider{
117+ + Name: "ionoscloud",
118+ + Fetch: fetchConfig,
119+ + })
115120+ }
116121+
117122+ func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
118- + var data []byte
119- + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
120- +
121- + dispatch := func(name string, fn func() ([]byte, error)) {
122- + raw, err := fn()
123- + if err != nil {
124- + switch err {
125- + case context.Canceled:
126- + case context.DeadlineExceeded:
127- + f.Logger.Err("timed out while fetching config from %s", name)
128- + default:
129- + f.Logger.Err("failed to fetch config from %s: %v", name, err)
130- + }
131- + return
132- + }
133- +
134- + data = raw
135- + cancel()
136- + }
137- +
138- + deviceLabel := os.Getenv(deviceLabelEnvVar)
139- + if deviceLabel == "" {
140- + deviceLabel = defaultDeviceLabel
141- + }
142- +
143- + go dispatch(
144- + "load config from disk", func() ([]byte, error) {
145- + return fetchConfigFromDevice(f.Logger, ctx, filepath.Join(distro.DiskByLabelDir(), deviceLabel))
146- + },
147- + )
148- +
149- + <-ctx.Done()
150- + if ctx.Err() == context.DeadlineExceeded {
151- + f.Logger.Info("disk was not available in time. Continuing without a config...")
152- + }
153- +
154- + return util.ParseConfig(f.Logger, data)
123+ + var data []byte
124+ + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
125+ +
126+ + dispatch := func(name string, fn func() ([]byte, error)) {
127+ + raw, err := fn()
128+ + if err != nil {
129+ + switch err {
130+ + case context.Canceled:
131+ + case context.DeadlineExceeded:
132+ + f.Logger.Err("timed out while fetching config from %s", name)
133+ + default:
134+ + f.Logger.Err("failed to fetch config from %s: %v", name, err)
135+ + }
136+ + return
137+ + }
138+ +
139+ + data = raw
140+ + cancel()
141+ + }
142+ +
143+ + deviceLabel, userDataPath, err := readFromKernelParams(f.Logger)
144+ +
145+ + if err != nil {
146+ + f.Logger.Err("couldn't read kernel parameters: %v", err)
147+ + return types.Config{}, report.Report{}, err
148+ + }
149+ +
150+ + if deviceLabel == "" {
151+ + deviceLabel = defaultDeviceLabel
152+ + }
153+ +
154+ + if userDataPath == "" {
155+ + userDataPath = defaultUserDataPath
156+ + }
157+ +
158+ + go dispatch(
159+ + "load config from disk", func() ([]byte, error) {
160+ + return fetchConfigFromDevice(f.Logger, ctx, deviceLabel, userDataPath)
161+ + },
162+ + )
163+ +
164+ + <-ctx.Done()
165+ + if ctx.Err() == context.DeadlineExceeded {
166+ + f.Logger.Info("disk was not available in time. Continuing without a config...")
167+ + }
168+ +
169+ + return util.ParseConfig(f.Logger, data)
155170+ }
156171+
157172+ func fileExists(path string) bool {
158- + _, err := os.Stat(path)
159- + return (err == nil)
173+ + _, err := os.Stat(path)
174+ + return (err == nil)
175+ + }
176+ +
177+ + func fetchConfigFromDevice(logger *log.Logger,
178+ + ctx context.Context,
179+ + deviceLabel string,
180+ + dataPath string,
181+ + ) ([]byte, error) {
182+ + device := filepath.Join(distro.DiskByLabelDir(), deviceLabel)
183+ + for !fileExists(device) {
184+ + logger.Debug("disk (%q) not found. Waiting...", device)
185+ + select {
186+ + case <-time.After(time.Second):
187+ + case <-ctx.Done():
188+ + return nil, ctx.Err()
189+ + }
190+ + }
191+ +
192+ + logger.Debug("creating temporary mount point")
193+ + mnt, err := os.MkdirTemp("", "ignition-config")
194+ + if err != nil {
195+ + return nil, fmt.Errorf("failed to create temp directory: %v", err)
196+ + }
197+ + defer os.Remove(mnt)
198+ +
199+ + cmd := exec.Command(distro.MountCmd(), "-o", "ro", "-t", "auto", device, mnt)
200+ + if _, err := logger.LogCmd(cmd, "mounting disk"); err != nil {
201+ + return nil, err
202+ + }
203+ + defer func() {
204+ + _ = logger.LogOp(
205+ + func() error {
206+ + return ut.UmountPath(mnt)
207+ + },
208+ + "unmounting %q at %q", device, mnt,
209+ + )
210+ + }()
211+ +
212+ + if !fileExists(filepath.Join(mnt, dataPath)) {
213+ + return nil, nil
214+ + }
215+ +
216+ + contents, err := os.ReadFile(filepath.Join(mnt, dataPath))
217+ + if err != nil {
218+ + return nil, err
219+ + }
220+ +
221+ + if util.IsCloudConfig(contents) {
222+ + logger.Debug("disk (%q) contains a cloud-config configuration, ignoring", device)
223+ + return nil, nil
224+ + }
225+ +
226+ + return contents, nil
227+ + }
228+ +
229+ + func readFromKernelParams(logger *log.Logger) (string, string, error) {
230+ + args, err := os.ReadFile(distro.KernelCmdlinePath())
231+ + if err != nil {
232+ + return "", "", err
233+ + }
234+ +
235+ + deviceLabel, userDataPath := parseParams(args)
236+ + logger.Debug("parsed device label from parameters: %s", deviceLabel)
237+ + logger.Debug("parsed user-data path from parameters: %s", userDataPath)
238+ + return deviceLabel, userDataPath, nil
160239+ }
161240+
162- + func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, device string) ([]byte, error) {
163- + for !fileExists(device) {
164- + logger.Debug("disk (%q) not found. Waiting...", device)
165- + select {
166- + case <-time.After(time.Second):
167- + case <-ctx.Done():
168- + return nil, ctx.Err()
169- + }
170- + }
171- +
172- + logger.Debug("creating temporary mount point")
173- + mnt, err := os.MkdirTemp("", "ignition-config")
174- + if err != nil {
175- + return nil, fmt.Errorf("failed to create temp directory: %v", err)
176- + }
177- + defer os.Remove(mnt)
178- +
179- + cmd := exec.Command(distro.MountCmd(), "-o", "ro", "-t", "auto", device, mnt)
180- + if _, err := logger.LogCmd(cmd, "mounting disk"); err != nil {
181- + return nil, err
182- + }
183- + defer func() {
184- + _ = logger.LogOp(
185- + func() error {
186- + return ut.UmountPath(mnt)
187- + },
188- + "unmounting %q at %q", device, mnt,
189- + )
190- + }()
191- +
192- + if !fileExists(filepath.Join(mnt, userDataPath)) {
193- + return nil, nil
194- + }
195- +
196- + contents, err := os.ReadFile(filepath.Join(mnt, userDataPath))
197- + if err != nil {
198- + return nil, err
199- + }
200- +
201- + if util.IsCloudConfig(contents) {
202- + logger.Debug("disk (%q) contains a cloud-config configuration, ignoring", device)
203- + return nil, nil
204- + }
205- +
206- + return contents, nil
241+ + func parseParams(args []byte) (deviceLabel, userDataPath string) {
242+ + for _, arg := range strings.Split(string(args), " ") {
243+ + parts := strings.SplitN(strings.TrimSpace(arg), "=", 2)
244+ + if len(parts) != 2 {
245+ + continue
246+ + }
247+ +
248+ + key := parts[0]
249+ + value := parts[1]
250+ +
251+ + if key == deviceLabelKernelFlag {
252+ + deviceLabel = value
253+ + }
254+ +
255+ + if key == userDataKernelFlag {
256+ + userDataPath = value
257+ + }
258+ + }
259+ +
260+ + return
207261+ }
208262diff --git a/internal/providers/proxmoxve/proxmoxve.go b/internal/providers/proxmoxve/proxmoxve.go
209- index 490bfe30..b0dbb481 100644
263+ index cbfe7c7d..58525c50 100644
210264--- a/internal/providers/proxmoxve/proxmoxve.go
211265+++ b/internal/providers/proxmoxve/proxmoxve.go
212266@@ -20,7 +20,6 @@
@@ -229,10 +283,24 @@ index 490bfe30..b0dbb481 100644
229283 }
230284diff --git a/internal/providers/util/cloudconfig.go b/internal/providers/util/cloudconfig.go
231285new file mode 100644
232- index 00000000..abe9a2b6
286+ index 00000000..82ed9f36
233287--- /dev/null
234288+++ b/internal/providers/util/cloudconfig.go
235- @@ -0,0 +1,13 @@
289+ @@ -0,0 +1,27 @@
290+ + // Copyright 2024 Red Hat, Inc.
291+ + //
292+ + // Licensed under the Apache License, Version 2.0 (the "License");
293+ + // you may not use this file except in compliance with the License.
294+ + // You may obtain a copy of the License at
295+ + //
296+ + // http://www.apache.org/licenses/LICENSE-2.0
297+ + //
298+ + // Unless required by applicable law or agreed to in writing, software
299+ + // distributed under the License is distributed on an "AS IS" BASIS,
300+ + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
301+ + // See the License for the specific language governing permissions and
302+ + // limitations under the License.
303+ +
236304+ package util
237305+
238306+ import (
@@ -247,7 +315,7 @@ index 00000000..abe9a2b6
247315+ return false
248316+ }
249317diff --git a/internal/register/providers.go b/internal/register/providers.go
250- index bda4b7cf..63249c7d 100644
318+ index eb4bd9d2..f37aa906 100644
251319--- a/internal/register/providers.go
252320+++ b/internal/register/providers.go
253321@@ -29,6 +29,7 @@ import (
0 commit comments