datasource: replace metadata map with struct

The loosely-typed metadata map is a load of crap. Make it a struct and
let the compiler help us out.
This commit is contained in:
Alex Crawford 2015-01-22 17:35:39 -08:00
parent d4c617fc23
commit 3e47c09b41
17 changed files with 248 additions and 276 deletions

View File

@ -176,21 +176,14 @@ 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 // Extract IPv4 addresses from metadata
var subs map[string]string subs := initialize.ExtractIPsFromMetadata(metadata)
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.convertNetconf, flags.sshKeyName, subs)
@ -198,10 +191,7 @@ func main() {
var ccm, ccu *config.CloudConfig var ccm, ccu *config.CloudConfig
var script *config.Script var script *config.Script
if ccm, err = initialize.ParseMetaData(string(metadataBytes)); err != nil { ccm = initialize.ParseMetaData(metadata)
fmt.Printf("Failed to parse meta-data: %v\n", err)
os.Exit(1)
}
if ccm != nil && flags.convertNetconf != "" { if ccm != nil && flags.convertNetconf != "" {
fmt.Printf("Fetching network config from datasource of type %q\n", ds.Type()) fmt.Printf("Fetching network config from datasource of type %q\n", ds.Type())

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,8 +50,28 @@ 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.NetworkConfigPath = m.NetworkConfig.ContentPath
return
} }
func (cd *configDrive) FetchUserdata() ([]byte, error) { func (cd *configDrive) FetchUserdata() ([]byte, error) {

View File

@ -15,8 +15,10 @@
package configdrive package configdrive
import ( import (
"reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/test" "github.com/coreos/coreos-cloudinit/datasource/test"
) )
@ -25,22 +27,28 @@ func TestFetchMetadata(t *testing.T) {
root string root string
files test.MockFilesystem files test.MockFilesystem
metadata string metadata datasource.Metadata
}{ }{
{ {
"/", root: "/",
test.MockFilesystem{}, files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"ignore": "me"}`},
"",
}, },
{ {
"/", root: "/",
test.MockFilesystem{"/openstack/latest/meta_data.json": "metadata"}, files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"hostname": "host"}`},
"metadata", metadata: datasource.Metadata{Hostname: "host"},
}, },
{ {
"/media/configdrive", root: "/media/configdrive",
test.MockFilesystem{"/media/configdrive/openstack/latest/meta_data.json": "metadata"}, files: test.MockFilesystem{"/media/configdrive/openstack/latest/meta_data.json": `{"hostname": "host", "network_config": {"content_path": "path"}, "public_keys":{"1": "key1", "2": "key2"}}`},
"metadata", metadata: datasource.Metadata{
Hostname: "host",
NetworkConfigPath: "path",
SSHPublicKeys: map[string]string{
"1": "key1",
"2": "key2",
},
},
}, },
} { } {
cd := configDrive{tt.root, tt.files.ReadFile} cd := configDrive{tt.root, tt.files.ReadFile}
@ -48,8 +56,8 @@ func TestFetchMetadata(t *testing.T) {
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(metadata) != tt.metadata { if !reflect.DeepEqual(tt.metadata, metadata) {
t.Fatalf("bad path for %q: want %q, got %q", tt, tt.metadata, metadata) t.Fatalf("bad metadata for %q: want %#v, got %#v", tt, tt.metadata, metadata)
} }
} }
} }
@ -83,7 +91,7 @@ func TestFetchUserdata(t *testing.T) {
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(userdata) != tt.userdata { if string(userdata) != tt.userdata {
t.Fatalf("bad path for %q: want %q, got %q", tt, tt.userdata, userdata) t.Fatalf("bad userdata for %q: want %q, got %q", tt, tt.userdata, userdata)
} }
} }
} }

View File

@ -14,12 +14,26 @@
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) 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
NetworkConfigPath string
}

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,8 +42,8 @@ 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) {

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) {
@ -154,14 +149,14 @@ func (scs *serverContextService) FetchNetworkConfig(a string) ([]byte, error) {
return nil, nil return nil, nil
} }
func (scs *serverContextService) findLocalIP(mac string) (string, error) { func (scs *serverContextService) findLocalIP(mac string) (net.IP, 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 +171,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"
) )
@ -68,45 +70,43 @@ 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 ms.interfaces = m.Interfaces
if err := json.Unmarshal(data, &metadata); err != nil { ms.dns = m.DNS
return []byte{}, err
if len(m.Interfaces.Public) > 0 {
if m.Interfaces.Public[0].IPv4 != nil {
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
} }
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) { func (ms metadataService) FetchNetworkConfig(filename string) ([]byte, error) {

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,14 @@ 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",
},
},
}, },
{ {
clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")}, clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")},
@ -99,8 +108,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,57 @@ 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 { if contentPath, err := ms.fetchAttribute(fmt.Sprintf("%s/network_config/content_path", ms.MetadataUrl())); err == nil {
attrs["network_config"] = map[string]string{ metadata.NetworkConfigPath = contentPath
"content_path": content_path,
}
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return metadata, err
} }
return json.Marshal(attrs) return metadata, nil
} }
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
}{ }{
@ -169,7 +170,13 @@ func TestFetchMetadata(t *testing.T) {
"/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", "/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"},
NetworkConfigPath: "path",
},
}, },
{ {
root: "/", root: "/",
@ -183,7 +190,13 @@ func TestFetchMetadata(t *testing.T) {
"/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", "/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"},
NetworkConfigPath: "path",
},
}, },
{ {
clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")}, clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")},
@ -199,8 +212,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

