From 983501e43be6a995b2bab21ed07dffa92f9c8f4b Mon Sep 17 00:00:00 2001 From: Michael Marineau Date: Thu, 10 Jul 2014 15:44:52 -0700 Subject: [PATCH] environment: add support for updating /etc/environment with IP values To maintain the behavior of the coreos-setup-environment that has started to move into cloudinit we need to write out /etc/environment with the public and private addresses, if known. The file is updated so that other contents are not replaced. This behavior is disabled entirely if /etc/environment was written by a write_files entry. --- initialize/config.go | 19 +++++++++++++++-- initialize/env.go | 22 +++++++++++++++++++ initialize/env_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++ system/env_file.go | 9 +++++--- 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/initialize/config.go b/initialize/config.go index 4a73997..8c08e6e 100644 --- a/initialize/config.go +++ b/initialize/config.go @@ -223,12 +223,27 @@ func Apply(cfg CloudConfig, env *Environment) error { cfg.Coreos.Units = append(cfg.Coreos.Units, u...) } + wroteEnvironment := false for _, file := range cfg.WriteFiles { - path, err := system.WriteFile(&file, env.Root()) + fullPath, err := system.WriteFile(&file, env.Root()) if err != nil { return err } - log.Printf("Wrote file %s to filesystem", path) + if path.Clean(file.Path) == "/etc/environment" { + wroteEnvironment = true + } + log.Printf("Wrote file %s to filesystem", fullPath) + } + + if !wroteEnvironment { + ef := env.DefaultEnvironmentFile() + if ef != nil { + err := system.WriteEnvFile(ef, env.Root()) + if err != nil { + return err + } + log.Printf("Updated /etc/environment") + } } if env.NetconfType() != "" { diff --git a/initialize/env.go b/initialize/env.go index 2019c3e..cae32f7 100644 --- a/initialize/env.go +++ b/initialize/env.go @@ -4,6 +4,8 @@ import ( "os" "path" "strings" + + "github.com/coreos/coreos-cloudinit/system" ) const DefaultSSHKeyName = "coreos-cloudinit" @@ -65,6 +67,26 @@ func (e *Environment) Apply(data string) string { return data } +func (e *Environment) DefaultEnvironmentFile() *system.EnvFile { + ef := system.EnvFile{ + File: &system.File{ + Path: "/etc/environment", + }, + Vars: map[string]string{}, + } + if ip, ok := e.substitutions["$public_ipv4"]; ok && len(ip) > 0 { + ef.Vars["COREOS_PUBLIC_IPV4"] = ip + } + if ip, ok := e.substitutions["$private_ipv4"]; ok && len(ip) > 0 { + ef.Vars["COREOS_PRIVATE_IPV4"] = ip + } + if len(ef.Vars) == 0 { + return nil + } else { + return &ef + } +} + // normalizeSvcEnv standardizes the keys of the map (environment variables for a service) // by replacing any dashes with underscores and ensuring they are entirely upper case. // For example, "some-env" --> "SOME_ENV" diff --git a/initialize/env_test.go b/initialize/env_test.go index b48ae21..d5178a5 100644 --- a/initialize/env_test.go +++ b/initialize/env_test.go @@ -1,8 +1,12 @@ package initialize import ( + "io/ioutil" "os" + "path" "testing" + + "github.com/coreos/coreos-cloudinit/system" ) func TestEnvironmentApply(t *testing.T) { @@ -56,3 +60,47 @@ ExecStop=/usr/bin/echo $unknown`, } } } + +func TestEnvironmentFile(t *testing.T) { + subs := map[string]string{ + "$public_ipv4": "1.2.3.4", + "$private_ipv4": "5.6.7.8", + } + expect := "COREOS_PUBLIC_IPV4=1.2.3.4\nCOREOS_PRIVATE_IPV4=5.6.7.8\n" + + dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") + if err != nil { + t.Fatalf("Unable to create tempdir: %v", err) + } + defer os.RemoveAll(dir) + + env := NewEnvironment("./", "./", "./", "", "", subs) + ef := env.DefaultEnvironmentFile() + err = system.WriteEnvFile(ef, dir) + if err != nil { + t.Fatalf("WriteEnvFile failed: %v", err) + } + + fullPath := path.Join(dir, "etc", "environment") + contents, err := ioutil.ReadFile(fullPath) + if err != nil { + t.Fatalf("Unable to read expected file: %v", err) + } + + if string(contents) != expect { + t.Fatalf("File has incorrect contents: %q", contents) + } +} + +func TestEnvironmentFileNil(t *testing.T) { + subs := map[string]string{ + "$public_ipv4": "", + "$private_ipv4": "", + } + + env := NewEnvironment("./", "./", "./", "", "", subs) + ef := env.DefaultEnvironmentFile() + if ef != nil { + t.Fatalf("Environment file not nil: %v", ef) + } +} diff --git a/system/env_file.go b/system/env_file.go index b860d48..83521ea 100644 --- a/system/env_file.go +++ b/system/env_file.go @@ -22,7 +22,10 @@ var validKey = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) // match each line, optionally capturing valid identifiers, discarding dos line endings var lineLexer = regexp.MustCompile(`(?m)^((?:([a-zA-Z0-9_]+)=)?.*?)\r?\n`) -func updateEnv(old []byte, pending map[string]string) []byte { +// mergeEnvContents: Update the existing file contents with new values, +// preserving variable ordering and all content this code doesn't understand. +// All new values are appended to the bottom of the old. +func mergeEnvContents(old []byte, pending map[string]string) []byte { var buf bytes.Buffer var match [][]byte @@ -53,7 +56,7 @@ func updateEnv(old []byte, pending map[string]string) []byte { // Existing ordering and any unknown formatting such as comments are // preserved. If no changes are required the file is untouched. func WriteEnvFile(ef *EnvFile, root string) error { - // validate new keys, updateEnv uses pending to track writes + // validate new keys, mergeEnvContents uses pending to track writes pending := make(map[string]string, len(ef.Vars)) for key, value := range ef.Vars { if !validKey.MatchString(key) { @@ -75,7 +78,7 @@ func WriteEnvFile(ef *EnvFile, root string) error { } } - newContent := updateEnv(oldContent, pending) + newContent := mergeEnvContents(oldContent, pending) if bytes.Equal(oldContent, newContent) { return nil }