Skip to content

Commit 859b937

Browse files
mcbenjemaatuunit
andcommitted
providers: add support for ionos cloud
Add support for IONOS Cloud Add check to ignore cloud-config Add mounting of root partition Add better documentation Co-authored-by: Jan Larwig <jan.larwig@ionos.com>
1 parent a204f42 commit 859b937

File tree

6 files changed

+233
-3
lines changed

6 files changed

+233
-3
lines changed

docs/release-notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ nav_order: 9
1010

1111
### Features
1212

13+
- Support IONOS Cloud
14+
1315
### Changes
1416

1517
### Bug fixes

docs/supported-platforms.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Ignition is currently supported for the following platforms:
2020
* [Hetzner Cloud] (`hetzner`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
2121
* [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.
2222
* [IBM Cloud] (`ibmcloud`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
23+
* [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`.
2324
* [KubeVirt] (`kubevirt`) - Ignition will read its configuration from the instance userdata via config drive. Cloud SSH keys are handled separately.
2425
* 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.
2526
* [Nutanix] (`nutanix`) - Ignition will read its configuration from the instance userdata via config drive. Cloud SSH keys are handled separately.
@@ -52,6 +53,7 @@ For most cloud providers, cloud SSH keys and custom network configuration are ha
5253
[Hetzner Cloud]: https://www.hetzner.com/cloud
5354
[Microsoft Hyper-V]: https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/
5455
[IBM Cloud]: https://www.ibm.com/cloud/vpc
56+
[IONOS Cloud]: https://cloud.ionos.com/
5557
[KubeVirt]: https://kubevirt.io
5658
[Nutanix]: https://www.nutanix.com/products/ahv
5759
[OpenStack]: https://www.openstack.org/
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright 2024 Red Hat, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// NOTE: This provider is still EXPERIMENTAL.
16+
//
17+
// The IONOS Cloud provider fetches the ignition config from the user-data
18+
// available in an injected file at /var/lib/cloud/seed/nocloud/user-data.
19+
// This file is created by the IONOS Cloud VM handler before the first boot
20+
// through the cloud init user data handling.
21+
//
22+
// User data with the directive #cloud-config will be ignored
23+
// See for more: https://docs.ionos.com/cloud/compute-services/compute-engine/how-tos/boot-cloud-init
24+
25+
package ionoscloud
26+
27+
import (
28+
"context"
29+
"fmt"
30+
"os"
31+
"os/exec"
32+
"path/filepath"
33+
"strings"
34+
"time"
35+
36+
"github.com/coreos/ignition/v2/config/v3_6_experimental/types"
37+
"github.com/coreos/ignition/v2/internal/distro"
38+
"github.com/coreos/ignition/v2/internal/log"
39+
"github.com/coreos/ignition/v2/internal/platform"
40+
"github.com/coreos/ignition/v2/internal/providers/util"
41+
"github.com/coreos/ignition/v2/internal/resource"
42+
ut "github.com/coreos/ignition/v2/internal/util"
43+
44+
"github.com/coreos/vcontext/report"
45+
)
46+
47+
const (
48+
deviceLabelKernelFlag = "ignition.config.device"
49+
defaultDeviceLabel = "OEM"
50+
userDataKernelFlag = "ignition.config.path"
51+
defaultUserDataPath = "config.ign"
52+
)
53+
54+
func init() {
55+
platform.Register(platform.Provider{
56+
Name: "ionoscloud",
57+
Fetch: fetchConfig,
58+
})
59+
}
60+
61+
func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
62+
var data []byte
63+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
64+
65+
dispatch := func(name string, fn func() ([]byte, error)) {
66+
raw, err := fn()
67+
if err != nil {
68+
switch err {
69+
case context.Canceled:
70+
case context.DeadlineExceeded:
71+
f.Logger.Err("timed out while fetching config from %s", name)
72+
default:
73+
f.Logger.Err("failed to fetch config from %s: %v", name, err)
74+
}
75+
return
76+
}
77+
78+
data = raw
79+
cancel()
80+
}
81+
82+
deviceLabel, userDataPath, err := readFromKernelParams(f.Logger)
83+
84+
if err != nil {
85+
f.Logger.Err("couldn't read kernel parameters: %v", err)
86+
return types.Config{}, report.Report{}, err
87+
}
88+
89+
if deviceLabel == "" {
90+
deviceLabel = defaultDeviceLabel
91+
}
92+
93+
if userDataPath == "" {
94+
userDataPath = defaultUserDataPath
95+
}
96+
97+
go dispatch(
98+
"load config from disk", func() ([]byte, error) {
99+
return fetchConfigFromDevice(f.Logger, ctx, deviceLabel, userDataPath)
100+
},
101+
)
102+
103+
<-ctx.Done()
104+
if ctx.Err() == context.DeadlineExceeded {
105+
f.Logger.Info("disk was not available in time. Continuing without a config...")
106+
}
107+
108+
return util.ParseConfig(f.Logger, data)
109+
}
110+
111+
func fileExists(path string) bool {
112+
_, err := os.Stat(path)
113+
return (err == nil)
114+
}
115+
116+
func fetchConfigFromDevice(logger *log.Logger,
117+
ctx context.Context,
118+
deviceLabel string,
119+
dataPath string,
120+
) ([]byte, error) {
121+
device := filepath.Join(distro.DiskByLabelDir(), deviceLabel)
122+
for !fileExists(device) {
123+
logger.Debug("disk (%q) not found. Waiting...", device)
124+
select {
125+
case <-time.After(time.Second):
126+
case <-ctx.Done():
127+
return nil, ctx.Err()
128+
}
129+
}
130+
131+
logger.Debug("creating temporary mount point")
132+
mnt, err := os.MkdirTemp("", "ignition-config")
133+
if err != nil {
134+
return nil, fmt.Errorf("failed to create temp directory: %v", err)
135+
}
136+
defer os.Remove(mnt)
137+
138+
cmd := exec.Command(distro.MountCmd(), "-o", "ro", "-t", "auto", device, mnt)
139+
if _, err := logger.LogCmd(cmd, "mounting disk"); err != nil {
140+
return nil, err
141+
}
142+
defer func() {
143+
_ = logger.LogOp(
144+
func() error {
145+
return ut.UmountPath(mnt)
146+
},
147+
"unmounting %q at %q", device, mnt,
148+
)
149+
}()
150+
151+
if !fileExists(filepath.Join(mnt, dataPath)) {
152+
return nil, nil
153+
}
154+
155+
contents, err := os.ReadFile(filepath.Join(mnt, dataPath))
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
if util.IsCloudConfig(contents) {
161+
logger.Debug("disk (%q) contains a cloud-config configuration, ignoring", device)
162+
return nil, nil
163+
}
164+
165+
return contents, nil
166+
}
167+
168+
func readFromKernelParams(logger *log.Logger) (string, string, error) {
169+
args, err := os.ReadFile(distro.KernelCmdlinePath())
170+
if err != nil {
171+
return "", "", err
172+
}
173+
174+
deviceLabel, userDataPath := parseParams(args)
175+
logger.Debug("parsed device label from parameters: %s", deviceLabel)
176+
logger.Debug("parsed user-data path from parameters: %s", userDataPath)
177+
return deviceLabel, userDataPath, nil
178+
}
179+
180+
func parseParams(args []byte) (deviceLabel, userDataPath string) {
181+
for _, arg := range strings.Split(string(args), " ") {
182+
parts := strings.SplitN(strings.TrimSpace(arg), "=", 2)
183+
if len(parts) != 2 {
184+
continue
185+
}
186+
187+
key := parts[0]
188+
value := parts[1]
189+
190+
if key == deviceLabelKernelFlag {
191+
deviceLabel = value
192+
}
193+
194+
if key == userDataKernelFlag {
195+
userDataPath = value
196+
}
197+
}
198+
199+
return
200+
}

internal/providers/proxmoxve/proxmoxve.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package proxmoxve
2121

2222
import (
23-
"bytes"
2423
"context"
2524
"fmt"
2625
"os"
@@ -132,8 +131,7 @@ func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, path string)
132131
return nil, err
133132
}
134133

135-
header := []byte("#cloud-config\n")
136-
if bytes.HasPrefix(contents, header) {
134+
if util.IsCloudConfig(contents) {
137135
logger.Debug("config drive (%q) contains a cloud-config configuration, ignoring", path)
138136
return nil, nil
139137
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2024 Red Hat, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package util
16+
17+
import (
18+
"bytes"
19+
)
20+
21+
func IsCloudConfig(contents []byte) bool {
22+
header := []byte("#cloud-config\n")
23+
if bytes.HasPrefix(contents, header) {
24+
return true
25+
}
26+
return false
27+
}

internal/register/providers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
_ "github.com/coreos/ignition/v2/internal/providers/hetzner"
3030
_ "github.com/coreos/ignition/v2/internal/providers/hyperv"
3131
_ "github.com/coreos/ignition/v2/internal/providers/ibmcloud"
32+
_ "github.com/coreos/ignition/v2/internal/providers/ionoscloud"
3233
_ "github.com/coreos/ignition/v2/internal/providers/kubevirt"
3334
_ "github.com/coreos/ignition/v2/internal/providers/metal"
3435
_ "github.com/coreos/ignition/v2/internal/providers/nutanix"

0 commit comments

Comments
 (0)