@ -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) {

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) {

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,30 +75,28 @@ 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) {

View File

@ -15,10 +15,11 @@
package waagent package waagent
import ( import (
"encoding/json" "net"
"reflect" "reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/test" "github.com/coreos/coreos-cloudinit/datasource/test"
) )
@ -26,26 +27,23 @@ func TestFetchMetadata(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
root string root string
files test.MockFilesystem files test.MockFilesystem
metadata map[string]string metadata datasource.Metadata
}{ }{
{ {
"/", root: "/",
test.MockFilesystem{}, files: test.MockFilesystem{},
nil,
}, },
{ {
"/", root: "/",
test.MockFilesystem{"/SharedConfig.xml": ""}, files: test.MockFilesystem{"/SharedConfig.xml": ""},
nil,
}, },
{ {
"/var/lib/waagent", root: "/var/lib/waagent",
test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ""}, files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ""},
nil,
}, },
{ {
"/var/lib/waagent", root: "/var/lib/waagent",
test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": `<?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}" />
@ -82,25 +80,19 @@ func TestFetchMetadata(t *testing.T) {
</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)
} }
} }
} }

View File

@ -15,68 +15,46 @@
package initialize package initialize
import ( import (
"encoding/json"
"sort" "sort"
"github.com/coreos/coreos-cloudinit/config" "github.com/coreos/coreos-cloudinit/config"
"github.com/coreos/coreos-cloudinit/datasource"
) )
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, // ParseMetaData parses a JSON blob in the OpenStack metadata service format,
// and converts it to a partially hydrated CloudConfig. // and converts it to a partially hydrated CloudConfig.
func ParseMetaData(contents string) (*config.CloudConfig, error) { func ParseMetaData(metadata datasource.Metadata) *config.CloudConfig {
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 var cfg config.CloudConfig
if len(metadata.SSHAuthorizedKeyMap) > 0 { if len(metadata.SSHPublicKeys) > 0 {
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap)) cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHPublicKeys))
for _, name := range sortedKeys(metadata.SSHAuthorizedKeyMap) { for _, name := range sortedKeys(metadata.SSHPublicKeys) {
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, metadata.SSHAuthorizedKeyMap[name]) cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, metadata.SSHPublicKeys[name])
} }
} }
cfg.Hostname = metadata.Hostname cfg.Hostname = metadata.Hostname
cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath cfg.NetworkConfigPath = metadata.NetworkConfigPath
return &cfg, nil return &cfg
} }
// ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service // ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service
// format and returns a substitution map possibly containing private_ipv4, // format and returns a substitution map possibly containing private_ipv4,
// public_ipv4, private_ipv6, and public_ipv6 addresses. // public_ipv4, private_ipv6, and public_ipv6 addresses.
func ExtractIPsFromMetadata(contents []byte) (map[string]string, error) { func ExtractIPsFromMetadata(metadata datasource.Metadata) map[string]string {
var ips struct { subs := map[string]string{}
PublicIPv4 string `json:"public-ipv4"` if metadata.PrivateIPv4 != nil {
PrivateIPv4 string `json:"local-ipv4"` subs["$private_ipv4"] = metadata.PrivateIPv4.String()
PublicIPv6 string `json:"public-ipv6"`
PrivateIPv6 string `json:"local-ipv6"`
} }
if err := json.Unmarshal(contents, &ips); err != nil { if metadata.PublicIPv4 != nil {
return nil, err subs["$public_ipv4"] = metadata.PublicIPv4.String()
} }
m := make(map[string]string) if metadata.PrivateIPv6 != nil {
if ips.PrivateIPv4 != "" { subs["$private_ipv6"] = metadata.PrivateIPv6.String()
m["$private_ipv4"] = ips.PrivateIPv4
} }
if ips.PublicIPv4 != "" { if metadata.PublicIPv6 != nil {
m["$public_ipv4"] = ips.PublicIPv4 subs["$public_ipv6"] = metadata.PublicIPv6.String()
} }
if ips.PrivateIPv6 != "" {
m["$private_ipv6"] = ips.PrivateIPv6 return subs
}
if ips.PublicIPv6 != "" {
m["$public_ipv6"] = ips.PublicIPv6
}
return m, nil
} }
func sortedKeys(m map[string]string) (keys []string) { func sortedKeys(m map[string]string) (keys []string) {

View File

@ -15,71 +15,29 @@
package initialize package initialize
import ( import (
"net"
"reflect" "reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/config" "github.com/coreos/coreos-cloudinit/datasource"
) )
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) { func TestExtractIPsFromMetadata(t *testing.T) {
for i, tt := range []struct { for i, tt := range []struct {
in []byte in datasource.Metadata
err bool
out map[string]string out map[string]string
}{ }{
{ {
[]byte(`{"public-ipv4": "12.34.56.78", "local-ipv4": "1.2.3.4", "public-ipv6": "1234::", "local-ipv6": "5678::"}`), datasource.Metadata{
false, PublicIPv4: net.ParseIP("12.34.56.78"),
PrivateIPv4: net.ParseIP("1.2.3.4"),
PublicIPv6: net.ParseIP("1234::"),
PrivateIPv6: net.ParseIP("5678::"),
},
map[string]string{"$public_ipv4": "12.34.56.78", "$private_ipv4": "1.2.3.4", "$public_ipv6": "1234::", "$private_ipv6": "5678::"}, 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) got := 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) { if !reflect.DeepEqual(got, tt.out) {
t.Errorf("case %d: got %s, want %s", i, got, tt.out) t.Errorf("case %d: got %s, want %s", i, got, tt.out)
} }