Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5b5ffea126 | ||
|
18068e9375 | ||
|
1b3cabb035 | ||
|
1be2bec1c2 | ||
|
f3bd5f543e | ||
|
660feb59b9 | ||
|
9673dbe12b | ||
|
2be435dd83 | ||
|
2d91369596 | ||
|
d8d3928978 | ||
|
7fcc540154 | ||
|
cb7fbd4668 | ||
|
d4e048a1f4 | ||
|
231c0fa20b | ||
|
1aabacc769 | ||
|
6a2927d701 | ||
|
126188510b | ||
|
4627ccb444 | ||
|
aff372111a | ||
|
c7081b9918 |
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "0.8.0"
|
version = "0.8.4"
|
||||||
datasourceInterval = 100 * time.Millisecond
|
datasourceInterval = 100 * time.Millisecond
|
||||||
datasourceMaxInterval = 30 * time.Second
|
datasourceMaxInterval = 30 * time.Second
|
||||||
datasourceTimeout = 5 * time.Minute
|
datasourceTimeout = 5 * time.Minute
|
||||||
@@ -102,6 +102,7 @@ func main() {
|
|||||||
die()
|
die()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract IPv4 addresses from metadata if possible
|
||||||
var subs map[string]string
|
var subs map[string]string
|
||||||
if len(metadataBytes) > 0 {
|
if len(metadataBytes) > 0 {
|
||||||
subs, err = initialize.ExtractIPsFromMetadata(metadataBytes)
|
subs, err = initialize.ExtractIPsFromMetadata(metadataBytes)
|
||||||
@@ -110,29 +111,88 @@ func main() {
|
|||||||
die()
|
die()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply environment to user-data
|
||||||
env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName, subs)
|
env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName, subs)
|
||||||
|
userdata := env.Apply(string(userdataBytes))
|
||||||
|
|
||||||
if len(metadataBytes) > 0 {
|
var ccm, ccu *initialize.CloudConfig
|
||||||
if err := processMetadata(string(metadataBytes), env); err != nil {
|
var script *system.Script
|
||||||
fmt.Printf("Failed to process meta-data: %v\n", err)
|
if ccm, err = initialize.ParseMetaData(string(metadataBytes)); err != nil {
|
||||||
|
fmt.Printf("Failed to parse meta-data: %v\n", err)
|
||||||
die()
|
die()
|
||||||
}
|
}
|
||||||
|
if ud, err := initialize.ParseUserData(userdata); err != nil {
|
||||||
|
fmt.Printf("Failed to parse user-data: %v\n", err)
|
||||||
|
die()
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("No meta-data to handle.")
|
switch t := ud.(type) {
|
||||||
|
case *initialize.CloudConfig:
|
||||||
|
ccu = t
|
||||||
|
case system.Script:
|
||||||
|
script = &t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(userdataBytes) > 0 {
|
var cc *initialize.CloudConfig
|
||||||
if err := processUserdata(string(userdataBytes), env); err != nil {
|
if ccm != nil && ccu != nil {
|
||||||
fmt.Printf("Failed to process user-data: %v\n", err)
|
fmt.Println("Merging cloud-config from meta-data and user-data")
|
||||||
if !ignoreFailure {
|
merged := mergeCloudConfig(*ccm, *ccu)
|
||||||
|
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 {
|
||||||
|
if err = initialize.Apply(*cc, env); err != nil {
|
||||||
|
fmt.Printf("Failed to apply cloud-config: %v\n", err)
|
||||||
die()
|
die()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fmt.Println("No user-data to handle.")
|
if script != nil {
|
||||||
|
if err = runScript(*script, env); err != nil {
|
||||||
|
fmt.Printf("Failed to run script: %v\n", err)
|
||||||
|
die()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeCloudConfig merges certain options from mdcc (a CloudConfig derived from
|
||||||
|
// meta-data) onto udcc (a CloudConfig derived from user-data), if they are
|
||||||
|
// not already set on udcc (i.e. user-data always takes precedence)
|
||||||
|
// NB: This needs to be kept in sync with ParseMetadata so that it tracks all
|
||||||
|
// elements of a CloudConfig which that function can populate.
|
||||||
|
func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudConfig) {
|
||||||
|
if mdcc.Hostname != "" {
|
||||||
|
if udcc.Hostname != "" {
|
||||||
|
fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)", udcc.Hostname, mdcc.Hostname)
|
||||||
|
} else {
|
||||||
|
udcc.Hostname = mdcc.Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
for _, key := range mdcc.SSHAuthorizedKeys {
|
||||||
|
udcc.SSHAuthorizedKeys = append(udcc.SSHAuthorizedKeys, key)
|
||||||
|
}
|
||||||
|
if mdcc.NetworkConfigPath != "" {
|
||||||
|
if udcc.NetworkConfigPath != "" {
|
||||||
|
fmt.Printf("Warning: user-data NetworkConfigPath %s overrides metadata NetworkConfigPath %s", udcc.NetworkConfigPath, mdcc.NetworkConfigPath)
|
||||||
|
} else {
|
||||||
|
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return udcc
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDatasources creates a slice of possible Datasources for cloudinit based
|
||||||
|
// on the different source command-line flags.
|
||||||
func getDatasources() []datasource.Datasource {
|
func getDatasources() []datasource.Datasource {
|
||||||
dss := make([]datasource.Datasource, 0, 5)
|
dss := make([]datasource.Datasource, 0, 5)
|
||||||
if sources.file != "" {
|
if sources.file != "" {
|
||||||
@@ -153,6 +213,11 @@ func getDatasources() []datasource.Datasource {
|
|||||||
return dss
|
return dss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selectDatasource attempts to choose a valid Datasource to use based on its
|
||||||
|
// current availability. The first Datasource to report to be available is
|
||||||
|
// returned. Datasources will be retried if possible if they are not
|
||||||
|
// immediately available. If all Datasources are permanently unavailable or
|
||||||
|
// datasourceTimeout is reached before one becomes available, nil is returned.
|
||||||
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
||||||
ds := make(chan datasource.Datasource)
|
ds := make(chan datasource.Datasource)
|
||||||
stop := make(chan struct{})
|
stop := make(chan struct{})
|
||||||
@@ -199,48 +264,18 @@ func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func processUserdata(userdata string, env *initialize.Environment) error {
|
// TODO(jonboulle): this should probably be refactored and moved into a different module
|
||||||
userdata = env.Apply(userdata)
|
func runScript(script system.Script, env *initialize.Environment) error {
|
||||||
|
err := initialize.PrepWorkspace(env.Workspace())
|
||||||
parsed, err := initialize.ParseUserData(userdata)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed parsing user-data: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = initialize.PrepWorkspace(env.Workspace())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed preparing workspace: %v\n", err)
|
fmt.Printf("Failed preparing workspace: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, err := initialize.PersistScriptInWorkspace(script, env.Workspace())
|
||||||
switch t := parsed.(type) {
|
|
||||||
case initialize.CloudConfig:
|
|
||||||
err = initialize.Apply(t, env)
|
|
||||||
case system.Script:
|
|
||||||
var path string
|
|
||||||
path, err = initialize.PersistScriptInWorkspace(t, env.Workspace())
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var name string
|
var name string
|
||||||
name, err = system.ExecuteScript(path)
|
name, err = system.ExecuteScript(path)
|
||||||
initialize.PersistUnitNameInWorkspace(name, env.Workspace())
|
initialize.PersistUnitNameInWorkspace(name, env.Workspace())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func processMetadata(metadata string, env *initialize.Environment) error {
|
|
||||||
parsed, err := initialize.ParseMetaData(metadata)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed parsing meta-data: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initialize.PrepWorkspace(env.Workspace())
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed preparing workspace: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialize.Apply(parsed, env)
|
|
||||||
}
|
|
||||||
|
110
coreos-cloudinit_test.go
Normal file
110
coreos-cloudinit_test.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/initialize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeCloudConfig(t *testing.T) {
|
||||||
|
simplecc := initialize.CloudConfig{
|
||||||
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
|
Hostname: "foobar",
|
||||||
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
}
|
||||||
|
for i, tt := range []struct {
|
||||||
|
udcc initialize.CloudConfig
|
||||||
|
mdcc initialize.CloudConfig
|
||||||
|
want initialize.CloudConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// If mdcc is empty, udcc should be returned unchanged
|
||||||
|
simplecc,
|
||||||
|
initialize.CloudConfig{},
|
||||||
|
simplecc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// If udcc is empty, mdcc should be returned unchanged(overridden)
|
||||||
|
initialize.CloudConfig{},
|
||||||
|
simplecc,
|
||||||
|
simplecc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// user-data should override completely in the case of conflicts
|
||||||
|
simplecc,
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "meta-hostname",
|
||||||
|
NetworkConfigPath: "/path/meta",
|
||||||
|
},
|
||||||
|
simplecc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Mixed merge should succeed
|
||||||
|
initialize.CloudConfig{
|
||||||
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
|
Hostname: "user-hostname",
|
||||||
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
SSHAuthorizedKeys: []string{"woof", "qux"},
|
||||||
|
Hostname: "meta-hostname",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
||||||
|
Hostname: "user-hostname",
|
||||||
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Completely non-conflicting merge should be fine
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "supercool",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
|
NetworkConfigPath: "/dev/fun",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "supercool",
|
||||||
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
|
NetworkConfigPath: "/dev/fun",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Non-mergeable settings in user-data should not be affected
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "mememe",
|
||||||
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "youyouyou",
|
||||||
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "mememe",
|
||||||
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Non-mergeable (unexpected) settings in meta-data are ignored
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "mememe",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
},
|
||||||
|
initialize.CloudConfig{
|
||||||
|
Hostname: "mememe",
|
||||||
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/pkg"
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
@@ -23,7 +24,7 @@ const (
|
|||||||
BaseUrl = "http://169.254.169.254/"
|
BaseUrl = "http://169.254.169.254/"
|
||||||
Ec2ApiVersion = "2009-04-04"
|
Ec2ApiVersion = "2009-04-04"
|
||||||
Ec2UserdataUrl = BaseUrl + Ec2ApiVersion + "/user-data"
|
Ec2UserdataUrl = BaseUrl + Ec2ApiVersion + "/user-data"
|
||||||
Ec2MetadataUrl = BaseUrl + Ec2ApiVersion + "/meta-data/"
|
Ec2MetadataUrl = BaseUrl + Ec2ApiVersion + "/meta-data"
|
||||||
OpenstackApiVersion = "openstack/2012-08-10"
|
OpenstackApiVersion = "openstack/2012-08-10"
|
||||||
OpenstackUserdataUrl = BaseUrl + OpenstackApiVersion + "/user_data"
|
OpenstackUserdataUrl = BaseUrl + OpenstackApiVersion + "/user_data"
|
||||||
OpenstackMetadataUrl = BaseUrl + OpenstackApiVersion + "/meta_data.json"
|
OpenstackMetadataUrl = BaseUrl + OpenstackApiVersion + "/meta_data.json"
|
||||||
@@ -78,10 +79,45 @@ func fetchMetadata(client getter) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs, err := fetchChildAttributes(client, Ec2MetadataUrl)
|
attrs := make(map[string]interface{})
|
||||||
|
if keynames, err := fetchAttributes(client, fmt.Sprintf("%s/public-keys", Ec2MetadataUrl)); 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\n", keyname)
|
||||||
|
}
|
||||||
|
keyIDs[tokens[1]] = tokens[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make(map[string]string)
|
||||||
|
for name, id := range keyIDs {
|
||||||
|
sshkey, err := fetchAttribute(client, fmt.Sprintf("%s/public-keys/%s/openssh-key", Ec2MetadataUrl, id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
keys[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
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostname, err := fetchAttribute(client, fmt.Sprintf("%s/hostname", Ec2MetadataUrl)); err == nil {
|
||||||
|
attrs["hostname"] = hostname
|
||||||
|
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if content_path, err := fetchAttribute(client, fmt.Sprintf("%s/network_config/content_path", Ec2MetadataUrl)); err == 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)
|
return json.Marshal(attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,37 +129,15 @@ func fetchAttributes(client getter, url string) ([]string, error) {
|
|||||||
scanner := bufio.NewScanner(bytes.NewBuffer(resp))
|
scanner := bufio.NewScanner(bytes.NewBuffer(resp))
|
||||||
data := make([]string, 0)
|
data := make([]string, 0)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
data = append(data, strings.Split(scanner.Text(), "=")[0])
|
data = append(data, scanner.Text())
|
||||||
}
|
}
|
||||||
return data, scanner.Err()
|
return data, scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAttribute(client getter, url string) (interface{}, error) {
|
func fetchAttribute(client getter, url string) (string, error) {
|
||||||
if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 {
|
if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 {
|
||||||
return attrs[0], nil
|
return attrs[0], nil
|
||||||
} else {
|
} else {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChildAttributes(client getter, url string) (interface{}, error) {
|
|
||||||
attrs := make(map[string]interface{})
|
|
||||||
attrList, err := fetchAttributes(client, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, attr := range attrList {
|
|
||||||
var fetchFunc func(getter, string) (interface{}, error)
|
|
||||||
if strings.HasSuffix(attr, "/") {
|
|
||||||
fetchFunc = fetchChildAttributes
|
|
||||||
} else {
|
|
||||||
fetchFunc = fetchAttribute
|
|
||||||
}
|
|
||||||
if value, err := fetchFunc(client, url+attr); err == nil {
|
|
||||||
attrs[strings.TrimSuffix(attr, "/")] = value
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attrs, nil
|
|
||||||
}
|
|
||||||
|
@@ -137,15 +137,13 @@ func TestFetchMetadata(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/": "a\nb\nc/",
|
"http://169.254.169.254/2009-04-04/meta-data/hostname": "host",
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/c/": "d\ne/",
|
"http://169.254.169.254/2009-04-04/meta-data/public-keys": "0=test1\n",
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/c/e/": "f",
|
"http://169.254.169.254/2009-04-04/meta-data/public-keys/0": "openssh-key",
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/a": "1",
|
"http://169.254.169.254/2009-04-04/meta-data/public-keys/0/openssh-key": "key",
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/b": "2",
|
"http://169.254.169.254/2009-04-04/meta-data/network_config/content_path": "path",
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/c/d": "3",
|
|
||||||
"http://169.254.169.254/2009-04-04/meta-data/c/e/f": "4",
|
|
||||||
},
|
},
|
||||||
expect: []byte(`{"a":"1","b":"2","c":{"d":"3","e":{"f":"4"}}}`),
|
expect: []byte(`{"hostname":"host","network_config":{"content_path":"path"},"public_keys":{"test1":"key"}}`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
@@ -154,7 +152,6 @@ func TestFetchMetadata(t *testing.T) {
|
|||||||
expect: []byte("test"),
|
expect: []byte("test"),
|
||||||
},
|
},
|
||||||
{err: pkg.ErrTimeout{fmt.Errorf("test error")}},
|
{err: pkg.ErrTimeout{fmt.Errorf("test error")}},
|
||||||
{err: pkg.ErrNotFound{fmt.Errorf("test error")}},
|
|
||||||
} {
|
} {
|
||||||
client := &TestHttpClient{tt.metadata, tt.err}
|
client := &TestHttpClient{tt.metadata, tt.err}
|
||||||
metadata, err := fetchMetadata(client)
|
metadata, err := fetchMetadata(client)
|
||||||
|
@@ -3,8 +3,11 @@ package initialize
|
|||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and
|
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and
|
||||||
// converts it to a CloudConfig
|
// converts it to a partially hydrated CloudConfig
|
||||||
func ParseMetaData(contents string) (cfg CloudConfig, err error) {
|
func ParseMetaData(contents string) (*CloudConfig, error) {
|
||||||
|
if len(contents) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
var metadata struct {
|
var metadata struct {
|
||||||
SSHAuthorizedKeyMap map[string]string `json:"public_keys"`
|
SSHAuthorizedKeyMap map[string]string `json:"public_keys"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
@@ -12,17 +15,20 @@ func ParseMetaData(contents string) (cfg CloudConfig, err error) {
|
|||||||
ContentPath string `json:"content_path"`
|
ContentPath string `json:"content_path"`
|
||||||
} `json:"network_config"`
|
} `json:"network_config"`
|
||||||
}
|
}
|
||||||
if err = json.Unmarshal([]byte(contents), &metadata); err != nil {
|
if err := json.Unmarshal([]byte(contents), &metadata); err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cfg CloudConfig
|
||||||
|
if len(metadata.SSHAuthorizedKeyMap) > 0 {
|
||||||
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
||||||
for _, key := range metadata.SSHAuthorizedKeyMap {
|
for _, key := range metadata.SSHAuthorizedKeyMap {
|
||||||
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, key)
|
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, key)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cfg.Hostname = metadata.Hostname
|
cfg.Hostname = metadata.Hostname
|
||||||
cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath
|
cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath
|
||||||
return
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service format,
|
// ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service format,
|
||||||
|
@@ -3,6 +3,39 @@ package initialize
|
|||||||
import "reflect"
|
import "reflect"
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
func TestParseMetadata(t *testing.T) {
|
||||||
|
for i, tt := range []struct {
|
||||||
|
in string
|
||||||
|
want *CloudConfig
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"", nil, false},
|
||||||
|
{`garbage, invalid json`, nil, true},
|
||||||
|
{`{"foo": "bar"}`, &CloudConfig{}, false},
|
||||||
|
{`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false},
|
||||||
|
{`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false},
|
||||||
|
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"jill", "alice"}}, false},
|
||||||
|
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &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 []byte
|
||||||
|
@@ -9,6 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ParseUserData(contents string) (interface{}, error) {
|
func ParseUserData(contents string) (interface{}, error) {
|
||||||
|
if len(contents) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
header := strings.SplitN(contents, "\n", 2)[0]
|
header := strings.SplitN(contents, "\n", 2)[0]
|
||||||
|
|
||||||
// Explicitly trim the header so we can handle user-data from
|
// Explicitly trim the header so we can handle user-data from
|
||||||
@@ -19,14 +22,9 @@ func ParseUserData(contents string) (interface{}, error) {
|
|||||||
if strings.HasPrefix(header, "#!") {
|
if strings.HasPrefix(header, "#!") {
|
||||||
log.Printf("Parsing user-data as script")
|
log.Printf("Parsing user-data as script")
|
||||||
return system.Script(contents), nil
|
return system.Script(contents), nil
|
||||||
|
|
||||||
} else if header == "#cloud-config" {
|
} else if header == "#cloud-config" {
|
||||||
log.Printf("Parsing user-data as cloud-config")
|
log.Printf("Parsing user-data as cloud-config")
|
||||||
cfg, err := NewCloudConfig(contents)
|
return NewCloudConfig(contents)
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
return *cfg, nil
|
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
|
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ func TestParseConfigCRLF(t *testing.T) {
|
|||||||
t.Fatalf("Failed parsing config: %v", err)
|
t.Fatalf("Failed parsing config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := ud.(CloudConfig)
|
cfg := ud.(*CloudConfig)
|
||||||
|
|
||||||
if cfg.Hostname != "foo" {
|
if cfg.Hostname != "foo" {
|
||||||
t.Error("Failed parsing hostname from config")
|
t.Error("Failed parsing hostname from config")
|
||||||
@@ -47,3 +47,12 @@ func TestParseConfigCRLF(t *testing.T) {
|
|||||||
t.Error("Parsed incorrect number of SSH keys")
|
t.Error("Parsed incorrect number of SSH keys")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseConfigEmpty(t *testing.T) {
|
||||||
|
i, e := ParseUserData(``)
|
||||||
|
if i != nil {
|
||||||
|
t.Error("ParseUserData of empty string returned non-nil unexpectedly")
|
||||||
|
} else if e != nil {
|
||||||
|
t.Error("ParseUserData of empty string returned error unexpectedly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
test
2
test
@@ -18,7 +18,7 @@ declare -a TESTPKGS=(initialize system datasource pkg network)
|
|||||||
if [ -z "$PKG" ]; then
|
if [ -z "$PKG" ]; then
|
||||||
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
||||||
# prepend repo path to each package
|
# prepend repo path to each package
|
||||||
TESTPKGS=${TESTPKGS[@]/#/${REPO_PATH}/}
|
TESTPKGS="${TESTPKGS[@]/#/${REPO_PATH}/} ./"
|
||||||
else
|
else
|
||||||
GOFMTPATH="$TESTPKGS"
|
GOFMTPATH="$TESTPKGS"
|
||||||
# strip out slashes and dots from PKG=./foo/
|
# strip out slashes and dots from PKG=./foo/
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Load cloud-config from /media/configdrive
|
Description=Load cloud-config from /media/configdrive
|
||||||
Requires=coreos-setup-environment.service
|
Requires=coreos-setup-environment.service
|
||||||
After=coreos-setup-environment.service
|
After=coreos-setup-environment.service system-config.target
|
||||||
Before=user-config.target
|
Before=user-config.target
|
||||||
|
|
||||||
# HACK: work around ordering between config drive and ec2 metadata It is
|
# HACK: work around ordering between config drive and ec2 metadata It is
|
||||||
@@ -13,7 +13,6 @@ Before=user-config.target
|
|||||||
# systemd knows about the ordering as early as possible.
|
# systemd knows about the ordering as early as possible.
|
||||||
# coreos-cloudinit could implement a simple lock but that cannot be used
|
# coreos-cloudinit could implement a simple lock but that cannot be used
|
||||||
# until after the systemd dbus calls are made non-blocking.
|
# until after the systemd dbus calls are made non-blocking.
|
||||||
After=system-cloudinit@usr-share-oem-cloud\x2dconfig.yml.service
|
|
||||||
After=ec2-cloudinit.service
|
After=ec2-cloudinit.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Reference in New Issue
Block a user