Merge pull request #304 from crawford/metadata

datasource: improve metadata handling
This commit is contained in:
Alex Crawford 2015-01-27 14:36:12 -08:00
commit 4eaaa5c927
30 changed files with 445 additions and 681 deletions

View File

@ -33,8 +33,6 @@ type CloudConfig struct {
Hostname string `yaml:"hostname"` Hostname string `yaml:"hostname"`
Users []User `yaml:"users"` Users []User `yaml:"users"`
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"` ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
NetworkConfigPath string `yaml:"-"`
NetworkConfig string `yaml:"-"`
} }
type CoreOS struct { type CoreOS struct {

View File

@ -25,6 +25,7 @@ func IsScript(userdata string) bool {
return strings.HasPrefix(header, "#!") return strings.HasPrefix(header, "#!")
} }
func NewScript(userdata string) (Script, error) { func NewScript(userdata string) (*Script, error) {
return Script(userdata), nil s := Script(userdata)
return &s, nil
} }

View File

@ -33,6 +33,7 @@ import (
"github.com/coreos/coreos-cloudinit/datasource/url" "github.com/coreos/coreos-cloudinit/datasource/url"
"github.com/coreos/coreos-cloudinit/datasource/waagent" "github.com/coreos/coreos-cloudinit/datasource/waagent"
"github.com/coreos/coreos-cloudinit/initialize" "github.com/coreos/coreos-cloudinit/initialize"
"github.com/coreos/coreos-cloudinit/network"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
@ -176,43 +177,18 @@ func main() {
} }
fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type()) fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
metadataBytes, err := ds.FetchMetadata() metadata, err := ds.FetchMetadata()
if err != nil { if err != nil {
fmt.Printf("Failed fetching meta-data from datasource: %v\n", err) fmt.Printf("Failed fetching meta-data from datasource: %v\n", err)
os.Exit(1) os.Exit(1)
} }
// Extract IPv4 addresses from metadata if possible
var subs map[string]string
if len(metadataBytes) > 0 {
subs, err = initialize.ExtractIPsFromMetadata(metadataBytes)
if err != nil {
fmt.Printf("Failed extracting IPs from meta-data: %v\n", err)
os.Exit(1)
}
}
// Apply environment to user-data // Apply environment to user-data
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.convertNetconf, flags.sshKeyName, subs) env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, metadata)
userdata := env.Apply(string(userdataBytes)) userdata := env.Apply(string(userdataBytes))
var ccm, ccu *config.CloudConfig var ccu *config.CloudConfig
var script *config.Script var script *config.Script
if ccm, err = initialize.ParseMetaData(string(metadataBytes)); err != nil {
fmt.Printf("Failed to parse meta-data: %v\n", err)
os.Exit(1)
}
if ccm != nil && flags.convertNetconf != "" {
fmt.Printf("Fetching network config from datasource of type %q\n", ds.Type())
netconfBytes, err := ds.FetchNetworkConfig(ccm.NetworkConfigPath)
if err != nil {
fmt.Printf("Failed fetching network config from datasource: %v\n", err)
os.Exit(1)
}
ccm.NetworkConfig = string(netconfBytes)
}
if ud, err := initialize.ParseUserData(userdata); err != nil { if ud, err := initialize.ParseUserData(userdata); err != nil {
fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err) fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err)
failure = true failure = true
@ -220,33 +196,36 @@ func main() {
switch t := ud.(type) { switch t := ud.(type) {
case *config.CloudConfig: case *config.CloudConfig:
ccu = t ccu = t
case config.Script: case *config.Script:
script = &t script = t
} }
} }
var cc *config.CloudConfig
if ccm != nil && ccu != nil {
fmt.Println("Merging cloud-config from meta-data and user-data") fmt.Println("Merging cloud-config from meta-data and user-data")
merged := mergeCloudConfig(*ccm, *ccu) cc := mergeConfigs(ccu, metadata)
cc = &merged
} else if ccm != nil && ccu == nil {
fmt.Println("Processing cloud-config from meta-data")
cc = ccm
} else if ccm == nil && ccu != nil {
fmt.Println("Processing cloud-config from user-data")
cc = ccu
} else {
fmt.Println("No cloud-config data to handle.")
}
if cc != nil { var ifaces []network.InterfaceGenerator
if err = initialize.Apply(*cc, env); err != nil { if flags.convertNetconf != "" {
fmt.Printf("Failed to apply cloud-config: %v\n", err) var err error
switch flags.convertNetconf {
case "debian":
ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig)
case "digitalocean":
ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig)
default:
err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf)
}
if err != nil {
fmt.Printf("Failed to generate interfaces: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} }
if err = initialize.Apply(cc, ifaces, env); err != nil {
fmt.Printf("Failed to apply cloud-config: %v\n", err)
os.Exit(1)
}
if script != nil { if script != nil {
if err = runScript(*script, env); err != nil { if err = runScript(*script, env); err != nil {
fmt.Printf("Failed to run script: %v\n", err) fmt.Printf("Failed to run script: %v\n", err)
@ -259,38 +238,25 @@ func main() {
} }
} }
// mergeCloudConfig merges certain options from mdcc (a CloudConfig derived from // mergeConfigs merges certain options from md (meta-data from the datasource)
// meta-data) onto udcc (a CloudConfig derived from user-data), if they are // onto cc (a CloudConfig derived from user-data), if they are not already set
// not already set on udcc (i.e. user-data always takes precedence) // on cc (i.e. user-data always takes precedence)
// NB: This needs to be kept in sync with ParseMetadata so that it tracks all func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.CloudConfig) {
// elements of a CloudConfig which that function can populate. if cc != nil {
func mergeCloudConfig(mdcc, udcc config.CloudConfig) (cc config.CloudConfig) { out = *cc
if mdcc.Hostname != "" {
if udcc.Hostname != "" {
fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", udcc.Hostname, mdcc.Hostname)
} else {
udcc.Hostname = mdcc.Hostname
} }
} if md.Hostname != "" {
for _, key := range mdcc.SSHAuthorizedKeys { if out.Hostname != "" {
udcc.SSHAuthorizedKeys = append(udcc.SSHAuthorizedKeys, key) fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname)
}
if mdcc.NetworkConfigPath != "" {
if udcc.NetworkConfigPath != "" {
fmt.Printf("Warning: user-data NetworkConfigPath %s overrides metadata NetworkConfigPath %s\n", udcc.NetworkConfigPath, mdcc.NetworkConfigPath)
} else { } else {
udcc.NetworkConfigPath = mdcc.NetworkConfigPath out.Hostname = md.Hostname
} }
} }
if mdcc.NetworkConfig != "" { for _, key := range md.SSHPublicKeys {
if udcc.NetworkConfig != "" { out.SSHAuthorizedKeys = append(out.SSHAuthorizedKeys, key)
fmt.Printf("Warning: user-data NetworkConfig %s overrides metadata NetworkConfig %s\n", udcc.NetworkConfig, mdcc.NetworkConfig)
} else {
udcc.NetworkConfig = mdcc.NetworkConfig
} }
} return
return udcc
} }
// getDatasources creates a slice of possible Datasources for cloudinit based // getDatasources creates a slice of possible Datasources for cloudinit based

View File

