Skip to content
This repository was archived by the owner on Sep 18, 2020. It is now read-only.

Commit 9d15f2c

Browse files
author
Alex Crawford
committed
Merge pull request #213 from crawford/digitalocean
digitalocean: Add support for DigitalOcean
2 parents 3a550af + 2134fce commit 9d15f2c

File tree

12 files changed

+763
-16
lines changed

12 files changed

+763
-16
lines changed

coreos-cloudinit.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
1212
"github.com/coreos/coreos-cloudinit/datasource/file"
1313
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
14+
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
1415
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
1516
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
1617
"github.com/coreos/coreos-cloudinit/datasource/url"
@@ -30,13 +31,14 @@ var (
3031
printVersion bool
3132
ignoreFailure bool
3233
sources struct {
33-
file string
34-
configDrive string
35-
metadataService bool
36-
ec2MetadataService string
37-
cloudSigmaMetadataService bool
38-
url string
39-
procCmdLine bool
34+
file string
35+
configDrive string
36+
metadataService bool
37+
ec2MetadataService string
38+
cloudSigmaMetadataService bool
39+
digitalOceanMetadataService string
40+
url string
41+
procCmdLine bool
4042
}
4143
convertNetconf string
4244
workspace string
@@ -49,11 +51,12 @@ func init() {
4951
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file")
5052
flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
5153
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
52-
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download data from the provided metadata service")
54+
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
5355
flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
56+
flag.StringVar(&sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
5457
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url")
5558
flag.BoolVar(&sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
56-
flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files (requires the -from-configdrive flag)")
59+
flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
5760
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
5861
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
5962
}
@@ -73,16 +76,12 @@ func main() {
7376
os.Exit(0)
7477
}
7578

76-
if convertNetconf != "" && sources.configDrive == "" {
77-
fmt.Println("-convert-netconf flag requires -from-configdrive")
78-
os.Exit(1)
79-
}
80-
8179
switch convertNetconf {
8280
case "":
8381
case "debian":
82+
case "digitalocean":
8483
default:
85-
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf)
84+
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", convertNetconf)
8685
os.Exit(1)
8786
}
8887

@@ -209,6 +208,13 @@ func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudCon
209208
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
210209
}
211210
}
211+
if mdcc.NetworkConfig != "" {
212+
if udcc.NetworkConfig != "" {
213+
fmt.Printf("Warning: user-data NetworkConfig %s overrides metadata NetworkConfig %s\n", udcc.NetworkConfig, mdcc.NetworkConfig)
214+
} else {
215+
udcc.NetworkConfig = mdcc.NetworkConfig
216+
}
217+
}
212218
return udcc
213219
}
214220

