Merge pull request #25 from bcwaldon/etcd-env-file
Write environment file from coreos.etcd options
This commit is contained in:
		| @@ -74,12 +74,38 @@ Provide a list of objects with the following attributes: | ||||
|  | ||||
| ## Custom cloud-config Parameters | ||||
|  | ||||
| ### coreos.etcd.discovery_url | ||||
| ### coreos.etcd | ||||
|  | ||||
| The value of `coreos.etcd.discovery_url` will be used to discover the instance's etcd peers using the [etcd discovery protocol][disco-proto]. Usage of the [public discovery service][disco-service] is encouraged. | ||||
| The `coreos.etcd.*` options are translated to a partial systemd unit acting as an etcd configuration file. | ||||
| `coreos-cloudinit` will also replace the strings `$private_ipv4` and `$public_ipv4` with the values generated by CoreOS based on a given provider. | ||||
|  | ||||
| [disco-proto]: https://github.com/coreos/etcd/blob/master/Documentation/discovery-protocol.md | ||||
| [disco-service]: http://discovery.etcd.io | ||||
| For example, the following cloud-config document... | ||||
|  | ||||
| ``` | ||||
| #cloud-config | ||||
|  | ||||
| coreos: | ||||
|     etcd: | ||||
|         name: node001 | ||||
|         discovery-url: https://discovery.etcd.io/3445fa65423d8b04df07f59fb40218f8 | ||||
|         bind-addr: $public_ipv4:4001 | ||||
|         peer-bind-addr: $private_ipv4:7001 | ||||
| ``` | ||||
|  | ||||
| ...will generate a systemd snippet like this: | ||||
|  | ||||
| ``` | ||||
| [Service] | ||||
| Environment="ETCD_NAME=node001"" | ||||
| Environment="ETCD_DISCOVERY_URL=https://discovery.etcd.io/3445fa65423d8b04df07f59fb40218f8" | ||||
| Environment="ETCD_BIND_ADDR=203.0.113.29:4001" | ||||
| Environment="ETCD_PEER_BIND_ADDR=192.0.2.13:7001" | ||||
| ``` | ||||
|  | ||||
| For more information about the available configuration options, see the [etcd documentation][etcd-config]. | ||||
| Note that hyphens in the coreos.etcd.* keys are mapped to underscores. | ||||
|  | ||||
| [etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md | ||||
|  | ||||
| ### coreos.units | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ const DefaultSSHKeyName = "coreos-cloudinit" | ||||
| type CloudConfig struct { | ||||
| 	SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` | ||||
| 	Coreos            struct { | ||||
| 		Etcd  struct{ Discovery_URL string } | ||||
| 		Etcd  EtcdEnvironment | ||||
| 		Fleet struct{ Autostart bool } | ||||
| 		Units []Unit | ||||
| 	} | ||||
| @@ -98,13 +98,12 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if cfg.Coreos.Etcd.Discovery_URL != "" { | ||||
| 		err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL) | ||||
| 		if err == nil { | ||||
| 			log.Printf("Consumed etcd discovery url") | ||||
| 		} else { | ||||
| 			log.Fatalf("Failed to persist etcd discovery url to filesystem: %v", err) | ||||
| 	if len(cfg.Coreos.Etcd) > 0 { | ||||
| 		if err := WriteEtcdEnvironment("/", cfg.Coreos.Etcd); err != nil { | ||||
| 			log.Fatalf("Failed to write etcd config to filesystem: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		log.Printf("Wrote etcd config file to filesystem") | ||||
| 	} | ||||
|  | ||||
| 	if len(cfg.Coreos.Units) > 0 { | ||||
|   | ||||
| @@ -17,10 +17,6 @@ func TestCloudConfigEmpty(t *testing.T) { | ||||
| 		t.Error("Parsed incorrect number of SSH keys") | ||||
| 	} | ||||
|  | ||||
| 	if cfg.Coreos.Etcd.Discovery_URL != "" { | ||||
| 		t.Error("Parsed incorrect value of discovery url") | ||||
| 	} | ||||
|  | ||||
| 	if cfg.Coreos.Fleet.Autostart { | ||||
| 		t.Error("Expected AutostartFleet not to be defined") | ||||
| 	} | ||||
| @@ -81,10 +77,6 @@ hostname: trontastic | ||||
| 		t.Error("Expected first SSH key to be 'foobaz'") | ||||
| 	} | ||||
|  | ||||
| 	if cfg.Coreos.Etcd.Discovery_URL != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" { | ||||
| 		t.Error("Failed to parse etcd discovery url") | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.Coreos.Fleet.Autostart { | ||||
| 		t.Error("Expected AutostartFleet to be true") | ||||
| 	} | ||||
|   | ||||
| @@ -1,25 +1,48 @@ | ||||
| package cloudinit | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	etcdDiscoveryPath = "/var/run/etcd/bootstrap.disco" | ||||
| ) | ||||
| type EtcdEnvironment map[string]string | ||||
|  | ||||
| func PersistEtcdDiscoveryURL(url string) error { | ||||
| 	dir := path.Dir(etcdDiscoveryPath) | ||||
| 	if _, err := os.Stat(dir); err != nil { | ||||
| 		log.Printf("Creating directory /var/run/etcd") | ||||
| 		err := os.MkdirAll(dir, os.FileMode(0644)) | ||||
| 		if err != nil { | ||||
| func (ec EtcdEnvironment) String() (out string) { | ||||
| 	public := os.Getenv("COREOS_PUBLIC_IPV4") | ||||
| 	private := os.Getenv("COREOS_PRIVATE_IPV4") | ||||
|  | ||||
| 	out += "[Service]\n" | ||||
|  | ||||
| 	for key, val := range ec { | ||||
| 		key = strings.ToUpper(key) | ||||
| 		key = strings.Replace(key, "-", "_", -1) | ||||
|  | ||||
| 		if public != "" { | ||||
| 			val = strings.Replace(val, "$public_ipv4", public, -1) | ||||
| 		} | ||||
|  | ||||
| 		if private != "" { | ||||
| 			val = strings.Replace(val, "$private_ipv4", private, -1) | ||||
| 		} | ||||
|  | ||||
| 		out += fmt.Sprintf("Environment=\"ETCD_%s=%s\"\n", key, val) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Write an EtcdEnvironment to the appropriate path on disk for etcd.service | ||||
| func WriteEtcdEnvironment(root string, env EtcdEnvironment) error { | ||||
| 	cfgDir := path.Join(root, "etc", "systemd", "system", "etcd.service.d") | ||||
| 	cfgFile := path.Join(cfgDir, "20-cloudinit.conf") | ||||
|  | ||||
| 	if _, err := os.Stat(cfgDir); err != nil { | ||||
| 		if err := os.MkdirAll(cfgDir, os.FileMode(0755)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ioutil.WriteFile(etcdDiscoveryPath, []byte(url), os.FileMode(0644)) | ||||
| 	return ioutil.WriteFile(cfgFile, []byte(env.String()), os.FileMode(0644)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										90
									
								
								cloudinit/etcd_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								cloudinit/etcd_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| package cloudinit | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"syscall" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestEtcdEnvironment(t *testing.T) { | ||||
| 	cfg := make(EtcdEnvironment, 0) | ||||
| 	cfg["discovery_url"] = "http://disco.example.com/foobar" | ||||
| 	cfg["peer-bind-addr"] = "127.0.0.1:7002" | ||||
|  | ||||
| 	env := cfg.String() | ||||
| 	expect := `[Service] | ||||
| Environment="ETCD_DISCOVERY_URL=http://disco.example.com/foobar" | ||||
| Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002" | ||||
| ` | ||||
|  | ||||
| 	if env != expect { | ||||
| 		t.Errorf("Generated environment:\n%s\nExpected environment:\n%s", env, expect) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestEtcdEnvironmentReplacement(t *testing.T) { | ||||
| 	os.Clearenv() | ||||
| 	os.Setenv("COREOS_PUBLIC_IPV4", "203.0.113.29") | ||||
| 	os.Setenv("COREOS_PRIVATE_IPV4", "192.0.2.13") | ||||
|  | ||||
| 	cfg := make(EtcdEnvironment, 0) | ||||
| 	cfg["bind-addr"] = "$public_ipv4:4001" | ||||
| 	cfg["peer-bind-addr"] = "$private_ipv4:7001" | ||||
|  | ||||
| 	env := cfg.String() | ||||
| 	expect := `[Service] | ||||
| Environment="ETCD_BIND_ADDR=203.0.113.29:4001" | ||||
| Environment="ETCD_PEER_BIND_ADDR=192.0.2.13:7001" | ||||
| ` | ||||
| 	if env != expect { | ||||
| 		t.Errorf("Generated environment:\n%s\nExpected environment:\n%s", env, expect) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestEtcdEnvironmentWrittenToDisk(t *testing.T) { | ||||
| 	ec := EtcdEnvironment{ | ||||
| 		"discovery_url": "http://disco.example.com/foobar", | ||||
| 		"peer-bind-addr": "127.0.0.1:7002", | ||||
| 	} | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
|  | ||||
| 	if err := WriteEtcdEnvironment(dir, ec); err != nil { | ||||
| 		t.Fatalf("Processing of EtcdEnvironment failed: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	fullPath := path.Join(dir, "etc", "systemd", "system", "etcd.service.d", "20-cloudinit.conf") | ||||
|  | ||||
| 	fi, err := os.Stat(fullPath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to stat file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if fi.Mode() != os.FileMode(0644) { | ||||
| 		t.Errorf("File has incorrect mode: %v", fi.Mode()) | ||||
| 	} | ||||
|  | ||||
| 	contents, err := ioutil.ReadFile(fullPath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to read expected file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	expect := `[Service] | ||||
| Environment="ETCD_DISCOVERY_URL=http://disco.example.com/foobar" | ||||
| Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002" | ||||
| ` | ||||
| 	if string(contents) != expect { | ||||
| 		t.Fatalf("File has incorrect contents") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func rmdir(path string) error { | ||||
|     cmd := exec.Command("rm", "-rf", path) | ||||
|     return cmd.Run() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user