@ -19,116 +19,71 @@ import (
"testing" "testing"
"github.com/coreos/coreos-cloudinit/config" "github.com/coreos/coreos-cloudinit/config"
"github.com/coreos/coreos-cloudinit/datasource"
) )
func TestMergeCloudConfig(t *testing.T) { func TestMergeConfigs(t *testing.T) {
simplecc := config.CloudConfig{ tests := []struct {
SSHAuthorizedKeys: []string{"abc", "def"}, cc *config.CloudConfig
Hostname: "foobar", md datasource.Metadata
NetworkConfigPath: "/path/somewhere",
NetworkConfig: `{}`, out config.CloudConfig
}
for i, tt := range []struct {
udcc config.CloudConfig
mdcc config.CloudConfig
want config.CloudConfig
}{ }{
{ {
// If mdcc is empty, udcc should be returned unchanged // If md is empty and cc is nil, result should be empty
simplecc, out: config.CloudConfig{},
config.CloudConfig{},
simplecc,
}, },
{ {
// If udcc is empty, mdcc should be returned unchanged(overridden) // If md and cc are empty, result should be empty
config.CloudConfig{}, cc: &config.CloudConfig{},
simplecc, out: config.CloudConfig{},
simplecc, },
{
// If cc is empty, cc should be returned unchanged
cc: &config.CloudConfig{SSHAuthorizedKeys: []string{"abc", "def"}, Hostname: "cc-host"},
out: config.CloudConfig{SSHAuthorizedKeys: []string{"abc", "def"}, Hostname: "cc-host"},
},
{
// If cc is empty, cc should be returned unchanged(overridden)
cc: &config.CloudConfig{},
md: datasource.Metadata{Hostname: "md-host", SSHPublicKeys: map[string]string{"key": "ghi"}},
out: config.CloudConfig{SSHAuthorizedKeys: []string{"ghi"}, Hostname: "md-host"},
},
{
// If cc is nil, cc should be returned unchanged(overridden)
md: datasource.Metadata{Hostname: "md-host", SSHPublicKeys: map[string]string{"key": "ghi"}},
out: config.CloudConfig{SSHAuthorizedKeys: []string{"ghi"}, Hostname: "md-host"},
}, },
{ {
// user-data should override completely in the case of conflicts // user-data should override completely in the case of conflicts
simplecc, cc: &config.CloudConfig{SSHAuthorizedKeys: []string{"abc", "def"}, Hostname: "cc-host"},
config.CloudConfig{ md: datasource.Metadata{Hostname: "md-host"},
Hostname: "meta-hostname", out: config.CloudConfig{SSHAuthorizedKeys: []string{"abc", "def"}, Hostname: "cc-host"},
NetworkConfigPath: "/path/meta",
NetworkConfig: `{"hostname":"test"}`,
},
simplecc,
}, },
{ {
// Mixed merge should succeed // Mixed merge should succeed
config.CloudConfig{ cc: &config.CloudConfig{SSHAuthorizedKeys: []string{"abc", "def"}, Hostname: "cc-host"},
SSHAuthorizedKeys: []string{"abc", "def"}, md: datasource.Metadata{Hostname: "md-host", SSHPublicKeys: map[string]string{"key": "ghi"}},
Hostname: "user-hostname", out: config.CloudConfig{SSHAuthorizedKeys: []string{"abc", "def", "ghi"}, Hostname: "cc-host"},
NetworkConfigPath: "/path/somewhere",
NetworkConfig: `{"hostname":"test"}`,
},
config.CloudConfig{
SSHAuthorizedKeys: []string{"woof", "qux"},
Hostname: "meta-hostname",
},
config.CloudConfig{
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
Hostname: "user-hostname",
NetworkConfigPath: "/path/somewhere",
NetworkConfig: `{"hostname":"test"}`,
},
}, },
{ {
// Completely non-conflicting merge should be fine // Completely non-conflicting merge should be fine
config.CloudConfig{ cc: &config.CloudConfig{Hostname: "cc-host"},
Hostname: "supercool", md: datasource.Metadata{SSHPublicKeys: map[string]string{"zaphod": "beeblebrox"}},
}, out: config.CloudConfig{Hostname: "cc-host", SSHAuthorizedKeys: []string{"beeblebrox"}},
config.CloudConfig{
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
NetworkConfigPath: "/dev/fun",
NetworkConfig: `{"hostname":"test"}`,
},
config.CloudConfig{
Hostname: "supercool",
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
NetworkConfigPath: "/dev/fun",
NetworkConfig: `{"hostname":"test"}`,
},
}, },
{ {
// Non-mergeable settings in user-data should not be affected // Non-mergeable settings in user-data should not be affected
config.CloudConfig{ cc: &config.CloudConfig{Hostname: "cc-host", ManageEtcHosts: config.EtcHosts("lolz")},
Hostname: "mememe", md: datasource.Metadata{Hostname: "md-host"},
ManageEtcHosts: config.EtcHosts("lolz"), out: config.CloudConfig{Hostname: "cc-host", ManageEtcHosts: config.EtcHosts("lolz")},
}, },
config.CloudConfig{ }
Hostname: "youyouyou",
NetworkConfigPath: "meta-meta-yo", for i, tt := range tests {
NetworkConfig: `{"hostname":"test"}`, out := mergeConfigs(tt.cc, tt.md)
}, if !reflect.DeepEqual(tt.out, out) {
config.CloudConfig{ t.Errorf("bad config (%d): want %#v, got %#v", i, tt.out, out)
Hostname: "mememe",
ManageEtcHosts: config.EtcHosts("lolz"),
NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
},
},
{
// Non-mergeable (unexpected) settings in meta-data are ignored
config.CloudConfig{
Hostname: "mememe",
},
config.CloudConfig{
ManageEtcHosts: config.EtcHosts("lolz"),
NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
},
config.CloudConfig{
Hostname: "mememe",
NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
},
},
} {
got := mergeCloudConfig(tt.mdcc, tt.udcc)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("case #%d: mergeCloudConfig mutated CloudConfig unexpectedly:\ngot:\n%s\nwant:\n%s", i, got, tt.want)
} }
} }
} }

View File

@ -15,10 +15,13 @@
package configdrive package configdrive
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"github.com/coreos/coreos-cloudinit/datasource"
) )
const ( const (
@ -47,21 +50,34 @@ func (cd *configDrive) ConfigRoot() string {
return cd.openstackRoot() return cd.openstackRoot()
} }
func (cd *configDrive) FetchMetadata() ([]byte, error) { func (cd *configDrive) FetchMetadata() (metadata datasource.Metadata, err error) {
return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "meta_data.json")) var data []byte
var m struct {
SSHAuthorizedKeyMap map[string]string `json:"public_keys"`
Hostname string `json:"hostname"`
NetworkConfig struct {
ContentPath string `json:"content_path"`
} `json:"network_config"`
}
if data, err = cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "meta_data.json")); err != nil {
return
}
if err = json.Unmarshal([]byte(data), &m); err != nil {
return
}
metadata.SSHPublicKeys = m.SSHAuthorizedKeyMap
metadata.Hostname = m.Hostname
metadata.NetworkConfig, err = cd.tryReadFile(path.Join(cd.openstackRoot(), m.NetworkConfig.ContentPath))
return
} }
func (cd *configDrive) FetchUserdata() ([]byte, error) { func (cd *configDrive) FetchUserdata() ([]byte, error) {
return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "user_data")) return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "user_data"))
} }
func (cd *configDrive) FetchNetworkConfig(filename string) ([]byte, error) {
if filename == "" {
return []byte{}, nil
}
return cd.tryReadFile(path.Join(cd.openstackRoot(), filename))
}
func (cd *configDrive) Type() string { func (cd *configDrive) Type() string {
return "cloud-drive" return "cloud-drive"
} }

View File

