From 3e47c09b41c6902646f47a34359b49a13029aec6 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 Jan 2015 17:35:39 -0800 Subject: [PATCH] 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. --- coreos-cloudinit.go | 18 ++--- datasource/configdrive/configdrive.go | 27 ++++++- datasource/configdrive/configdrive_test.go | 34 +++++---- datasource/datasource.go | 16 ++++- datasource/file/file.go | 6 +- .../metadata/cloudsigma/server_context.go | 45 ++++++------ .../cloudsigma/server_context_test.go | 18 ++--- datasource/metadata/digitalocean/metadata.go | 70 +++++++++---------- .../metadata/digitalocean/metadata_test.go | 19 +++-- datasource/metadata/ec2/metadata.go | 41 ++++++----- datasource/metadata/ec2/metadata_test.go | 25 +++++-- datasource/proc_cmdline/proc_cmdline.go | 5 +- datasource/url/url.go | 5 +- datasource/waagent/waagent.go | 31 ++++---- datasource/waagent/waagent_test.go | 40 +++++------ initialize/meta_data.go | 62 ++++++---------- initialize/meta_data_test.go | 62 +++------------- 17 files changed, 248 insertions(+), 276 deletions(-) diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 50514be..5977aae 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -176,21 +176,14 @@ func main() { } fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type()) - metadataBytes, err := ds.FetchMetadata() + metadata, err := ds.FetchMetadata() if err != nil { fmt.Printf("Failed fetching meta-data from datasource: %v\n", err) 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) - } - } + // Extract IPv4 addresses from metadata + subs := initialize.ExtractIPsFromMetadata(metadata) // Apply environment to user-data env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.convertNetconf, flags.sshKeyName, subs) @@ -198,10 +191,7 @@ func main() { var ccm, ccu *config.CloudConfig 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) - } + ccm = initialize.ParseMetaData(metadata) if ccm != nil && flags.convertNetconf != "" { fmt.Printf("Fetching network config from datasource of type %q\n", ds.Type()) diff --git a/datasource/configdrive/configdrive.go b/datasource/configdrive/configdrive.go index 533e47d..4b5b2e7 100644 --- a/datasource/configdrive/configdrive.go +++ b/datasource/configdrive/configdrive.go @@ -15,10 +15,13 @@ package configdrive import ( + "encoding/json" "fmt" "io/ioutil" "os" "path" + + "github.com/coreos/coreos-cloudinit/datasource" ) const ( @@ -47,8 +50,28 @@ func (cd *configDrive) ConfigRoot() string { return cd.openstackRoot() } -func (cd *configDrive) FetchMetadata() ([]byte, error) { - return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "meta_data.json")) +func (cd *configDrive) FetchMetadata() (metadata datasource.Metadata, err error) { + 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) { diff --git a/datasource/configdrive/configdrive_test.go b/datasource/configdrive/configdrive_test.go index fa42209..f53a5a4 100644 --- a/datasource/configdrive/configdrive_test.go +++ b/datasource/configdrive/configdrive_test.go @@ -15,8 +15,10 @@ package configdrive import ( + "reflect" "testing" + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource/test" ) @@ -25,22 +27,28 @@ func TestFetchMetadata(t *testing.T) { root string files test.MockFilesystem - metadata string + metadata datasource.Metadata }{ { - "/", - test.MockFilesystem{}, - "", + root: "/", + files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"ignore": "me"}`}, }, { - "/", - test.MockFilesystem{"/openstack/latest/meta_data.json": "metadata"}, - "metadata", + root: "/", + files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"hostname": "host"}`}, + metadata: datasource.Metadata{Hostname: "host"}, }, { - "/media/configdrive", - test.MockFilesystem{"/media/configdrive/openstack/latest/meta_data.json": "metadata"}, - "metadata", + root: "/media/configdrive", + files: test.MockFilesystem{"/media/configdrive/openstack/latest/meta_data.json": `{"hostname": "host", "network_config": {"content_path": "path"}, "public_keys":{"1": "key1", "2": "key2"}}`}, + metadata: datasource.Metadata{ + Hostname: "host", + NetworkConfigPath: "path", + SSHPublicKeys: map[string]string{ + "1": "key1", + "2": "key2", + }, + }, }, } { cd := configDrive{tt.root, tt.files.ReadFile} @@ -48,8 +56,8 @@ func TestFetchMetadata(t *testing.T) { if err != nil { t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err) } - if string(metadata) != tt.metadata { - t.Fatalf("bad path for %q: want %q, got %q", tt, tt.metadata, metadata) + if !reflect.DeepEqual(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) } 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) } } } diff --git a/datasource/datasource.go b/datasource/datasource.go index 7a95f18..cf0f2f8 100644 --- a/datasource/datasource.go +++ b/datasource/datasource.go @@ -14,12 +14,26 @@ package datasource +import ( + "net" +) + type Datasource interface { IsAvailable() bool AvailabilityChanges() bool ConfigRoot() string - FetchMetadata() ([]byte, error) + FetchMetadata() (Metadata, error) FetchUserdata() ([]byte, error) FetchNetworkConfig(string) ([]byte, error) 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 +} diff --git a/datasource/file/file.go b/datasource/file/file.go index c2db2ff..e4f2424 100644 --- a/datasource/file/file.go +++ b/datasource/file/file.go @@ -17,6 +17,8 @@ package file import ( "io/ioutil" "os" + + "github.com/coreos/coreos-cloudinit/datasource" ) type localFile struct { @@ -40,8 +42,8 @@ func (f *localFile) ConfigRoot() string { return "" } -func (f *localFile) FetchMetadata() ([]byte, error) { - return []byte{}, nil +func (f *localFile) FetchMetadata() (datasource.Metadata, error) { + return datasource.Metadata{}, nil } func (f *localFile) FetchUserdata() ([]byte, error) { diff --git a/datasource/metadata/cloudsigma/server_context.go b/datasource/metadata/cloudsigma/server_context.go index 59bcc37..c3d58b8 100644 --- a/datasource/metadata/cloudsigma/server_context.go +++ b/datasource/metadata/cloudsigma/server_context.go @@ -24,6 +24,8 @@ import ( "os" "strings" + "github.com/coreos/coreos-cloudinit/datasource" + "github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/cloudsigma/cepgo" ) @@ -69,7 +71,7 @@ func (_ *serverContextService) Type() string { return "server-context" } -func (scs *serverContextService) FetchMetadata() ([]byte, error) { +func (scs *serverContextService) FetchMetadata() (metadata datasource.Metadata, err error) { var ( inputMetadata struct { Name string `json:"name"` @@ -88,48 +90,41 @@ func (scs *serverContextService) FetchMetadata() ([]byte, error) { } `json:"vlan"` } `json:"nics"` } - outputMetadata struct { - Hostname string `json:"name"` - PublicKeys map[string]string `json:"public_keys"` - LocalIPv4 string `json:"local-ipv4"` - PublicIPv4 string `json:"public-ipv4"` - } + rawMetadata []byte ) - rawMetadata, err := scs.client.FetchRaw("") - if err != nil { - return []byte{}, err + if rawMetadata, err = scs.client.FetchRaw(""); err != nil { + return } - err = json.Unmarshal(rawMetadata, &inputMetadata) - if err != nil { - return []byte{}, err + if err = json.Unmarshal(rawMetadata, &inputMetadata); err != nil { + return } if inputMetadata.Name != "" { - outputMetadata.Hostname = inputMetadata.Name + metadata.Hostname = inputMetadata.Name } else { - outputMetadata.Hostname = inputMetadata.UUID + metadata.Hostname = inputMetadata.UUID } + metadata.SSHPublicKeys = map[string]string{} if key, ok := inputMetadata.Meta["ssh_public_key"]; ok { splitted := strings.Split(key, " ") - outputMetadata.PublicKeys = make(map[string]string) - outputMetadata.PublicKeys[splitted[len(splitted)-1]] = key + metadata.SSHPublicKeys[splitted[len(splitted)-1]] = key } for _, nic := range inputMetadata.Nics { if nic.IPv4Conf.IP.UUID != "" { - outputMetadata.PublicIPv4 = nic.IPv4Conf.IP.UUID + metadata.PublicIPv4 = net.ParseIP(nic.IPv4Conf.IP.UUID) } if nic.VLAN.UUID != "" { 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) { @@ -154,14 +149,14 @@ func (scs *serverContextService) FetchNetworkConfig(a string) ([]byte, error) { return nil, nil } -func (scs *serverContextService) findLocalIP(mac string) (string, error) { +func (scs *serverContextService) findLocalIP(mac string) (net.IP, error) { ifaces, err := net.Interfaces() if err != nil { - return "", err + return nil, err } ifaceMac, err := net.ParseMAC(mac) if err != nil { - return "", err + return nil, err } for _, iface := range ifaces { if !bytes.Equal(iface.HardwareAddr, ifaceMac) { @@ -176,12 +171,12 @@ func (scs *serverContextService) findLocalIP(mac string) (string, error) { switch ip := addr.(type) { case *net.IPNet: 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 { diff --git a/datasource/metadata/cloudsigma/server_context_test.go b/datasource/metadata/cloudsigma/server_context_test.go index b234294..726425b 100644 --- a/datasource/metadata/cloudsigma/server_context_test.go +++ b/datasource/metadata/cloudsigma/server_context_test.go @@ -15,7 +15,7 @@ package cloudsigma import ( - "encoding/json" + "net" "reflect" "testing" ) @@ -44,12 +44,6 @@ func (f *fakeCepgoClient) FetchRaw(key string) ([]byte, error) { } 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) scs := NewServerContextService() scs.client = client @@ -114,24 +108,20 @@ func TestServerContextFetchMetadata(t *testing.T) { "uuid": "20a0059b-041e-4d0c-bcc6-9b2852de48b3" }`) - metadataBytes, err := scs.FetchMetadata() + metadata, err := scs.FetchMetadata() if err != nil { t.Error(err.Error()) } - if err := json.Unmarshal(metadataBytes, &metadata); err != nil { - t.Error(err.Error()) - } - if metadata.Hostname != "coreos" { 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") } - 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) } } diff --git a/datasource/metadata/digitalocean/metadata.go b/datasource/metadata/digitalocean/metadata.go index 14f9c3e..194cae0 100644 --- a/datasource/metadata/digitalocean/metadata.go +++ b/datasource/metadata/digitalocean/metadata.go @@ -16,8 +16,10 @@ package digitalocean import ( "encoding/json" + "net" "strconv" + "github.com/coreos/coreos-cloudinit/datasource" "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)} } -func (ms *metadataService) FetchMetadata() ([]byte, error) { - data, err := ms.FetchData(ms.MetadataUrl()) - if err != nil || len(data) == 0 { - return []byte{}, err +func (ms *metadataService) FetchMetadata() (metadata datasource.Metadata, err error) { + var data []byte + var m Metadata + + 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 err := json.Unmarshal(data, &metadata); err != nil { - return []byte{}, err + ms.interfaces = m.Interfaces + ms.dns = m.DNS + + 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 - 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) + return } func (ms metadataService) FetchNetworkConfig(filename string) ([]byte, error) { diff --git a/datasource/metadata/digitalocean/metadata_test.go b/datasource/metadata/digitalocean/metadata_test.go index 4b967c2..aa03b37 100644 --- a/datasource/metadata/digitalocean/metadata_test.go +++ b/datasource/metadata/digitalocean/metadata_test.go @@ -15,10 +15,12 @@ package digitalocean import ( - "bytes" "fmt" + "net" + "reflect" "testing" + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/datasource/metadata/test" "github.com/coreos/coreos-cloudinit/pkg" @@ -36,7 +38,7 @@ func TestFetchMetadata(t *testing.T) { root string metadataPath string resources map[string]string - expect []byte + expect datasource.Metadata clientErr 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")}, @@ -99,8 +108,8 @@ func TestFetchMetadata(t *testing.T) { if Error(err) != Error(tt.expectErr) { t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err) } - if !bytes.Equal(metadata, tt.expect) { - t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata) + if !reflect.DeepEqual(tt.expect, metadata) { + t.Fatalf("bad fetch (%q): want %#q, got %#q", tt.resources, tt.expect, metadata) } } } diff --git a/datasource/metadata/ec2/metadata.go b/datasource/metadata/ec2/metadata.go index 9e3735c..ab1417f 100644 --- a/datasource/metadata/ec2/metadata.go +++ b/datasource/metadata/ec2/metadata.go @@ -17,10 +17,11 @@ package ec2 import ( "bufio" "bytes" - "encoding/json" "fmt" + "net" "strings" + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/pkg" ) @@ -40,59 +41,57 @@ func NewDatasource(root string) *metadataService { return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)} } -func (ms metadataService) FetchMetadata() ([]byte, error) { - attrs := make(map[string]interface{}) +func (ms metadataService) FetchMetadata() (datasource.Metadata, error) { + metadata := datasource.Metadata{} + if keynames, err := ms.fetchAttributes(fmt.Sprintf("%s/public-keys", ms.MetadataUrl())); err == nil { keyIDs := make(map[string]string) for _, keyname := range keynames { tokens := strings.SplitN(keyname, "=", 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] } - keys := make(map[string]string) + metadata.SSHPublicKeys = map[string]string{} for name, id := range keyIDs { sshkey, err := ms.fetchAttribute(fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.MetadataUrl(), id)) 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) } - attrs["public_keys"] = keys } 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 { - attrs["hostname"] = strings.Split(hostname, " ")[0] + metadata.Hostname = strings.Split(hostname, " ")[0] } 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 { - attrs["local-ipv4"] = localAddr + metadata.PrivateIPv4 = net.ParseIP(localAddr) } 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 { - attrs["public-ipv4"] = publicAddr + metadata.PublicIPv4 = net.ParseIP(publicAddr) } 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 { - attrs["network_config"] = map[string]string{ - "content_path": content_path, - } + if contentPath, err := ms.fetchAttribute(fmt.Sprintf("%s/network_config/content_path", ms.MetadataUrl())); err == nil { + metadata.NetworkConfigPath = contentPath } 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 { diff --git a/datasource/metadata/ec2/metadata_test.go b/datasource/metadata/ec2/metadata_test.go index ff78efd..f888175 100644 --- a/datasource/metadata/ec2/metadata_test.go +++ b/datasource/metadata/ec2/metadata_test.go @@ -15,11 +15,12 @@ package ec2 import ( - "bytes" "fmt" + "net" "reflect" "testing" + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/datasource/metadata/test" "github.com/coreos/coreos-cloudinit/pkg" @@ -145,7 +146,7 @@ func TestFetchMetadata(t *testing.T) { root string metadataPath string resources map[string]string - expect []byte + expect datasource.Metadata clientErr 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/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: "/", @@ -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/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")}, @@ -199,8 +212,8 @@ func TestFetchMetadata(t *testing.T) { if Error(err) != Error(tt.expectErr) { t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err) } - if !bytes.Equal(metadata, tt.expect) { - t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata) + if !reflect.DeepEqual(tt.expect, metadata) { + t.Fatalf("bad fetch (%q): want %#v, got %#v", tt.resources, tt.expect, metadata) } } } diff --git a/datasource/proc_cmdline/proc_cmdline.go b/datasource/proc_cmdline/proc_cmdline.go index 5b05acf..64e13aa 100644 --- a/datasource/proc_cmdline/proc_cmdline.go +++ b/datasource/proc_cmdline/proc_cmdline.go @@ -20,6 +20,7 @@ import ( "log" "strings" + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/pkg" ) @@ -55,8 +56,8 @@ func (c *procCmdline) ConfigRoot() string { return "" } -func (c *procCmdline) FetchMetadata() ([]byte, error) { - return []byte{}, nil +func (c *procCmdline) FetchMetadata() (datasource.Metadata, error) { + return datasource.Metadata{}, nil } func (c *procCmdline) FetchUserdata() ([]byte, error) { diff --git a/datasource/url/url.go b/datasource/url/url.go index 68c110c..aa699bc 100644 --- a/datasource/url/url.go +++ b/datasource/url/url.go @@ -15,6 +15,7 @@ package url import ( + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/pkg" ) @@ -40,8 +41,8 @@ func (f *remoteFile) ConfigRoot() string { return "" } -func (f *remoteFile) FetchMetadata() ([]byte, error) { - return []byte{}, nil +func (f *remoteFile) FetchMetadata() (datasource.Metadata, error) { + return datasource.Metadata{}, nil } func (f *remoteFile) FetchUserdata() ([]byte, error) { diff --git a/datasource/waagent/waagent.go b/datasource/waagent/waagent.go index eccde3c..fdc543b 100644 --- a/datasource/waagent/waagent.go +++ b/datasource/waagent/waagent.go @@ -15,13 +15,14 @@ package waagent import ( - "encoding/json" "encoding/xml" "fmt" "io/ioutil" "net" "os" "path" + + "github.com/coreos/coreos-cloudinit/datasource" ) type waagent struct { @@ -46,13 +47,13 @@ func (a *waagent) ConfigRoot() string { return a.root } -func (a *waagent) FetchMetadata() ([]byte, error) { - metadataBytes, err := a.tryReadFile(path.Join(a.root, "SharedConfig.xml")) - if err != nil { - return nil, err +func (a *waagent) FetchMetadata() (metadata datasource.Metadata, err error) { + var metadataBytes []byte + if metadataBytes, err = a.tryReadFile(path.Join(a.root, "SharedConfig.xml")); err != nil { + return } if len(metadataBytes) == 0 { - return metadataBytes, nil + return } type Instance struct { @@ -74,30 +75,28 @@ func (a *waagent) FetchMetadata() ([]byte, error) { } } - var metadata SharedConfig - if err := xml.Unmarshal(metadataBytes, &metadata); err != nil { - return nil, err + var m SharedConfig + if err = xml.Unmarshal(metadataBytes, &m); err != nil { + return } var instance Instance - for _, i := range metadata.Instances.Instances { - if i.Id == metadata.Incarnation.Instance { + for _, i := range m.Instances.Instances { + if i.Id == m.Incarnation.Instance { instance = i break } } - attrs := map[string]string{ - "local-ipv4": instance.Address, - } + metadata.PrivateIPv4 = net.ParseIP(instance.Address) for _, e := range instance.InputEndpoints.Endpoints { host, _, err := net.SplitHostPort(e.LoadBalancedPublicAddress) if err == nil { - attrs["public-ipv4"] = host + metadata.PublicIPv4 = net.ParseIP(host) break } } - return json.Marshal(attrs) + return } func (a *waagent) FetchUserdata() ([]byte, error) { diff --git a/datasource/waagent/waagent_test.go b/datasource/waagent/waagent_test.go index 326c6a0..1036b03 100644 --- a/datasource/waagent/waagent_test.go +++ b/datasource/waagent/waagent_test.go @@ -15,10 +15,11 @@ package waagent import ( - "encoding/json" + "net" "reflect" "testing" + "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource/test" ) @@ -26,26 +27,23 @@ func TestFetchMetadata(t *testing.T) { for _, tt := range []struct { root string files test.MockFilesystem - metadata map[string]string + metadata datasource.Metadata }{ { - "/", - test.MockFilesystem{}, - nil, + root: "/", + files: test.MockFilesystem{}, }, { - "/", - test.MockFilesystem{"/SharedConfig.xml": ""}, - nil, + root: "/", + files: test.MockFilesystem{"/SharedConfig.xml": ""}, }, { - "/var/lib/waagent", - test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ""}, - nil, + root: "/var/lib/waagent", + files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ""}, }, { - "/var/lib/waagent", - test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ` + root: "/var/lib/waagent", + files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ` @@ -82,25 +80,19 @@ func TestFetchMetadata(t *testing.T) { `}, - map[string]string{ - "local-ipv4": "100.73.202.64", - "public-ipv4": "191.239.39.77", + metadata: datasource.Metadata{ + PrivateIPv4: net.ParseIP("100.73.202.64"), + PublicIPv4: net.ParseIP("191.239.39.77"), }, }, } { a := waagent{tt.root, tt.files.ReadFile} - metadataBytes, err := a.FetchMetadata() + metadata, err := a.FetchMetadata() if err != nil { 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) { - 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) } } } diff --git a/initialize/meta_data.go b/initialize/meta_data.go index 1140e16..35a9122 100644 --- a/initialize/meta_data.go +++ b/initialize/meta_data.go @@ -15,68 +15,46 @@ package initialize import ( - "encoding/json" "sort" "github.com/coreos/coreos-cloudinit/config" + "github.com/coreos/coreos-cloudinit/datasource" ) // 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 - } - +func ParseMetaData(metadata datasource.Metadata) *config.CloudConfig { 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]) + if len(metadata.SSHPublicKeys) > 0 { + cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHPublicKeys)) + for _, name := range sortedKeys(metadata.SSHPublicKeys) { + cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, metadata.SSHPublicKeys[name]) } } cfg.Hostname = metadata.Hostname - cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath - return &cfg, nil + cfg.NetworkConfigPath = metadata.NetworkConfigPath + return &cfg } // 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"` +func ExtractIPsFromMetadata(metadata datasource.Metadata) map[string]string { + subs := map[string]string{} + if metadata.PrivateIPv4 != nil { + subs["$private_ipv4"] = metadata.PrivateIPv4.String() } - if err := json.Unmarshal(contents, &ips); err != nil { - return nil, err + if metadata.PublicIPv4 != nil { + subs["$public_ipv4"] = metadata.PublicIPv4.String() } - m := make(map[string]string) - if ips.PrivateIPv4 != "" { - m["$private_ipv4"] = ips.PrivateIPv4 + if metadata.PrivateIPv6 != nil { + subs["$private_ipv6"] = metadata.PrivateIPv6.String() } - if ips.PublicIPv4 != "" { - m["$public_ipv4"] = ips.PublicIPv4 + if metadata.PublicIPv6 != nil { + subs["$public_ipv6"] = metadata.PublicIPv6.String() } - if ips.PrivateIPv6 != "" { - m["$private_ipv6"] = ips.PrivateIPv6 - } - if ips.PublicIPv6 != "" { - m["$public_ipv6"] = ips.PublicIPv6 - } - return m, nil + + return subs } func sortedKeys(m map[string]string) (keys []string) { diff --git a/initialize/meta_data_test.go b/initialize/meta_data_test.go index fd5b856..15c08a0 100644 --- a/initialize/meta_data_test.go +++ b/initialize/meta_data_test.go @@ -15,71 +15,29 @@ package initialize import ( + "net" "reflect" "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) { for i, tt := range []struct { - in []byte - err bool + in datasource.Metadata out map[string]string }{ { - []byte(`{"public-ipv4": "12.34.56.78", "local-ipv4": "1.2.3.4", "public-ipv6": "1234::", "local-ipv6": "5678::"}`), - false, + datasource.Metadata{ + 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::"}, }, - { - []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) - } + got := ExtractIPsFromMetadata(tt.in) if !reflect.DeepEqual(got, tt.out) { t.Errorf("case %d: got %s, want %s", i, got, tt.out) }