Skip to content

Commit 90da30b

Browse files
fmaximusSigert Goeminne
authored andcommitted
*: add support for cloudstack config-drive
Co-Authored-By: Frank Maximus <frank.maximus@nuagenetworks.net> Upstream PR: apache/cloudstack#2097 Design document: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Using+ConfigDrive+for+Metadata%2C+Userdata+and+Password
1 parent e6a8281 commit 90da30b

2 files changed

Lines changed: 142 additions & 4 deletions

File tree

internal/oem/oem.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/coreos/ignition/internal/log"
2323
"github.com/coreos/ignition/internal/providers"
2424
"github.com/coreos/ignition/internal/providers/azure"
25+
"github.com/coreos/ignition/internal/providers/cloudstack"
2526
"github.com/coreos/ignition/internal/providers/digitalocean"
2627
"github.com/coreos/ignition/internal/providers/ec2"
2728
"github.com/coreos/ignition/internal/providers/file"
@@ -98,10 +99,6 @@ func init() {
9899
name: "cloudsigma",
99100
fetch: noop.FetchConfig,
100101
})
101-
configs.Register(Config{
102-
name: "cloudstack",
103-
fetch: noop.FetchConfig,
104-
})
105102
configs.Register(Config{
106103
name: "digitalocean",
107104
fetch: digitalocean.FetchConfig,
@@ -138,6 +135,10 @@ func init() {
138135
},
139136
},
140137
})
138+
configs.Register(Config{name: "cloudstack",
139+
fetch: cloudstack.FetchConfig,
140+
defaultUserConfig: types.Config{Systemd: types.Systemd{Units: []types.Unit{userCloudInit("CloudStack", "cloudstack")}}},
141+
})
141142
configs.Register(Config{
142143
name: "ec2",
143144
fetch: ec2.FetchConfig,
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2017 CoreOS, 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+
// The CloudStack provider fetches configurations from the userdata available in
16+
// the config-drive.
17+
// NOTE: This provider is still EXPERIMENTAL.
18+
19+
package cloudstack
20+
21+
import (
22+
"fmt"
23+
"io/ioutil"
24+
"os"
25+
"os/exec"
26+
"path/filepath"
27+
"syscall"
28+
"time"
29+
30+
"github.com/coreos/ignition/config"
31+
"github.com/coreos/ignition/config/types"
32+
"github.com/coreos/ignition/config/validate/report"
33+
"github.com/coreos/ignition/internal/log"
34+
"github.com/coreos/ignition/internal/resource"
35+
36+
"golang.org/x/net/context"
37+
)
38+
39+
const (
40+
diskByLabelPath = "/dev/disk/by-label/"
41+
configDriveUserdataPath = "/cloudstack/userdata/user_data.txt"
42+
)
43+
44+
func FetchConfig(f resource.Fetcher) (types.Config, report.Report, error) {
45+
var data []byte
46+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
47+
48+
dispatch := func(name string, fn func() ([]byte, error)) {
49+
raw, err := fn()
50+
if err != nil {
51+
switch err {
52+
case context.Canceled:
53+
case context.DeadlineExceeded:
54+
f.Logger.Err("timed out while fetching config from %s", name)
55+
default:
56+
f.Logger.Err("failed to fetch config from %s: %v", name, err)
57+
}
58+
return
59+
}
60+
61+
data = raw
62+
cancel()
63+
}
64+
65+
go dispatch("config drive (config)", func() ([]byte, error) {
66+
return fetchConfigFromDevice(f.Logger, ctx, "config-2")
67+
})
68+
69+
go dispatch("config drive (CONFIG)", func() ([]byte, error) {
70+
return fetchConfigFromDevice(f.Logger, ctx, "CONFIG-2")
71+
})
72+
73+
<-ctx.Done()
74+
if ctx.Err() == context.DeadlineExceeded {
75+
f.Logger.Info("Config drive was not available in time. Continuing without a config...")
76+
}
77+
78+
return config.Parse(data)
79+
}
80+
81+
func fileExists(path string) bool {
82+
_, err := os.Stat(path)
83+
return (err == nil)
84+
}
85+
86+
func labelExists(label string) bool {
87+
_, err := getPath(label)
88+
return (err == nil)
89+
}
90+
91+
func getPath(label string) (string, error) {
92+
path := diskByLabelPath + label
93+
94+
if fileExists(path) {
95+
return path, nil
96+
}
97+
98+
return "", fmt.Errorf("label not found: %s", label)
99+
}
100+
101+
func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, label string) ([]byte, error) {
102+
for !labelExists(label) {
103+
logger.Debug("config drive (%q) not found. Waiting...", label)
104+
select {
105+
case <-time.After(time.Second):
106+
case <-ctx.Done():
107+
return nil, ctx.Err()
108+
}
109+
}
110+
111+
path, err := getPath(label)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
logger.Debug("creating temporary mount point")
117+
mnt, err := ioutil.TempDir("", "ignition-configdrive")
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to create temp directory: %v", err)
120+
}
121+
defer os.Remove(mnt)
122+
123+
cmd := exec.Command("/bin/mount", "-o", "ro", "-t", "auto", path, mnt)
124+
if _, err := logger.LogCmd(cmd, "mounting config drive"); err != nil {
125+
return nil, err
126+
}
127+
defer logger.LogOp(
128+
func() error { return syscall.Unmount(mnt, 0) },
129+
"unmounting %q at %q", path, mnt,
130+
)
131+
132+
if !fileExists(filepath.Join(mnt, configDriveUserdataPath)) {
133+
return nil, nil
134+
}
135+
136+
return ioutil.ReadFile(filepath.Join(mnt, configDriveUserdataPath))
137+
}

0 commit comments

Comments
 (0)