@ -15,50 +15,52 @@
package configdrive package configdrive
import ( import (
"os" "reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/test"
) )
type mockFilesystem []string
func (m mockFilesystem) readFile(filename string) ([]byte, error) {
for _, file := range m {
if file == filename {
return []byte(filename), nil
}
}
return nil, os.ErrNotExist
}
func TestFetchMetadata(t *testing.T) { func TestFetchMetadata(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
root string root string
filename string files test.MockFilesystem
files mockFilesystem
metadata datasource.Metadata
}{ }{
{ {
"/", root: "/",
"", files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"ignore": "me"}`},
mockFilesystem{},
}, },
{ {
"/", root: "/",
"/openstack/latest/meta_data.json", files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"hostname": "host"}`},
mockFilesystem([]string{"/openstack/latest/meta_data.json"}), metadata: datasource.Metadata{Hostname: "host"},
}, },
{ {
"/media/configdrive", root: "/media/configdrive",
"/media/configdrive/openstack/latest/meta_data.json", files: test.MockFilesystem{
mockFilesystem([]string{"/media/configdrive/openstack/latest/meta_data.json"}), "/media/configdrive/openstack/latest/meta_data.json": `{"hostname": "host", "network_config": {"content_path": "config_file.json"}, "public_keys":{"1": "key1", "2": "key2"}}`,
"/media/configdrive/openstack/config_file.json": "make it work",
},
metadata: datasource.Metadata{
Hostname: "host",
NetworkConfig: []byte("make it work"),
SSHPublicKeys: map[string]string{
"1": "key1",
"2": "key2",
},
},
}, },
} { } {
cd := configDrive{tt.root, tt.files.readFile} cd := configDrive{tt.root, tt.files.ReadFile}
filename, err := cd.FetchMetadata() metadata, err := cd.FetchMetadata()
if err != nil { if err != nil {
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err) t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
} }
if string(filename) != tt.filename { if !reflect.DeepEqual(tt.metadata, metadata) {
t.Fatalf("bad path for %q: want %q, got %q", tt, tt.filename, filename) t.Fatalf("bad metadata for %q: want %#v, got %#v", tt, tt.metadata, metadata)
} }
} }
} }
@ -66,32 +68,33 @@ func TestFetchMetadata(t *testing.T) {
func TestFetchUserdata(t *testing.T) { func TestFetchUserdata(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
root string root string
filename string files test.MockFilesystem
files mockFilesystem
userdata string
}{ }{
{ {
"/", "/",
test.MockFilesystem{},
"", "",
mockFilesystem{},
}, },
{ {
"/", "/",
"/openstack/latest/user_data", test.MockFilesystem{"/openstack/latest/user_data": "userdata"},
mockFilesystem([]string{"/openstack/latest/user_data"}), "userdata",
}, },
{ {
"/media/configdrive", "/media/configdrive",
"/media/configdrive/openstack/latest/user_data", test.MockFilesystem{"/media/configdrive/openstack/latest/user_data": "userdata"},
mockFilesystem([]string{"/media/configdrive/openstack/latest/user_data"}), "userdata",
}, },
} { } {
cd := configDrive{tt.root, tt.files.readFile} cd := configDrive{tt.root, tt.files.ReadFile}
filename, err := cd.FetchUserdata() userdata, err := cd.FetchUserdata()
if err != nil { if err != nil {
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err) t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
} }
if string(filename) != tt.filename { if string(userdata) != tt.userdata {
t.Fatalf("bad path for %q: want %q, got %q", tt, tt.filename, filename) t.Fatalf("bad userdata for %q: want %q, got %q", tt, tt.userdata, userdata)
} }
} }
} }

View File

@ -14,12 +14,25 @@
package datasource package datasource
import (
"net"
)
type Datasource interface { type Datasource interface {
IsAvailable() bool IsAvailable() bool
AvailabilityChanges() bool AvailabilityChanges() bool
ConfigRoot() string ConfigRoot() string
FetchMetadata() ([]byte, error) FetchMetadata() (Metadata, error)
FetchUserdata() ([]byte, error) FetchUserdata() ([]byte, error)
FetchNetworkConfig(string) ([]byte, error)
Type() string Type() string
} }
type Metadata struct {
PublicIPv4 net.IP
PublicIPv6 net.IP
PrivateIPv4 net.IP
PrivateIPv6 net.IP
Hostname string
SSHPublicKeys map[string]string
NetworkConfig []byte
}

View File

@ -17,6 +17,8 @@ package file
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/coreos/coreos-cloudinit/datasource"
) )
type localFile struct { type localFile struct {
@ -40,18 +42,14 @@ func (f *localFile) ConfigRoot() string {
return "" return ""
} }
func (f *localFile) FetchMetadata() ([]byte, error) { func (f *localFile) FetchMetadata() (datasource.Metadata, error) {
return []byte{}, nil return datasource.Metadata{}, nil
} }
func (f *localFile) FetchUserdata() ([]byte, error) { func (f *localFile) FetchUserdata() ([]byte, error) {
return ioutil.ReadFile(f.path) return ioutil.ReadFile(f.path)
} }
func (f *localFile) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (f *localFile) Type() string { func (f *localFile) Type() string {
return "local-file" return "local-file"
} }

View File

@ -24,6 +24,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/cloudsigma/cepgo" "github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/cloudsigma/cepgo"
) )
@ -69,7 +71,7 @@ func (_ *serverContextService) Type() string {
return "server-context" return "server-context"
} }
func (scs *serverContextService) FetchMetadata() ([]byte, error) { func (scs *serverContextService) FetchMetadata() (metadata datasource.Metadata, err error) {
var ( var (
inputMetadata struct { inputMetadata struct {
Name string `json:"name"` Name string `json:"name"`
@ -88,48 +90,41 @@ func (scs *serverContextService) FetchMetadata() ([]byte, error) {
} `json:"vlan"` } `json:"vlan"`
} `json:"nics"` } `json:"nics"`
} }
outputMetadata struct { rawMetadata []byte
Hostname string `json:"name"`
PublicKeys map[string]string `json:"public_keys"`
LocalIPv4 string `json:"local-ipv4"`
PublicIPv4 string `json:"public-ipv4"`
}
) )
rawMetadata, err := scs.client.FetchRaw("") if rawMetadata, err = scs.client.FetchRaw(""); err != nil {
if err != nil { return
return []byte{}, err
} }
err = json.Unmarshal(rawMetadata, &inputMetadata) if err = json.Unmarshal(rawMetadata, &inputMetadata); err != nil {
if err != nil { return
return []byte{}, err
} }
if inputMetadata.Name != "" { if inputMetadata.Name != "" {
outputMetadata.Hostname = inputMetadata.Name metadata.Hostname = inputMetadata.Name
} else { } else {
outputMetadata.Hostname = inputMetadata.UUID metadata.Hostname = inputMetadata.UUID
} }
metadata.SSHPublicKeys = map[string]string{}
if key, ok := inputMetadata.Meta["ssh_public_key"]; ok { if key, ok := inputMetadata.Meta["ssh_public_key"]; ok {
splitted := strings.Split(key, " ") splitted := strings.Split(key, " ")
outputMetadata.PublicKeys = make(map[string]string) metadata.SSHPublicKeys[splitted[len(splitted)-1]] = key
outputMetadata.PublicKeys[splitted[len(splitted)-1]] = key
} }
for _, nic := range inputMetadata.Nics { for _, nic := range inputMetadata.Nics {
if nic.IPv4Conf.IP.UUID != "" { if nic.IPv4Conf.IP.UUID != "" {
outputMetadata.PublicIPv4 = nic.IPv4Conf.IP.UUID metadata.PublicIPv4 = net.ParseIP(nic.IPv4Conf.IP.UUID)
} }
if nic.VLAN.UUID != "" { if nic.VLAN.UUID != "" {
if localIP, err := scs.findLocalIP(nic.Mac); err == nil { if localIP, err := scs.findLocalIP(nic.Mac); err == nil {
outputMetadata.LocalIPv4 = localIP metadata.PrivateIPv4 = localIP
} }
} }
} }
return json.Marshal(outputMetadata) return
} }
func (scs *serverContextService) FetchUserdata() ([]byte, error) { func (scs *serverContextService) FetchUserdata() ([]byte, error) {
@ -150,18 +145,14 @@ func (scs *serverContextService) FetchUserdata() ([]byte, error) {
return []byte(userData), nil return []byte(userData), nil
} }
func (scs *serverContextService) FetchNetworkConfig(a string) ([]byte, error) { func (scs *serverContextService) findLocalIP(mac string) (net.IP, error) {
return nil, nil
}
func (scs *serverContextService) findLocalIP(mac string) (string, error) {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return "", err return nil, err
} }
ifaceMac, err := net.ParseMAC(mac) ifaceMac, err := net.ParseMAC(mac)
if err != nil { if err != nil {
return "", err return nil, err
} }
for _, iface := range ifaces { for _, iface := range ifaces {
if !bytes.Equal(iface.HardwareAddr, ifaceMac) { if !bytes.Equal(iface.HardwareAddr, ifaceMac) {
@ -176,12 +167,12 @@ func (scs *serverContextService) findLocalIP(mac string) (string, error) {
switch ip := addr.(type) { switch ip := addr.(type) {
case *net.IPNet: case *net.IPNet:
if ip.IP.To4() != nil { if ip.IP.To4() != nil {
return ip.IP.To4().String(), nil return ip.IP.To4(), nil
} }
} }
} }
} }
return "", errors.New("Local IP not found") return nil, errors.New("Local IP not found")
} }
func isBase64Encoded(field string, userdata map[string]string) bool { func isBase64Encoded(field string, userdata map[string]string) bool {

View File

@ -15,7 +15,7 @@
package cloudsigma package cloudsigma
import ( import (
"encoding/json" "net"
"reflect" "reflect"
"testing" "testing"
) )
@ -44,12 +44,6 @@ func (f *fakeCepgoClient) FetchRaw(key string) ([]byte, error) {
} }
func TestServerContextFetchMetadata(t *testing.T) { func TestServerContextFetchMetadata(t *testing.T) {
var metadata struct {
Hostname string `json:"name"`
PublicKeys map[string]string `json:"public_keys"`
LocalIPv4 string `json:"local-ipv4"`
PublicIPv4 string `json:"public-ipv4"`
}
client := new(fakeCepgoClient) client := new(fakeCepgoClient)
scs := NewServerContextService() scs := NewServerContextService()
scs.client = client scs.client = client
@ -114,24 +108,20 @@ func TestServerContextFetchMetadata(t *testing.T) {
"uuid": "20a0059b-041e-4d0c-bcc6-9b2852de48b3" "uuid": "20a0059b-041e-4d0c-bcc6-9b2852de48b3"
}`) }`)
metadataBytes, err := scs.FetchMetadata() metadata, err := scs.FetchMetadata()
if err != nil { if err != nil {
t.Error(err.Error()) t.Error(err.Error())
} }
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
t.Error(err.Error())
}
if metadata.Hostname != "coreos" { if metadata.Hostname != "coreos" {
t.Errorf("Hostname is not 'coreos' but %s instead", metadata.Hostname) t.Errorf("Hostname is not 'coreos' but %s instead", metadata.Hostname)
} }
if metadata.PublicKeys["john@doe"] != "ssh-rsa AAAAB3NzaC1yc2E.../hQ5D5 john@doe" { if metadata.SSHPublicKeys["john@doe"] != "ssh-rsa AAAAB3NzaC1yc2E.../hQ5D5 john@doe" {
t.Error("Public SSH Keys are not being read properly") t.Error("Public SSH Keys are not being read properly")
} }
if metadata.PublicIPv4 != "31.171.251.74" { if !metadata.PublicIPv4.Equal(net.ParseIP("31.171.251.74")) {
t.Errorf("Public IP is not 31.171.251.74 but %s instead", metadata.PublicIPv4) t.Errorf("Public IP is not 31.171.251.74 but %s instead", metadata.PublicIPv4)
} }
} }

