Merge pull request #213 from crawford/digitalocean
digitalocean: Add support for DigitalOcean
This commit is contained in:
commit
9d15f2cfaf
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/file"
|
"github.com/coreos/coreos-cloudinit/datasource/file"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
|
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/url"
|
"github.com/coreos/coreos-cloudinit/datasource/url"
|
||||||
@ -35,6 +36,7 @@ var (
|
|||||||
metadataService bool
|
metadataService bool
|
||||||
ec2MetadataService string
|
ec2MetadataService string
|
||||||
cloudSigmaMetadataService bool
|
cloudSigmaMetadataService bool
|
||||||
|
digitalOceanMetadataService string
|
||||||
url string
|
url string
|
||||||
procCmdLine bool
|
procCmdLine bool
|
||||||
}
|
}
|
||||||
@ -49,11 +51,12 @@ func init() {
|
|||||||
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file")
|
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file")
|
||||||
flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
|
flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
|
||||||
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
|
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
|
||||||
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download data from the provided metadata service")
|
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
|
||||||
flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
|
flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
|
||||||
|
flag.StringVar(&sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
|
||||||
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url")
|
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url")
|
||||||
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))
|
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))
|
||||||
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)")
|
flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
|
||||||
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
||||||
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
||||||
}
|
}
|
||||||
@ -73,16 +76,12 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if convertNetconf != "" && sources.configDrive == "" {
|
|
||||||
fmt.Println("-convert-netconf flag requires -from-configdrive")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch convertNetconf {
|
switch convertNetconf {
|
||||||
case "":
|
case "":
|
||||||
case "debian":
|
case "debian":
|
||||||
|
case "digitalocean":
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf)
|
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", convertNetconf)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +208,13 @@ func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudCon
|
|||||||
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
|
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if mdcc.NetworkConfig != "" {
|
||||||
|
if udcc.NetworkConfig != "" {
|
||||||
|
fmt.Printf("Warning: user-data NetworkConfig %s overrides metadata NetworkConfig %s\n", udcc.NetworkConfig, mdcc.NetworkConfig)
|
||||||
|
} else {
|
||||||
|
udcc.NetworkConfig = mdcc.NetworkConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
return udcc
|
return udcc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,6 +240,9 @@ func getDatasources() []datasource.Datasource {
|
|||||||
if sources.cloudSigmaMetadataService {
|
if sources.cloudSigmaMetadataService {
|
||||||
dss = append(dss, cloudsigma.NewServerContextService())
|
dss = append(dss, cloudsigma.NewServerContextService())
|
||||||
}
|
}
|
||||||
|
if sources.digitalOceanMetadataService != "" {
|
||||||
|
dss = append(dss, digitalocean.NewDatasource(sources.digitalOceanMetadataService))
|
||||||
|
}
|
||||||
if sources.procCmdLine {
|
if sources.procCmdLine {
|
||||||
dss = append(dss, proc_cmdline.NewDatasource())
|
dss = append(dss, proc_cmdline.NewDatasource())
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
NetworkConfig: `{}`,
|
||||||
}
|
}
|
||||||
for i, tt := range []struct {
|
for i, tt := range []struct {
|
||||||
udcc initialize.CloudConfig
|
udcc initialize.CloudConfig
|
||||||
@ -36,6 +37,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "meta-hostname",
|
Hostname: "meta-hostname",
|
||||||
NetworkConfigPath: "/path/meta",
|
NetworkConfigPath: "/path/meta",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
simplecc,
|
simplecc,
|
||||||
},
|
},
|
||||||
@ -45,6 +47,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
Hostname: "user-hostname",
|
Hostname: "user-hostname",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"woof", "qux"},
|
SSHAuthorizedKeys: []string{"woof", "qux"},
|
||||||
@ -54,6 +57,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
||||||
Hostname: "user-hostname",
|
Hostname: "user-hostname",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -64,11 +68,13 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
NetworkConfigPath: "/dev/fun",
|
NetworkConfigPath: "/dev/fun",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "supercool",
|
Hostname: "supercool",
|
||||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
NetworkConfigPath: "/dev/fun",
|
NetworkConfigPath: "/dev/fun",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,11 +86,13 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "youyouyou",
|
Hostname: "youyouyou",
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -95,10 +103,12 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
107
datasource/metadata/digitalocean/metadata.go
Normal file
107
datasource/metadata/digitalocean/metadata.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultAddress = "http://169.254.169.254/"
|
||||||
|
apiVersion = "metadata/v1"
|
||||||
|
userdataUrl = apiVersion + "/user-data"
|
||||||
|
metadataPath = apiVersion + ".json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
Netmask string `json:"netmask"`
|
||||||
|
Cidr int `json:"cidr"`
|
||||||
|
Gateway string `json:"gateway"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Interface struct {
|
||||||
|
IPv4 *Address `json:"ipv4"`
|
||||||
|
IPv6 *Address `json:"ipv6"`
|
||||||
|
MAC string `json:"mac"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Interfaces struct {
|
||||||
|
Public []Interface `json:"public"`
|
||||||
|
Private []Interface `json:"private"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNS struct {
|
||||||
|
Nameservers []string `json:"nameservers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Interfaces Interfaces `json:"interfaces"`
|
||||||
|
PublicKeys []string `json:"public_keys"`
|
||||||
|
DNS DNS `json:"dns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadataService struct {
|
||||||
|
interfaces Interfaces
|
||||||
|
dns DNS
|
||||||
|
metadata.MetadataService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatasource(root string) *metadataService {
|
||||||
|
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *metadataService) FetchMetadata() ([]byte, error) {
|
||||||
|
data, err := ms.FetchData(ms.MetadataUrl())
|
||||||
|
if err != nil || len(data) == 0 {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata Metadata
|
||||||
|
if err := json.Unmarshal(data, &metadata); err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.interfaces = metadata.Interfaces
|
||||||
|
ms.dns = metadata.DNS
|
||||||
|
|
||||||
|
attrs := make(map[string]interface{})
|
||||||
|
if len(metadata.Interfaces.Public) > 0 {
|
||||||
|
if metadata.Interfaces.Public[0].IPv4 != nil {
|
||||||
|
attrs["public-ipv4"] = metadata.Interfaces.Public[0].IPv4.IPAddress
|
||||||
|
}
|
||||||
|
if metadata.Interfaces.Public[0].IPv6 != nil {
|
||||||
|
attrs["public-ipv6"] = metadata.Interfaces.Public[0].IPv6.IPAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(metadata.Interfaces.Private) > 0 {
|
||||||
|
if metadata.Interfaces.Private[0].IPv4 != nil {
|
||||||
|
attrs["local-ipv4"] = metadata.Interfaces.Private[0].IPv4.IPAddress
|
||||||
|
}
|
||||||
|
if metadata.Interfaces.Private[0].IPv6 != nil {
|
||||||
|
attrs["local-ipv6"] = metadata.Interfaces.Private[0].IPv6.IPAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs["hostname"] = metadata.Hostname
|
||||||
|
keys := make(map[string]string)
|
||||||
|
for i, key := range metadata.PublicKeys {
|
||||||
|
keys[strconv.Itoa(i)] = key
|
||||||
|
}
|
||||||
|
attrs["public_keys"] = keys
|
||||||
|
|
||||||
|
return json.Marshal(attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms metadataService) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return json.Marshal(Metadata{
|
||||||
|
Interfaces: ms.interfaces,
|
||||||
|
DNS: ms.dns,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms metadataService) Type() string {
|
||||||
|
return "digitalocean-metadata-service"
|
||||||
|
}
|
99
datasource/metadata/digitalocean/metadata_test.go
Normal file
99
datasource/metadata/digitalocean/metadata_test.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata"
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
|
||||||
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestType(t *testing.T) {
|
||||||
|
want := "digitalocean-metadata-service"
|
||||||
|
if kind := (metadataService{}).Type(); kind != want {
|
||||||
|
t.Fatalf("bad type: want %q, got %q", want, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchMetadata(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
root string
|
||||||
|
metadataPath string
|
||||||
|
resources map[string]string
|
||||||
|
expect []byte
|
||||||
|
clientErr error
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
metadataPath: "v1.json",
|
||||||
|
resources: map[string]string{
|
||||||
|
"/v1.json": "bad",
|
||||||
|
},
|
||||||
|
expectErr: fmt.Errorf("invalid character 'b' looking for beginning of value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
metadataPath: "v1.json",
|
||||||
|
resources: map[string]string{
|
||||||
|
"/v1.json": `{
|
||||||
|
"droplet_id": 1,
|
||||||
|
"user_data": "hello",
|
||||||
|
"vendor_data": "hello",
|
||||||
|
"public_keys": [
|
||||||
|
"publickey1",
|
||||||
|
"publickey2"
|
||||||
|
],
|
||||||
|
"region": "nyc2",
|
||||||
|
"interfaces": {
|
||||||
|
"public": [
|
||||||
|
{
|
||||||
|
"ipv4": {
|
||||||
|
"ip_address": "192.168.1.2",
|
||||||
|
"netmask": "255.255.255.0",
|
||||||
|
"gateway": "192.168.1.1"
|
||||||
|
},
|
||||||
|
"ipv6": {
|
||||||
|
"ip_address": "fe00::",
|
||||||
|
"cidr": 126,
|
||||||
|
"gateway": "fe00::"
|
||||||
|
},
|
||||||
|
"mac": "ab:cd:ef:gh:ij",
|
||||||
|
"type": "public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
expect: []byte(`{"hostname":"","public-ipv4":"192.168.1.2","public-ipv6":"fe00::","public_keys":{"0":"publickey1","1":"publickey2"}}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientErr: pkg.ErrTimeout{fmt.Errorf("test error")},
|
||||||
|
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
service := &metadataService{
|
||||||
|
MetadataService: metadata.MetadataService{
|
||||||
|
Root: tt.root,
|
||||||
|
Client: &test.HttpClient{tt.resources, tt.clientErr},
|
||||||
|
MetadataPath: tt.metadataPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
metadata, err := service.FetchMetadata()
|
||||||
|
if Error(err) != Error(tt.expectErr) {
|
||||||
|
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(metadata, tt.expect) {
|
||||||
|
t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(err error) string {
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
@ -263,6 +263,8 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
switch env.NetconfType() {
|
switch env.NetconfType() {
|
||||||
case "debian":
|
case "debian":
|
||||||
interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig)
|
interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig)
|
||||||
|
case "digitalocean":
|
||||||
|
interfaces, err = network.ProcessDigitalOceanNetconf(cfg.NetworkConfig)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
|
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
||||||
|
log.Println("Processing Debian network config")
|
||||||
lines := formatConfig(config)
|
lines := formatConfig(config)
|
||||||
stanzas, err := parseStanzas(lines)
|
stanzas, err := parseStanzas(lines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -18,7 +20,9 @@ func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
|||||||
interfaces = append(interfaces, s)
|
interfaces = append(interfaces, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Printf("Parsed %d network interfaces\n", len(interfaces))
|
||||||
|
|
||||||
|
log.Println("Processed Debian network config")
|
||||||
return buildInterfaces(interfaces), nil
|
return buildInterfaces(interfaces), nil
|
||||||
}
|
}
|
||||||
|
|
142
network/digitalocean.go
Normal file
142
network/digitalocean.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProcessDigitalOceanNetconf(config string) ([]InterfaceGenerator, error) {
|
||||||
|
log.Println("Processing DigitalOcean network config")
|
||||||
|
if config == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg digitalocean.Metadata
|
||||||
|
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Parsing nameservers")
|
||||||
|
nameservers, err := parseNameservers(cfg.DNS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("Parsed %d nameservers\n", len(nameservers))
|
||||||
|
|
||||||
|
log.Println("Parsing interfaces")
|
||||||
|
generators, err := parseInterfaces(cfg.Interfaces, nameservers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("Parsed %d network interfaces\n", len(generators))
|
||||||
|
|
||||||
|
log.Println("Processed DigitalOcean network config")
|
||||||
|
return generators, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) {
|
||||||
|
nameservers := make([]net.IP, 0, len(cfg.Nameservers))
|
||||||
|
for _, ns := range cfg.Nameservers {
|
||||||
|
if ip := net.ParseIP(ns); ip == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as nameserver IP address", ns)
|
||||||
|
} else {
|
||||||
|
nameservers = append(nameservers, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nameservers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaces(cfg digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) {
|
||||||
|
generators := make([]InterfaceGenerator, 0, len(cfg.Public)+len(cfg.Private))
|
||||||
|
for _, iface := range cfg.Public {
|
||||||
|
if generator, err := parseInterface(iface, nameservers, true); err == nil {
|
||||||
|
generators = append(generators, &physicalInterface{*generator})
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, iface := range cfg.Private {
|
||||||
|
if generator, err := parseInterface(iface, []net.IP{}, false); err == nil {
|
||||||
|
generators = append(generators, &physicalInterface{*generator})
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return generators, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterface(iface digitalocean.Interface, nameservers []net.IP, useRoute bool) (*logicalInterface, error) {
|
||||||
|
routes := make([]route, 0)
|
||||||
|
addresses := make([]net.IPNet, 0)
|
||||||
|
if iface.IPv4 != nil {
|
||||||
|
var ip, mask, gateway net.IP
|
||||||
|
if ip = net.ParseIP(iface.IPv4.IPAddress); ip == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv4 address", iface.IPv4.IPAddress)
|
||||||
|
}
|
||||||
|
if mask = net.ParseIP(iface.IPv4.Netmask); mask == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv4 mask", iface.IPv4.Netmask)
|
||||||
|
}
|
||||||
|
addresses = append(addresses, net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.IPMask(mask),
|
||||||
|
})
|
||||||
|
|
||||||
|
if useRoute {
|
||||||
|
if gateway = net.ParseIP(iface.IPv4.Gateway); gateway == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv4 gateway", iface.IPv4.Gateway)
|
||||||
|
}
|
||||||
|
routes = append(routes, route{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Mask: net.IPMask(net.IPv4zero),
|
||||||
|
},
|
||||||
|
gateway: gateway,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iface.IPv6 != nil {
|
||||||
|
var ip, gateway net.IP
|
||||||
|
if ip = net.ParseIP(iface.IPv6.IPAddress); ip == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv6 address", iface.IPv6.IPAddress)
|
||||||
|
}
|
||||||
|
addresses = append(addresses, net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.CIDRMask(iface.IPv6.Cidr, net.IPv6len*8),
|
||||||
|
})
|
||||||
|
|
||||||
|
if useRoute {
|
||||||
|
if gateway = net.ParseIP(iface.IPv6.Gateway); gateway == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv6 gateway", iface.IPv6.Gateway)
|
||||||
|
}
|
||||||
|
routes = append(routes, route{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv6zero,
|
||||||
|
Mask: net.IPMask(net.IPv6zero),
|
||||||
|
},
|
||||||
|
gateway: gateway,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(iface.MAC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if nameservers == nil {
|
||||||
|
nameservers = []net.IP{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logicalInterface{
|
||||||
|
hwaddr: hwaddr,
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: addresses,
|
||||||
|
nameservers: nameservers,
|
||||||
|
routes: routes,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
367
network/digitalocean_test.go
Normal file
367
network/digitalocean_test.go
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseNameservers(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
dns digitalocean.DNS
|
||||||
|
nss []net.IP
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
dns: digitalocean.DNS{},
|
||||||
|
nss: []net.IP{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dns: digitalocean.DNS{[]string{"1.2.3.4"}},
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dns: digitalocean.DNS{[]string{"bad"}},
|
||||||
|
err: errors.New("could not parse \"bad\" as nameserver IP address"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
nss, err := parseNameservers(tt.dns)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%+v): want %q, got %q", tt.dns, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.nss, nss) {
|
||||||
|
t.Fatalf("bad nameservers (%+v): want %#v, got %#v", tt.dns, tt.nss, nss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterface(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
cfg digitalocean.Interface
|
||||||
|
nss []net.IP
|
||||||
|
useRoute bool
|
||||||
|
iface *logicalInterface
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "bad",
|
||||||
|
},
|
||||||
|
err: errors.New("invalid MAC address: bad"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "bad",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 mask"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
Gateway: "ignoreme",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("1.2.3.4"), net.IPMask(net.ParseIP("255.255.0.0"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
Gateway: "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 gateway"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
Gateway: "5.6.7.8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("1.2.3.4"), net.IPMask(net.ParseIP("255.255.0.0"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{route{net.IPNet{net.IPv4zero, net.IPMask(net.IPv4zero)}, net.ParseIP("5.6.7.8")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "bad",
|
||||||
|
Cidr: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv6 address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "fe00::",
|
||||||
|
Cidr: 16,
|
||||||
|
Gateway: "ignoreme",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("fe00::"), net.IPMask(net.ParseIP("ffff::"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "fe00::",
|
||||||
|
Cidr: 16,
|
||||||
|
Gateway: "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv6 gateway"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "fe00::",
|
||||||
|
Cidr: 16,
|
||||||
|
Gateway: "fe00:1234::",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("fe00::"), net.IPMask(net.ParseIP("ffff::"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{route{net.IPNet{net.IPv6zero, net.IPMask(net.IPv6zero)}, net.ParseIP("fe00:1234::")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
iface, err := parseInterface(tt.cfg, tt.nss, tt.useRoute)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%+v): want %q, got %q", tt.cfg, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.iface, iface) {
|
||||||
|
t.Fatalf("bad interface (%+v): want %#v, got %#v", tt.cfg, tt.iface, iface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaces(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
cfg digitalocean.Interfaces
|
||||||
|
nss []net.IP
|
||||||
|
ifaces []InterfaceGenerator
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ifaces: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Public: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Private: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Public: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Private: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Public: []digitalocean.Interface{{MAC: "bad"}},
|
||||||
|
},
|
||||||
|
err: errors.New("invalid MAC address: bad"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Private: []digitalocean.Interface{{MAC: "bad"}},
|
||||||
|
},
|
||||||
|
err: errors.New("invalid MAC address: bad"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
ifaces, err := parseInterfaces(tt.cfg, tt.nss)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%+v): want %q, got %q", tt.cfg, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.ifaces, ifaces) {
|
||||||
|
t.Fatalf("bad interfaces (%+v): want %#v, got %#v", tt.cfg, tt.ifaces, ifaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessDigitalOceanNetconf(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
cfg string
|
||||||
|
ifaces []InterfaceGenerator
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cfg: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `{"dns":{"nameservers":["bad"]}}`,
|
||||||
|
err: errors.New("could not parse \"bad\" as nameserver IP address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `{"interfaces":{"public":[{"ipv4":{"ip_address":"bad"}}]}}`,
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `{}`,
|
||||||
|
ifaces: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
ifaces, err := ProcessDigitalOceanNetconf(tt.cfg)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.ifaces, ifaces) {
|
||||||
|
t.Fatalf("bad interfaces (%q): want %#v, got %#v", tt.cfg, tt.ifaces, ifaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorsEqual(a, b error) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (a != nil && b == nil) || (a == nil && b != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (a.Error() == b.Error())
|
||||||
|
}
|
@ -82,7 +82,11 @@ func (i *logicalInterface) Netdev() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) Filename() string {
|
func (i *logicalInterface) Filename() string {
|
||||||
return fmt.Sprintf("%02x-%s", i.configDepth, i.name)
|
name := i.name
|
||||||
|
if name == "" {
|
||||||
|
name = i.hwaddr.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%02x-%s", i.configDepth, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) Children() []networkInterface {
|
func (i *logicalInterface) Children() []networkInterface {
|
||||||
|
@ -344,6 +344,8 @@ func TestFilename(t *testing.T) {
|
|||||||
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
|
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
|
||||||
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
|
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
|
||||||
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
|
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
|
||||||
|
{logicalInterface{hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-01:23:45:67:89:ab"},
|
||||||
|
{logicalInterface{name: "iface", hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-iface"},
|
||||||
} {
|
} {
|
||||||
if tt.i.Filename() != tt.f {
|
if tt.i.Filename() != tt.f {
|
||||||
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
|
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
|
||||||
|
1
test
1
test
@ -20,6 +20,7 @@ declare -a TESTPKGS=(initialize
|
|||||||
datasource/file
|
datasource/file
|
||||||
datasource/metadata
|
datasource/metadata
|
||||||
datasource/metadata/cloudsigma
|
datasource/metadata/cloudsigma
|
||||||
|
datasource/metadata/digitalocean
|
||||||
datasource/metadata/ec2
|
datasource/metadata/ec2
|
||||||
datasource/proc_cmdline
|
datasource/proc_cmdline
|
||||||
datasource/url
|
datasource/url
|
||||||
|
Loading…
Reference in New Issue
Block a user