@@ -234,6 +240,9 @@ func getDatasources() []datasource.Datasource {
234240
if sources.cloudSigmaMetadataService {
235241
dss = append(dss, cloudsigma.NewServerContextService())
236242
}
243+
if sources.digitalOceanMetadataService != "" {
244+
dss = append(dss, digitalocean.NewDatasource(sources.digitalOceanMetadataService))
245+
}
237246
if sources.procCmdLine {
238247
dss = append(dss, proc_cmdline.NewDatasource())
239248
}

coreos-cloudinit_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func TestMergeCloudConfig(t *testing.T) {
1212
SSHAuthorizedKeys: []string{"abc", "def"},
1313
Hostname: "foobar",
1414
NetworkConfigPath: "/path/somewhere",
15+
NetworkConfig: `{}`,
1516
}
1617
for i, tt := range []struct {
1718
udcc initialize.CloudConfig
@@ -36,6 +37,7 @@ func TestMergeCloudConfig(t *testing.T) {
3637
initialize.CloudConfig{
3738
Hostname: "meta-hostname",
3839
NetworkConfigPath: "/path/meta",
40+
NetworkConfig: `{"hostname":"test"}`,
3941
},
4042
simplecc,
4143
},
@@ -45,6 +47,7 @@ func TestMergeCloudConfig(t *testing.T) {
4547
SSHAuthorizedKeys: []string{"abc", "def"},
4648
Hostname: "user-hostname",
4749
NetworkConfigPath: "/path/somewhere",
50+
NetworkConfig: `{"hostname":"test"}`,
4851
},
4952
initialize.CloudConfig{
5053
SSHAuthorizedKeys: []string{"woof", "qux"},
@@ -54,6 +57,7 @@ func TestMergeCloudConfig(t *testing.T) {
5457
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
5558
Hostname: "user-hostname",
5659
NetworkConfigPath: "/path/somewhere",
60+
NetworkConfig: `{"hostname":"test"}`,
5761
},
5862
},
5963
{
@@ -64,11 +68,13 @@ func TestMergeCloudConfig(t *testing.T) {
6468
initialize.CloudConfig{
6569
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
6670
NetworkConfigPath: "/dev/fun",
71+
NetworkConfig: `{"hostname":"test"}`,
6772
},
6873
initialize.CloudConfig{
6974
Hostname: "supercool",
7075
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
7176
NetworkConfigPath: "/dev/fun",
77+
NetworkConfig: `{"hostname":"test"}`,
7278
},
7379
},
7480
{
@@ -80,11 +86,13 @@ func TestMergeCloudConfig(t *testing.T) {
8086
initialize.CloudConfig{
8187
Hostname: "youyouyou",
8288
NetworkConfigPath: "meta-meta-yo",
89+
NetworkConfig: `{"hostname":"test"}`,
8390
},
8491
initialize.CloudConfig{
8592
Hostname: "mememe",
8693
ManageEtcHosts: initialize.EtcHosts("lolz"),
8794
NetworkConfigPath: "meta-meta-yo",
95+
NetworkConfig: `{"hostname":"test"}`,
8896
},
8997
},
9098
{
@@ -95,10 +103,12 @@ func TestMergeCloudConfig(t *testing.T) {
95103
initialize.CloudConfig{
96104
ManageEtcHosts: initialize.EtcHosts("lolz"),
97105
NetworkConfigPath: "meta-meta-yo",
106+
NetworkConfig: `{"hostname":"test"}`,
98107
},
99108
initialize.CloudConfig{
100109
Hostname: "mememe",
101110
NetworkConfigPath: "meta-meta-yo",
111+
NetworkConfig: `{"hostname":"test"}`,
102112
},
103113
},
104114
} {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package digitalocean
2+
3+
import (
4+
"encoding/json"
5+
"strconv"
6+
7+
"github.com/coreos/coreos-cloudinit/datasource/metadata"
8+
)
9+
10+
const (
11+
DefaultAddress = "http://169.254.169.254/"
12+
apiVersion = "metadata/v1"
13+
userdataUrl = apiVersion + "/user-data"
14+
metadataPath = apiVersion + ".json"
15+
)
16+
17+
type Address struct {
18+
IPAddress string `json:"ip_address"`
19+
Netmask string `json:"netmask"`
20+
Cidr int `json:"cidr"`
21+
Gateway string `json:"gateway"`
22+
}
23+
24+
type Interface struct {
25+
IPv4 *Address `json:"ipv4"`
26+
IPv6 *Address `json:"ipv6"`
27+
MAC string `json:"mac"`
28+
Type string `json:"type"`
29+
}
30+
31+
type Interfaces struct {
32+
Public []Interface `json:"public"`
33+
Private []Interface `json:"private"`
34+
}
35+
36+
type DNS struct {
37+
Nameservers []string `json:"nameservers"`
38+
}
39+
40+
type Metadata struct {
41+
Hostname string `json:"hostname"`
42+
Interfaces Interfaces `json:"interfaces"`
43+
PublicKeys []string `json:"public_keys"`
44+
DNS DNS `json:"dns"`
45+
}
46+
47+
type metadataService struct {
48+
interfaces Interfaces
49+
dns DNS
50+
metadata.MetadataService
51+
}
52+
53+
func NewDatasource(root string) *metadataService {
54+
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
55+
}
56+
57+
func (ms *metadataService) FetchMetadata() ([]byte, error) {
58+
data, err := ms.FetchData(ms.MetadataUrl())
59+
if err != nil || len(data) == 0 {
60+
return []byte{}, err
61+
}
62+
63+
var metadata Metadata
64+
if err := json.Unmarshal(data, &metadata); err != nil {
65+
return []byte{}, err
66+
}
67+
68+
ms.interfaces = metadata.Interfaces
69+
ms.dns = metadata.DNS
70+
71+
attrs := make(map[string]interface{})
72+
if len(metadata.Interfaces.Public) > 0 {
73+
if metadata.Interfaces.Public[0].IPv4 != nil {
74+
attrs["public-ipv4"] = metadata.Interfaces.Public[0].IPv4.IPAddress
75+
}
76+
if metadata.Interfaces.Public[0].IPv6 != nil {
77+
attrs["public-ipv6"] = metadata.Interfaces.Public[0].IPv6.IPAddress
78+
}
79+
}
80+
if len(metadata.Interfaces.Private) > 0 {
81+
if metadata.Interfaces.Private[0].IPv4 != nil {
82+
attrs["local-ipv4"] = metadata.Interfaces.Private[0].IPv4.IPAddress
83+
}
84+
if metadata.Interfaces.Private[0].IPv6 != nil {
85+
attrs["local-ipv6"] = metadata.Interfaces.Private[0].IPv6.IPAddress
86+
}
87+
}
88+
attrs["hostname"] = metadata.Hostname
89+
keys := make(map[string]string)
90+
for i, key := range metadata.PublicKeys {
91+
keys[strconv.Itoa(i)] = key
92+
}
93+
attrs["public_keys"] = keys
94+
95+
return json.Marshal(attrs)
96+
}
97+
98+
func (ms metadataService) FetchNetworkConfig(filename string) ([]byte, error) {
99+
return json.Marshal(Metadata{
100+
Interfaces: ms.interfaces,
101+
DNS: ms.dns,
102+
})
103+
}
104+
105+
func (ms metadataService) Type() string {
106+
return "digitalocean-metadata-service"
107+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package digitalocean
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/coreos/coreos-cloudinit/datasource/metadata"
9+
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
10+
"github.com/coreos/coreos-cloudinit/pkg"
11+
)
12+
13+
func TestType(t *testing.T) {
14+
want := "digitalocean-metadata-service"
15+
if kind := (metadataService{}).Type(); kind != want {
16+
t.Fatalf("bad type: want %q, got %q", want, kind)
17+
}
18+
}
19+
20+
func TestFetchMetadata(t *testing.T) {
21+
for _, tt := range []struct {
22+
root string
23+
metadataPath string
24+
resources map[string]string
25+
expect []byte
26+
clientErr error
27+
expectErr error
28+
}{
29+
{
30+
root: "/",
31+
metadataPath: "v1.json",
32+
resources: map[string]string{
33+
"/v1.json": "bad",
34+
},
35+
expectErr: fmt.Errorf("invalid character 'b' looking for beginning of value"),
36+
},
37+
{
38+
root: "/",
39+
metadataPath: "v1.json",
40+
resources: map[string]string{
41+
"/v1.json": `{
42+
"droplet_id": 1,
43+
"user_data": "hello",
44+
"vendor_data": "hello",
45+
"public_keys": [
46+
"publickey1",
47+
"publickey2"
48+
],
49+
"region": "nyc2",
50+
"interfaces": {
51+
"public": [
52+
{
53+
"ipv4": {
54+
"ip_address": "192.168.1.2",
55+
"netmask": "255.255.255.0",
56+
"gateway": "192.168.1.1"
57+
},
58+
"ipv6": {
59+
"ip_address": "fe00::",
60+
"cidr": 126,
61+
"gateway": "fe00::"
62+
},
63+
"mac": "ab:cd:ef:gh:ij",
64+
"type": "public"
65+
}
66+
]
67+
}
68+
}`,
69+
},
70+
expect: []byte(`{"hostname":"","public-ipv4":"192.168.1.2","public-ipv6":"fe00::","public_keys":{"0":"publickey1","1":"publickey2"}}`),
71+
},
72+
{
73+
clientErr: pkg.ErrTimeout{fmt.Errorf("test error")},
74+
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
75+
},
76+
} {
77+
service := &metadataService{
78+
MetadataService: metadata.MetadataService{
79+
Root: tt.root,
80+
Client: &test.HttpClient{tt.resources, tt.clientErr},
81+
MetadataPath: tt.metadataPath,
82+
},
83+
}
84+
metadata, err := service.FetchMetadata()
85+
if Error(err) != Error(tt.expectErr) {
86+
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
87+
}
88+
if !bytes.Equal(metadata, tt.expect) {
89+
t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata)
90+
}
91+
}
92+
}
93+
94+
func Error(err error) string {
95+
if err != nil {
96+
return err.Error()
97+
}
98+
return ""
99+
}

initialize/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ func Apply(cfg CloudConfig, env *Environment) error {
263263
switch env.NetconfType() {
264264
case "debian":
265265
interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig)
266+
case "digitalocean":
267+
interfaces, err = network.ProcessDigitalOceanNetconf(cfg.NetworkConfig)
266268
default:
267269
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
268270
}

0 commit comments

Comments
 (0)