View File

@ -16,8 +16,10 @@ package digitalocean
import ( import (
"encoding/json" "encoding/json"
"net"
"strconv" "strconv"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/datasource/metadata"
) )
@ -59,8 +61,6 @@ type Metadata struct {
} }
type metadataService struct { type metadataService struct {
interfaces Interfaces
dns DNS
metadata.MetadataService metadata.MetadataService
} }
@ -68,52 +68,41 @@ func NewDatasource(root string) *metadataService {
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)} return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
} }
func (ms *metadataService) FetchMetadata() ([]byte, error) { func (ms *metadataService) FetchMetadata() (metadata datasource.Metadata, err error) {
data, err := ms.FetchData(ms.MetadataUrl()) var data []byte
if err != nil || len(data) == 0 { var m Metadata
return []byte{}, err
if data, err = ms.FetchData(ms.MetadataUrl()); err != nil || len(data) == 0 {
return
}
if err = json.Unmarshal(data, &m); err != nil {
return
} }
var metadata Metadata if len(m.Interfaces.Public) > 0 {
if err := json.Unmarshal(data, &metadata); err != nil { if m.Interfaces.Public[0].IPv4 != nil {
return []byte{}, err metadata.PublicIPv4 = net.ParseIP(m.Interfaces.Public[0].IPv4.IPAddress)
} }
if m.Interfaces.Public[0].IPv6 != nil {
metadata.PublicIPv6 = net.ParseIP(m.Interfaces.Public[0].IPv6.IPAddress)
}
}
if len(m.Interfaces.Private) > 0 {
if m.Interfaces.Private[0].IPv4 != nil {
metadata.PrivateIPv4 = net.ParseIP(m.Interfaces.Private[0].IPv4.IPAddress)
}
if m.Interfaces.Private[0].IPv6 != nil {
metadata.PrivateIPv6 = net.ParseIP(m.Interfaces.Private[0].IPv6.IPAddress)
}
}
metadata.Hostname = m.Hostname
metadata.SSHPublicKeys = map[string]string{}
for i, key := range m.PublicKeys {
metadata.SSHPublicKeys[strconv.Itoa(i)] = key
}
metadata.NetworkConfig = data
ms.interfaces = metadata.Interfaces return
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 { func (ms metadataService) Type() string {

View File

@ -15,10 +15,12 @@
package digitalocean package digitalocean
import ( import (
"bytes"
"fmt" "fmt"
"net"
"reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/datasource/metadata/test" "github.com/coreos/coreos-cloudinit/datasource/metadata/test"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
@ -36,7 +38,7 @@ func TestFetchMetadata(t *testing.T) {
root string root string
metadataPath string metadataPath string
resources map[string]string resources map[string]string
expect []byte expect datasource.Metadata
clientErr error clientErr error
expectErr error expectErr error
}{ }{
@ -81,7 +83,42 @@ func TestFetchMetadata(t *testing.T) {
} }
}`, }`,
}, },
expect: []byte(`{"hostname":"","public-ipv4":"192.168.1.2","public-ipv6":"fe00::","public_keys":{"0":"publickey1","1":"publickey2"}}`), expect: datasource.Metadata{
PublicIPv4: net.ParseIP("192.168.1.2"),
PublicIPv6: net.ParseIP("fe00::"),
SSHPublicKeys: map[string]string{
"0": "publickey1",
"1": "publickey2",
},
NetworkConfig: []byte(`{
"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"
}
]
}
}`),
},
}, },
{ {
clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")}, clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")},
@ -99,8 +136,8 @@ func TestFetchMetadata(t *testing.T) {
if Error(err) != Error(tt.expectErr) { if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err) t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
} }
if !bytes.Equal(metadata, tt.expect) { if !reflect.DeepEqual(tt.expect, metadata) {
t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata) t.Fatalf("bad fetch (%q): want %#q, got %#q", tt.resources, tt.expect, metadata)
} }
} }
} }

View File

