diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 30a250b..5876828 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -88,7 +88,16 @@ func main() { die() } - env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName) + 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) + die() + } + } + env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName, subs) + if len(userdataBytes) > 0 { if err := processUserdata(string(userdataBytes), env); err != nil { fmt.Printf("Failed to process user-data: %v\n", err) diff --git a/initialize/env.go b/initialize/env.go index 781a77c..4161898 100644 --- a/initialize/env.go +++ b/initialize/env.go @@ -17,10 +17,16 @@ type Environment struct { substitutions map[string]string } -func NewEnvironment(root, configRoot, workspace, netconfType, sshKeyName string) *Environment { - substitutions := map[string]string{ +// TODO(jonboulle): this is getting unwieldy, should be able to simplify the interface somehow +func NewEnvironment(root, configRoot, workspace, netconfType, sshKeyName string, substitutions map[string]string) *Environment { + // If certain values are not in the supplied substitution, fall back to retrieving them from the environment + for k, v := range map[string]string{ "$public_ipv4": os.Getenv("COREOS_PUBLIC_IPV4"), "$private_ipv4": os.Getenv("COREOS_PRIVATE_IPV4"), + } { + if _, ok := substitutions[k]; !ok { + substitutions[k] = v + } } return &Environment{root, configRoot, workspace, netconfType, sshKeyName, substitutions} } diff --git a/initialize/env_test.go b/initialize/env_test.go index 33ff933..756c957 100644 --- a/initialize/env_test.go +++ b/initialize/env_test.go @@ -6,22 +6,47 @@ import ( ) func TestEnvironmentApply(t *testing.T) { - os.Setenv("COREOS_PUBLIC_IPV4", "192.0.2.3") - os.Setenv("COREOS_PRIVATE_IPV4", "192.0.2.203") - env := NewEnvironment("./", "./", "./", "", "") - input := `[Service] + os.Setenv("COREOS_PUBLIC_IPV4", "1.2.3.4") + os.Setenv("COREOS_PRIVATE_IPV4", "5.6.7.8") + for _, tt := range []struct { + subs map[string]string + input string + out string + }{ + { + // Substituting both values directly should always take precedence + // over environment variables + map[string]string{ + "$public_ipv4": "192.0.2.3", + "$private_ipv4": "192.0.2.203", + }, + `[Service] ExecStart=/usr/bin/echo "$public_ipv4" ExecStop=/usr/bin/echo $private_ipv4 -ExecStop=/usr/bin/echo $unknown -` - expected := `[Service] +ExecStop=/usr/bin/echo $unknown`, + `[Service] ExecStart=/usr/bin/echo "192.0.2.3" ExecStop=/usr/bin/echo 192.0.2.203 -ExecStop=/usr/bin/echo $unknown -` +ExecStop=/usr/bin/echo $unknown`, + }, + { + // Substituting one value directly while falling back with the other + map[string]string{"$private_ipv4": "127.0.0.1"}, + "$private_ipv4\n$public_ipv4", + "127.0.0.1\n1.2.3.4", + }, + { + // Falling back to environment variables for both values + map[string]string{"foo": "bar"}, + "$private_ipv4\n$public_ipv4", + "5.6.7.8\n1.2.3.4", + }, + } { - output := env.Apply(input) - if output != expected { - t.Fatalf("Environment incorrectly applied.\nOutput:\n%s\nExpected:\n%s", output, expected) + env := NewEnvironment("./", "./", "./", "", "", tt.subs) + got := env.Apply(tt.input) + if got != tt.out { + t.Fatalf("Environment incorrectly applied.\ngot:\n%s\nwant:\n%s", got, tt.out) + } } } diff --git a/initialize/meta_data.go b/initialize/meta_data.go index d83b8b5..5168d36 100644 --- a/initialize/meta_data.go +++ b/initialize/meta_data.go @@ -1,9 +1,9 @@ package initialize -import ( - "encoding/json" -) +import "encoding/json" +// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and +// converts it to a CloudConfig func ParseMetaData(contents string) (cfg CloudConfig, err error) { var metadata struct { SSHAuthorizedKeyMap map[string]string `json:"public_keys"` @@ -24,3 +24,23 @@ func ParseMetaData(contents string) (cfg CloudConfig, err error) { cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath return } + +// ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service format, +// and returns a substitution map possibly containing private_ipv4 and public_ipv4 addresses +func ExtractIPsFromMetadata(contents []byte) (map[string]string, error) { + var ips struct { + Public string `json:"public-ipv4"` + Private string `json:"local-ipv4"` + } + if err := json.Unmarshal(contents, &ips); err != nil { + return nil, err + } + m := make(map[string]string) + if ips.Private != "" { + m["$private_ipv4"] = ips.Private + } + if ips.Public != "" { + m["$public_ipv4"] = ips.Public + } + return m, nil +} diff --git a/initialize/meta_data_test.go b/initialize/meta_data_test.go new file mode 100644 index 0000000..a362243 --- /dev/null +++ b/initialize/meta_data_test.go @@ -0,0 +1,36 @@ +package initialize + +import "reflect" +import "testing" + +func TestExtractIPsFromMetadata(t *testing.T) { + for i, tt := range []struct { + in []byte + err bool + out map[string]string + }{ + { + []byte(`{"public-ipv4": "12.34.56.78", "local-ipv4": "1.2.3.4"}`), + false, + map[string]string{"$public_ipv4": "12.34.56.78", "$private_ipv4": "1.2.3.4"}, + }, + { + []byte(`{"local-ipv4": "127.0.0.1", "something_else": "don't care"}`), + false, + map[string]string{"$private_ipv4": "127.0.0.1"}, + }, + { + []byte(`garbage`), + true, + nil, + }, + } { + got, err := ExtractIPsFromMetadata(tt.in) + if (err != nil) != tt.err { + t.Errorf("bad error state (got %t, want %t)", err != nil, tt.err) + } + if !reflect.DeepEqual(got, tt.out) { + t.Errorf("case %d: got %s, want %s", i, got, tt.out) + } + } +}