@ -17,10 +17,11 @@ package ec2
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"net"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
) )
@ -40,59 +41,51 @@ func NewDatasource(root string) *metadataService {
return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)} return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)}
} }
func (ms metadataService) FetchMetadata() ([]byte, error) { func (ms metadataService) FetchMetadata() (datasource.Metadata, error) {
attrs := make(map[string]interface{}) metadata := datasource.Metadata{}
if keynames, err := ms.fetchAttributes(fmt.Sprintf("%s/public-keys", ms.MetadataUrl())); err == nil { if keynames, err := ms.fetchAttributes(fmt.Sprintf("%s/public-keys", ms.MetadataUrl())); err == nil {
keyIDs := make(map[string]string) keyIDs := make(map[string]string)
for _, keyname := range keynames { for _, keyname := range keynames {
tokens := strings.SplitN(keyname, "=", 2) tokens := strings.SplitN(keyname, "=", 2)
if len(tokens) != 2 { if len(tokens) != 2 {
return nil, fmt.Errorf("malformed public key: %q", keyname) return metadata, fmt.Errorf("malformed public key: %q", keyname)
} }
keyIDs[tokens[1]] = tokens[0] keyIDs[tokens[1]] = tokens[0]
} }
keys := make(map[string]string) metadata.SSHPublicKeys = map[string]string{}
for name, id := range keyIDs { for name, id := range keyIDs {
sshkey, err := ms.fetchAttribute(fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.MetadataUrl(), id)) sshkey, err := ms.fetchAttribute(fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.MetadataUrl(), id))
if err != nil { if err != nil {
return nil, err return metadata, err
} }
keys[name] = sshkey metadata.SSHPublicKeys[name] = sshkey
fmt.Printf("Found SSH key for %q\n", name) fmt.Printf("Found SSH key for %q\n", name)
} }
attrs["public_keys"] = keys
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return metadata, err
} }
if hostname, err := ms.fetchAttribute(fmt.Sprintf("%s/hostname", ms.MetadataUrl())); err == nil { if hostname, err := ms.fetchAttribute(fmt.Sprintf("%s/hostname", ms.MetadataUrl())); err == nil {
attrs["hostname"] = strings.Split(hostname, " ")[0] metadata.Hostname = strings.Split(hostname, " ")[0]
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return metadata, err
} }
if localAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/local-ipv4", ms.MetadataUrl())); err == nil { if localAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/local-ipv4", ms.MetadataUrl())); err == nil {
attrs["local-ipv4"] = localAddr metadata.PrivateIPv4 = net.ParseIP(localAddr)
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return metadata, err
} }
if publicAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/public-ipv4", ms.MetadataUrl())); err == nil { if publicAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/public-ipv4", ms.MetadataUrl())); err == nil {
attrs["public-ipv4"] = publicAddr metadata.PublicIPv4 = net.ParseIP(publicAddr)
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return metadata, err
} }
if content_path, err := ms.fetchAttribute(fmt.Sprintf("%s/network_config/content_path", ms.MetadataUrl())); err == nil { return metadata, nil
attrs["network_config"] = map[string]string{
"content_path": content_path,
}
} else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err
}
return json.Marshal(attrs)
} }
func (ms metadataService) Type() string { func (ms metadataService) Type() string {

View File

@ -15,11 +15,12 @@
package ec2 package ec2
import ( import (
"bytes"
"fmt" "fmt"
"net"
"reflect" "reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/datasource/metadata/test" "github.com/coreos/coreos-cloudinit/datasource/metadata/test"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
@ -145,7 +146,7 @@ func TestFetchMetadata(t *testing.T) {
root string root string
metadataPath string metadataPath string
resources map[string]string resources map[string]string
expect []byte expect datasource.Metadata
clientErr error clientErr error
expectErr error expectErr error
}{ }{
@ -167,9 +168,13 @@ func TestFetchMetadata(t *testing.T) {
"/2009-04-04/meta-data/public-keys": "0=test1\n", "/2009-04-04/meta-data/public-keys": "0=test1\n",
"/2009-04-04/meta-data/public-keys/0": "openssh-key", "/2009-04-04/meta-data/public-keys/0": "openssh-key",
"/2009-04-04/meta-data/public-keys/0/openssh-key": "key", "/2009-04-04/meta-data/public-keys/0/openssh-key": "key",
"/2009-04-04/meta-data/network_config/content_path": "path",
}, },
expect: []byte(`{"hostname":"host","local-ipv4":"1.2.3.4","network_config":{"content_path":"path"},"public-ipv4":"5.6.7.8","public_keys":{"test1":"key"}}`), expect: datasource.Metadata{
Hostname: "host",
PrivateIPv4: net.ParseIP("1.2.3.4"),
PublicIPv4: net.ParseIP("5.6.7.8"),
SSHPublicKeys: map[string]string{"test1": "key"},
},
}, },
{ {
root: "/", root: "/",
@ -181,9 +186,13 @@ func TestFetchMetadata(t *testing.T) {
"/2009-04-04/meta-data/public-keys": "0=test1\n", "/2009-04-04/meta-data/public-keys": "0=test1\n",
"/2009-04-04/meta-data/public-keys/0": "openssh-key", "/2009-04-04/meta-data/public-keys/0": "openssh-key",
"/2009-04-04/meta-data/public-keys/0/openssh-key": "key", "/2009-04-04/meta-data/public-keys/0/openssh-key": "key",
"/2009-04-04/meta-data/network_config/content_path": "path",
}, },
expect: []byte(`{"hostname":"host","local-ipv4":"1.2.3.4","network_config":{"content_path":"path"},"public-ipv4":"5.6.7.8","public_keys":{"test1":"key"}}`), expect: datasource.Metadata{
Hostname: "host",
PrivateIPv4: net.ParseIP("1.2.3.4"),
PublicIPv4: net.ParseIP("5.6.7.8"),
SSHPublicKeys: map[string]string{"test1": "key"},
},
}, },
{ {
clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")}, clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")},
@ -199,8 +208,8 @@ func TestFetchMetadata(t *testing.T) {
if Error(err) != Error(tt.expectErr) { if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err) t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
} }
if !bytes.Equal(metadata, tt.expect) { if !reflect.DeepEqual(tt.expect, metadata) {
t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata) t.Fatalf("bad fetch (%q): want %#v, got %#v", tt.resources, tt.expect, metadata)
} }
} }
} }

View File

@ -52,10 +52,6 @@ func (ms MetadataService) FetchUserdata() ([]byte, error) {
return ms.FetchData(ms.UserdataUrl()) return ms.FetchData(ms.UserdataUrl())
} }
func (ms MetadataService) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (ms MetadataService) FetchData(url string) ([]byte, error) { func (ms MetadataService) FetchData(url string) ([]byte, error) {
if data, err := ms.Client.GetRetry(url); err == nil { if data, err := ms.Client.GetRetry(url); err == nil {
return data, err return data, err

View File

@ -20,6 +20,7 @@ import (
"log" "log"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
) )
@ -55,8 +56,8 @@ func (c *procCmdline) ConfigRoot() string {
return "" return ""
} }
func (c *procCmdline) FetchMetadata() ([]byte, error) { func (c *procCmdline) FetchMetadata() (datasource.Metadata, error) {
return []byte{}, nil return datasource.Metadata{}, nil
} }
func (c *procCmdline) FetchUserdata() ([]byte, error) { func (c *procCmdline) FetchUserdata() ([]byte, error) {
@ -80,10 +81,6 @@ func (c *procCmdline) FetchUserdata() ([]byte, error) {
return cfg, nil return cfg, nil
} }
func (c *procCmdline) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (c *procCmdline) Type() string { func (c *procCmdline) Type() string {
return "proc-cmdline" return "proc-cmdline"
} }

28
datasource/test/test.go Normal file
View File

@ -0,0 +1,28 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"os"
)
type MockFilesystem map[string]string
func (m MockFilesystem) ReadFile(filename string) ([]byte, error) {
if contents, ok := m[filename]; ok {
return []byte(contents), nil
}
return nil, os.ErrNotExist
}

View File

@ -15,6 +15,7 @@
package url package url
import ( import (
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
) )
@ -40,8 +41,8 @@ func (f *remoteFile) ConfigRoot() string {
return "" return ""
} }
func (f *remoteFile) FetchMetadata() ([]byte, error) { func (f *remoteFile) FetchMetadata() (datasource.Metadata, error) {
return []byte{}, nil return datasource.Metadata{}, nil
} }
func (f *remoteFile) FetchUserdata() ([]byte, error) { func (f *remoteFile) FetchUserdata() ([]byte, error) {
@ -49,10 +50,6 @@ func (f *remoteFile) FetchUserdata() ([]byte, error) {
return client.GetRetry(f.url) return client.GetRetry(f.url)
} }
func (f *remoteFile) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (f *remoteFile) Type() string { func (f *remoteFile) Type() string {
return "url" return "url"
} }

View File

@ -15,13 +15,14 @@
package waagent package waagent
import ( import (
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path" "path"
"github.com/coreos/coreos-cloudinit/datasource"
) )
type waagent struct { type waagent struct {
@ -46,13 +47,13 @@ func (a *waagent) ConfigRoot() string {
return a.root return a.root
} }
func (a *waagent) FetchMetadata() ([]byte, error) { func (a *waagent) FetchMetadata() (metadata datasource.Metadata, err error) {
metadataBytes, err := a.tryReadFile(path.Join(a.root, "SharedConfig.xml")) var metadataBytes []byte
if err != nil { if metadataBytes, err = a.tryReadFile(path.Join(a.root, "SharedConfig.xml")); err != nil {
return nil, err return
} }
if len(metadataBytes) == 0 { if len(metadataBytes) == 0 {
return metadataBytes, nil return
} }
type Instance struct { type Instance struct {
@ -74,40 +75,34 @@ func (a *waagent) FetchMetadata() ([]byte, error) {
} }
} }
var metadata SharedConfig var m SharedConfig
if err := xml.Unmarshal(metadataBytes, &metadata); err != nil { if err = xml.Unmarshal(metadataBytes, &m); err != nil {
return nil, err return
} }
var instance Instance var instance Instance
for _, i := range metadata.Instances.Instances { for _, i := range m.Instances.Instances {
if i.Id == metadata.Incarnation.Instance { if i.Id == m.Incarnation.Instance {
instance = i instance = i
break break
} }
} }
attrs := map[string]string{ metadata.PrivateIPv4 = net.ParseIP(instance.Address)
"local-ipv4": instance.Address,
}
for _, e := range instance.InputEndpoints.Endpoints { for _, e := range instance.InputEndpoints.Endpoints {
host, _, err := net.SplitHostPort(e.LoadBalancedPublicAddress) host, _, err := net.SplitHostPort(e.LoadBalancedPublicAddress)
if err == nil { if err == nil {
attrs["public-ipv4"] = host metadata.PublicIPv4 = net.ParseIP(host)
break break
} }
} }
return json.Marshal(attrs) return
} }
func (a *waagent) FetchUserdata() ([]byte, error) { func (a *waagent) FetchUserdata() ([]byte, error) {
return a.tryReadFile(path.Join(a.root, "CustomData")) return a.tryReadFile(path.Join(a.root, "CustomData"))
} }
func (a *waagent) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (a *waagent) Type() string { func (a *waagent) Type() string {
return "waagent" return "waagent"
} }

View File

@ -15,45 +15,35 @@
package waagent package waagent
import ( import (
"encoding/json" "net"
"os"
"reflect" "reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/test"
) )
type mockFilesystem map[string][]byte
func (m mockFilesystem) readFile(filename string) ([]byte, error) {
if contents := m[filename]; contents != nil {
return contents, nil
}
return nil, os.ErrNotExist
}
func TestFetchMetadata(t *testing.T) { func TestFetchMetadata(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
root string root string
files mockFilesystem files test.MockFilesystem
metadata map[string]string metadata datasource.Metadata
}{ }{
{ {
"/", root: "/",
mockFilesystem{}, files: test.MockFilesystem{},
nil,
}, },
{ {
"/", root: "/",
mockFilesystem{"/SharedConfig.xml": []byte("")}, files: test.MockFilesystem{"/SharedConfig.xml": ""},
nil,
}, },
{ {
"/var/lib/waagent", root: "/var/lib/waagent",
mockFilesystem{"/var/lib/waagent/SharedConfig.xml": []byte("")}, files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ""},
nil,
}, },
{ {
"/var/lib/waagent", root: "/var/lib/waagent",
mockFilesystem{"/var/lib/waagent/SharedConfig.xml": []byte(`<?xml version="1.0" encoding="utf-8"?> files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": `<?xml version="1.0" encoding="utf-8"?>
<SharedConfig version="1.0.0.0" goalStateIncarnation="1"> <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
<Deployment name="c8f9e4c9c18948e1bebf57c5685da756" guid="{1d10394f-c741-4a1a-a6bb-278f213c5a5e}" incarnation="0" isNonCancellableTopologyChangeEnabled="false"> <Deployment name="c8f9e4c9c18948e1bebf57c5685da756" guid="{1d10394f-c741-4a1a-a6bb-278f213c5a5e}" incarnation="0" isNonCancellableTopologyChangeEnabled="false">
<Service name="core-test-1" guid="{00000000-0000-0000-0000-000000000000}" /> <Service name="core-test-1" guid="{00000000-0000-0000-0000-000000000000}" />
@ -89,26 +79,20 @@ func TestFetchMetadata(t *testing.T) {
</InputEndpoints> </InputEndpoints>
</Instance> </Instance>
</Instances> </Instances>
</SharedConfig>`)}, </SharedConfig>`},
map[string]string{ metadata: datasource.Metadata{
"local-ipv4": "100.73.202.64", PrivateIPv4: net.ParseIP("100.73.202.64"),
"public-ipv4": "191.239.39.77", PublicIPv4: net.ParseIP("191.239.39.77"),
}, },
}, },
} { } {
a := waagent{tt.root, tt.files.readFile} a := waagent{tt.root, tt.files.ReadFile}
metadataBytes, err := a.FetchMetadata() metadata, err := a.FetchMetadata()
if err != nil { if err != nil {
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err) t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
} }
var metadata map[string]string
if len(metadataBytes) > 0 {
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
panic(err)
}
}
if !reflect.DeepEqual(tt.metadata, metadata) { if !reflect.DeepEqual(tt.metadata, metadata) {
t.Fatalf("bad metadata for %q: want %q, got %q", tt, tt.metadata, metadata) t.Fatalf("bad metadata for %q: want %#v, got %#v", tt, tt.metadata, metadata)
} }
} }
} }
@ -116,22 +100,22 @@ func TestFetchMetadata(t *testing.T) {
func TestFetchUserdata(t *testing.T) { func TestFetchUserdata(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
root string root string
files mockFilesystem files test.MockFilesystem
}{ }{
{ {
"/", "/",
mockFilesystem{}, test.MockFilesystem{},
}, },
{ {
"/", "/",
mockFilesystem{"/CustomData": []byte{}}, test.MockFilesystem{"/CustomData": ""},
}, },
{ {
"/var/lib/waagent/", "/var/lib/waagent/",
mockFilesystem{"/var/lib/waagent/CustomData": []byte{}}, test.MockFilesystem{"/var/lib/waagent/CustomData": ""},
}, },
} { } {
a := waagent{tt.root, tt.files.readFile} a := waagent{tt.root, tt.files.ReadFile}
_, err := a.FetchUserdata() _, err := a.FetchUserdata()
if err != nil { if err != nil {
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err) t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)

View File

@ -42,7 +42,7 @@ type CloudConfigUnit interface {
// Apply renders a CloudConfig to an Environment. This can involve things like // Apply renders a CloudConfig to an Environment. This can involve things like
// configuring the hostname, adding new users, writing various configuration // configuring the hostname, adding new users, writing various configuration
// files to disk, and manipulating systemd services. // files to disk, and manipulating systemd services.
func Apply(cfg config.CloudConfig, env *Environment) error { func Apply(cfg config.CloudConfig, ifaces []network.InterfaceGenerator, env *Environment) error {
if cfg.Hostname != "" { if cfg.Hostname != "" {
if err := system.SetHostname(cfg.Hostname); err != nil { if err := system.SetHostname(cfg.Hostname); err != nil {
return err return err
@ -165,23 +165,9 @@ func Apply(cfg config.CloudConfig, env *Environment) error {
} }
} }
if env.NetconfType() != "" { if len(ifaces) > 0 {
var interfaces []network.InterfaceGenerator units = append(units, createNetworkingUnits(ifaces)...)
var err error if err := system.RestartNetwork(ifaces); err != nil {
switch env.NetconfType() {
case "debian":
interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig)
case "digitalocean":
interfaces, err = network.ProcessDigitalOceanNetconf(cfg.NetworkConfig)
default:
err = fmt.Errorf("Unsupported network config format %q", env.NetconfType())
}
if err != nil {
return err
}
units = append(units, createNetworkingUnits(interfaces)...)
if err := system.RestartNetwork(interfaces); err != nil {
return err return err
} }
} }

View File

@ -15,12 +15,14 @@
package initialize package initialize
import ( import (
"net"
"os" "os"
"path" "path"
"regexp" "regexp"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/config" "github.com/coreos/coreos-cloudinit/config"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
@ -30,28 +32,25 @@ type Environment struct {
root string root string
configRoot string configRoot string
workspace string workspace string
netconfType string
sshKeyName string sshKeyName string
substitutions map[string]string substitutions map[string]string
} }
// TODO(jonboulle): this is getting unwieldy, should be able to simplify the interface somehow // TODO(jonboulle): this is getting unwieldy, should be able to simplify the interface somehow
func NewEnvironment(root, configRoot, workspace, netconfType, sshKeyName string, substitutions map[string]string) *Environment { func NewEnvironment(root, configRoot, workspace, sshKeyName string, metadata datasource.Metadata) *Environment {
if substitutions == nil { firstNonNull := func(ip net.IP, env string) string {
substitutions = make(map[string]string) if ip == nil {
return env
} }
// If certain values are not in the supplied substitution, fall back to retrieving them from the environment return ip.String()
for k, v := range map[string]string{
"$public_ipv4": os.Getenv("COREOS_PUBLIC_IPV4"),
"$private_ipv4": os.Getenv("COREOS_PRIVATE_IPV4"),
"$public_ipv6": os.Getenv("COREOS_PUBLIC_IPV6"),
"$private_ipv6": os.Getenv("COREOS_PRIVATE_IPV6"),
} {
if _, ok := substitutions[k]; !ok {
substitutions[k] = v
} }
substitutions := map[string]string{
"$public_ipv4": firstNonNull(metadata.PublicIPv4, os.Getenv("COREOS_PUBLIC_IPV4")),
"$private_ipv4": firstNonNull(metadata.PrivateIPv4, os.Getenv("COREOS_PRIVATE_IPV4")),
"$public_ipv6": firstNonNull(metadata.PublicIPv6, os.Getenv("COREOS_PUBLIC_IPV6")),
"$private_ipv6": firstNonNull(metadata.PrivateIPv6, os.Getenv("COREOS_PRIVATE_IPV6")),
} }
return &Environment{root, configRoot, workspace, netconfType, sshKeyName, substitutions} return &Environment{root, configRoot, workspace, sshKeyName, substitutions}
} }
func (e *Environment) Workspace() string { func (e *Environment) Workspace() string {
@ -66,10 +65,6 @@ func (e *Environment) ConfigRoot() string {
return e.configRoot return e.configRoot
} }
func (e *Environment) NetconfType() string {
return e.netconfType
}
func (e *Environment) SSHKeyName() string { func (e *Environment) SSHKeyName() string {
return e.sshKeyName return e.sshKeyName
} }

View File

@ -16,10 +16,12 @@ package initialize
import ( import (
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path" "path"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
@ -29,18 +31,18 @@ func TestEnvironmentApply(t *testing.T) {
os.Setenv("COREOS_PUBLIC_IPV6", "1234::") os.Setenv("COREOS_PUBLIC_IPV6", "1234::")
os.Setenv("COREOS_PRIVATE_IPV6", "5678::") os.Setenv("COREOS_PRIVATE_IPV6", "5678::")
for _, tt := range []struct { for _, tt := range []struct {
subs map[string]string metadata datasource.Metadata
input string input string
out string out string
}{ }{
{ {
// Substituting both values directly should always take precedence // Substituting both values directly should always take precedence
// over environment variables // over environment variables
map[string]string{ datasource.Metadata{
"$public_ipv4": "192.0.2.3", PublicIPv4: net.ParseIP("192.0.2.3"),
"$private_ipv4": "192.0.2.203", PrivateIPv4: net.ParseIP("192.0.2.203"),
"$public_ipv6": "fe00:1234::", PublicIPv6: net.ParseIP("fe00:1234::"),
"$private_ipv6": "fe00:5678::", PrivateIPv6: net.ParseIP("fe00:5678::"),
}, },
`[Service] `[Service]
ExecStart=/usr/bin/echo "$public_ipv4 $public_ipv6" ExecStart=/usr/bin/echo "$public_ipv4 $public_ipv6"
@ -53,25 +55,29 @@ ExecStop=/usr/bin/echo $unknown`,
}, },
{ {
// Substituting one value directly while falling back with the other // Substituting one value directly while falling back with the other
map[string]string{"$private_ipv4": "127.0.0.1"}, datasource.Metadata{
PrivateIPv4: net.ParseIP("127.0.0.1"),
},
"$private_ipv4\n$public_ipv4", "$private_ipv4\n$public_ipv4",
"127.0.0.1\n1.2.3.4", "127.0.0.1\n1.2.3.4",
}, },
{ {
// Falling back to environment variables for both values // Falling back to environment variables for both values
map[string]string{"foo": "bar"}, datasource.Metadata{},
"$private_ipv4\n$public_ipv4", "$private_ipv4\n$public_ipv4",
"5.6.7.8\n1.2.3.4", "5.6.7.8\n1.2.3.4",
}, },
{ {
// No substitutions // No substitutions
nil, datasource.Metadata{},
"$private_ipv4\nfoobar", "$private_ipv4\nfoobar",
"5.6.7.8\nfoobar", "5.6.7.8\nfoobar",
}, },
{ {
// Escaping substitutions // Escaping substitutions
map[string]string{"$private_ipv4": "127.0.0.1"}, datasource.Metadata{
PrivateIPv4: net.ParseIP("127.0.0.1"),
},
`\$private_ipv4 `\$private_ipv4
$private_ipv4 $private_ipv4
addr: \$private_ipv4 addr: \$private_ipv4
@ -83,13 +89,13 @@ addr: $private_ipv4
}, },
{ {
// No substitutions with escaping // No substitutions with escaping
nil, datasource.Metadata{},
"\\$test\n$test", "\\$test\n$test",
"\\$test\n$test", "\\$test\n$test",
}, },
} { } {
env := NewEnvironment("./", "./", "./", "", "", tt.subs) env := NewEnvironment("./", "./", "./", "", tt.metadata)
got := env.Apply(tt.input) got := env.Apply(tt.input)
if got != tt.out { if got != tt.out {
t.Fatalf("Environment incorrectly applied.\ngot:\n%s\nwant:\n%s", got, tt.out) t.Fatalf("Environment incorrectly applied.\ngot:\n%s\nwant:\n%s", got, tt.out)
@ -98,11 +104,11 @@ addr: $private_ipv4
} }
func TestEnvironmentFile(t *testing.T) { func TestEnvironmentFile(t *testing.T) {
subs := map[string]string{ metadata := datasource.Metadata{
"$public_ipv4": "1.2.3.4", PublicIPv4: net.ParseIP("1.2.3.4"),
"$private_ipv4": "5.6.7.8", PrivateIPv4: net.ParseIP("5.6.7.8"),
"$public_ipv6": "1234::", PublicIPv6: net.ParseIP("1234::"),
"$private_ipv6": "5678::", PrivateIPv6: net.ParseIP("5678::"),
} }
expect := "COREOS_PRIVATE_IPV4=5.6.7.8\nCOREOS_PRIVATE_IPV6=5678::\nCOREOS_PUBLIC_IPV4=1.2.3.4\nCOREOS_PUBLIC_IPV6=1234::\n" expect := "COREOS_PRIVATE_IPV4=5.6.7.8\nCOREOS_PRIVATE_IPV6=5678::\nCOREOS_PUBLIC_IPV4=1.2.3.4\nCOREOS_PUBLIC_IPV6=1234::\n"
@ -112,7 +118,7 @@ func TestEnvironmentFile(t *testing.T) {
} }
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
env := NewEnvironment("./", "./", "./", "", "", subs) env := NewEnvironment("./", "./", "./", "", metadata)
ef := env.DefaultEnvironmentFile() ef := env.DefaultEnvironmentFile()
err = system.WriteEnvFile(ef, dir) err = system.WriteEnvFile(ef, dir)
if err != nil { if err != nil {
@ -131,14 +137,10 @@ func TestEnvironmentFile(t *testing.T) {
} }
func TestEnvironmentFileNil(t *testing.T) { func TestEnvironmentFileNil(t *testing.T) {
subs := map[string]string{ os.Clearenv()
"$public_ipv4": "", metadata := datasource.Metadata{}
"$private_ipv4": "",
"$public_ipv6": "",
"$private_ipv6": "",
}
env := NewEnvironment("./", "./", "./", "", "", subs) env := NewEnvironment("./", "./", "./", "", metadata)
ef := env.DefaultEnvironmentFile() ef := env.DefaultEnvironmentFile()
if ef != nil { if ef != nil {
t.Fatalf("Environment file not nil: %v", ef) t.Fatalf("Environment file not nil: %v", ef)

View File

@ -1,88 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package initialize
import (
"encoding/json"
"sort"
"github.com/coreos/coreos-cloudinit/config"
)
// ParseMetaData parses a JSON blob in the OpenStack metadata service format,
// and converts it to a partially hydrated CloudConfig.
func ParseMetaData(contents string) (*config.CloudConfig, error) {
if len(contents) == 0 {
return nil, nil
}
var metadata struct {
SSHAuthorizedKeyMap map[string]string `json:"public_keys"`
Hostname string `json:"hostname"`
NetworkConfig struct {
ContentPath string `json:"content_path"`
} `json:"network_config"`
}
if err := json.Unmarshal([]byte(contents), &metadata); err != nil {
return nil, err
}
var cfg config.CloudConfig
if len(metadata.SSHAuthorizedKeyMap) > 0 {
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
for _, name := range sortedKeys(metadata.SSHAuthorizedKeyMap) {
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, metadata.SSHAuthorizedKeyMap[name])
}
}
cfg.Hostname = metadata.Hostname
cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath
return &cfg, nil
}
// ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service
// format and returns a substitution map possibly containing private_ipv4,
// public_ipv4, private_ipv6, and public_ipv6 addresses.
func ExtractIPsFromMetadata(contents []byte) (map[string]string, error) {
var ips struct {
PublicIPv4 string `json:"public-ipv4"`
PrivateIPv4 string `json:"local-ipv4"`
PublicIPv6 string `json:"public-ipv6"`
PrivateIPv6 string `json:"local-ipv6"`
}
if err := json.Unmarshal(contents, &ips); err != nil {
return nil, err
}
m := make(map[string]string)
if ips.PrivateIPv4 != "" {
m["$private_ipv4"] = ips.PrivateIPv4
}
if ips.PublicIPv4 != "" {
m["$public_ipv4"] = ips.PublicIPv4
}
if ips.PrivateIPv6 != "" {
m["$private_ipv6"] = ips.PrivateIPv6
}
if ips.PublicIPv6 != "" {
m["$public_ipv6"] = ips.PublicIPv6
}
return m, nil
}
func sortedKeys(m map[string]string) (keys []string) {
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return
}

View File

@ -1,87 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package initialize
import (
"reflect"
"testing"
"github.com/coreos/coreos-cloudinit/config"
)
func TestParseMetadata(t *testing.T) {
for i, tt := range []struct {
in string
want *config.CloudConfig
err bool
}{
{"", nil, false},
{`garbage, invalid json`, nil, true},
{`{"foo": "bar"}`, &config.CloudConfig{}, false},
{`{"network_config": {"content_path": "asdf"}}`, &config.CloudConfig{NetworkConfigPath: "asdf"}, false},
{`{"hostname": "turkleton"}`, &config.CloudConfig{Hostname: "turkleton"}, false},
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &config.CloudConfig{SSHAuthorizedKeys: []string{"alice", "jill"}}, false},
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &config.CloudConfig{SSHAuthorizedKeys: []string{"re", "fa"}, Hostname: "my_host", NetworkConfigPath: "/root"}, false},
} {
got, err := ParseMetaData(tt.in)
if tt.err != (err != nil) {
t.Errorf("case #%d: bad error state: got %t, want %t (err=%v)", i, (err != nil), tt.err, err)
}
if got == nil {
if tt.want != nil {
t.Errorf("case #%d: unexpected nil output", i)
}
} else if tt.want == nil {
t.Errorf("case #%d: unexpected non-nil output", i)
} else {
if !reflect.DeepEqual(*got, *tt.want) {
t.Errorf("case #%d: bad output:\ngot\n%v\nwant\n%v", i, *got, *tt.want)
}
}
}
}
func TestExtractIPsFromMetadata(t *testing.T) {
for i, tt := range []struct {
in []byte
err bool
out map[string]string
}{
{
[]byte(`{"public-ipv4": "12.34.56.78", "local-ipv4": "1.2.3.4", "public-ipv6": "1234::", "local-ipv6": "5678::"}`),
false,
map[string]string{"$public_ipv4": "12.34.56.78", "$private_ipv4": "1.2.3.4", "$public_ipv6": "1234::", "$private_ipv6": "5678::"},
},
{
[]byte(`{"local-ipv4": "127.0.0.1", "something_else": "don't care"}`),
false,
map[string]string{"$private_ipv4": "127.0.0.1"},
},
{
[]byte(`garbage`),
true,
nil,
},
} {
got, err := ExtractIPsFromMetadata(tt.in)
if (err != nil) != tt.err {
t.Errorf("bad error state (got %t, want %t)", err != nil, tt.err)
}
if !reflect.DeepEqual(got, tt.out) {
t.Errorf("case %d: got %s, want %s", i, got, tt.out)
}
}
}

View File

@ -19,9 +19,9 @@ import (
"strings" "strings"
) )
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) { func ProcessDebianNetconf(config []byte) ([]InterfaceGenerator, error) {
log.Println("Processing Debian network config") log.Println("Processing Debian network config")
lines := formatConfig(config) lines := formatConfig(string(config))
stanzas, err := parseStanzas(lines) stanzas, err := parseStanzas(lines)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -44,7 +44,7 @@ func TestProcessDebianNetconf(t *testing.T) {
{"auto eth1\nauto eth2", false, 0}, {"auto eth1\nauto eth2", false, 0},
{"iface eth1 inet manual", false, 1}, {"iface eth1 inet manual", false, 1},
} { } {
interfaces, err := ProcessDebianNetconf(tt.in) interfaces, err := ProcessDebianNetconf([]byte(tt.in))
failed := err != nil failed := err != nil
if tt.fail != failed { if tt.fail != failed {
t.Fatalf("bad failure state for %q: got %t, want %t", tt.in, failed, tt.fail) t.Fatalf("bad failure state for %q: got %t, want %t", tt.in, failed, tt.fail)

View File

@ -23,14 +23,14 @@ import (
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean" "github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
) )
func ProcessDigitalOceanNetconf(config string) ([]InterfaceGenerator, error) { func ProcessDigitalOceanNetconf(config []byte) ([]InterfaceGenerator, error) {
log.Println("Processing DigitalOcean network config") log.Println("Processing DigitalOcean network config")
if config == "" { if len(config) == 0 {
return nil, nil return nil, nil
} }
var cfg digitalocean.Metadata var cfg digitalocean.Metadata
if err := json.Unmarshal([]byte(config), &cfg); err != nil { if err := json.Unmarshal(config, &cfg); err != nil {
return nil, err return nil, err
} }

View File

@ -378,7 +378,7 @@ func TestProcessDigitalOceanNetconf(t *testing.T) {
ifaces: []InterfaceGenerator{}, ifaces: []InterfaceGenerator{},
}, },
} { } {
ifaces, err := ProcessDigitalOceanNetconf(tt.cfg) ifaces, err := ProcessDigitalOceanNetconf([]byte(tt.cfg))
if !errorsEqual(tt.err, err) { if !errorsEqual(tt.err, err) {
t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err) t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err)
} }

2
test
View File

@ -33,7 +33,7 @@ declare -a TESTPKGS=(
) )
if [ -z "$PKG" ]; then if [ -z "$PKG" ]; then
GOFMTPATH="${TESTPKGS[*]} coreos-cloudinit.go" GOFMTPATH="${TESTPKGS[*]} *.go"
# prepend repo path to each package # prepend repo path to each package
TESTPKGS="${TESTPKGS[*]/#/${REPO_PATH}/} ./" TESTPKGS="${TESTPKGS[*]/#/${REPO_PATH}/} ./"
else else