Merge pull request #100 from jonboulle/rework
refactor(*): rework cloudconfig for better extensibility and consistency
This commit is contained in:
commit
32c52d8729
@ -1,6 +1,7 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
"path"
|
||||||
@ -10,18 +11,35 @@ import (
|
|||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CloudConfigFile represents a CoreOS specific configuration option that can generate
|
||||||
|
// an associated system.File to be written to disk
|
||||||
|
type CloudConfigFile interface {
|
||||||
|
// File should either return (*system.File, error), or (nil, nil) if nothing
|
||||||
|
// needs to be done for this configuration option.
|
||||||
|
File(root string) (*system.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloudConfigUnit represents a CoreOS specific configuration option that can generate
|
||||||
|
// an associated system.Unit to be created/enabled appropriately
|
||||||
|
type CloudConfigUnit interface {
|
||||||
|
// Unit should either return (*system.Unit, error), or (nil, nil) if nothing
|
||||||
|
// needs to be done for this configuration option.
|
||||||
|
Unit(root string) (*system.Unit, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloudConfig encapsulates the entire cloud-config configuration file and maps directly to YAML
|
||||||
type CloudConfig struct {
|
type CloudConfig struct {
|
||||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||||
Coreos struct {
|
Coreos struct {
|
||||||
Etcd EtcdEnvironment
|
Etcd EtcdEnvironment
|
||||||
Update map[string]string
|
|
||||||
Units []system.Unit
|
|
||||||
OEM OEMRelease
|
OEM OEMRelease
|
||||||
|
Update UpdateConfig
|
||||||
|
Units []system.Unit
|
||||||
}
|
}
|
||||||
WriteFiles []system.File `yaml:"write_files"`
|
WriteFiles []system.File `yaml:"write_files"`
|
||||||
Hostname string
|
Hostname string
|
||||||
Users []system.User
|
Users []system.User
|
||||||
ManageEtcHosts string `yaml:"manage_etc_hosts"`
|
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||||
@ -42,6 +60,9 @@ func (cc CloudConfig) String() string {
|
|||||||
return stringified
|
return stringified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply renders a CloudConfig to an Environment. This can involve things like
|
||||||
|
// configuring the hostname, adding new users, writing various configuration
|
||||||
|
// files to disk, and manipulating systemd services.
|
||||||
func Apply(cfg CloudConfig, env *Environment) error {
|
func Apply(cfg CloudConfig, env *Environment) error {
|
||||||
if cfg.Hostname != "" {
|
if cfg.Hostname != "" {
|
||||||
if err := system.SetHostname(cfg.Hostname); err != nil {
|
if err := system.SetHostname(cfg.Hostname); err != nil {
|
||||||
@ -50,14 +71,6 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
log.Printf("Set hostname to %s", cfg.Hostname)
|
log.Printf("Set hostname to %s", cfg.Hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Coreos.OEM.ID != "" {
|
|
||||||
if err := WriteOEMRelease(&cfg.Coreos.OEM, env.Root()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Wrote /etc/oem-release to filesystem")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Users) > 0 {
|
|
||||||
for _, user := range cfg.Users {
|
for _, user := range cfg.Users {
|
||||||
if user.Name == "" {
|
if user.Name == "" {
|
||||||
log.Printf("User object has no 'name' field, skipping")
|
log.Printf("User object has no 'name' field, skipping")
|
||||||
@ -100,7 +113,6 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.SSHAuthorizedKeys) > 0 {
|
if len(cfg.SSHAuthorizedKeys) > 0 {
|
||||||
err := system.AuthorizeSSHKeys("core", env.SSHKeyName(), cfg.SSHAuthorizedKeys)
|
err := system.AuthorizeSSHKeys("core", env.SSHKeyName(), cfg.SSHAuthorizedKeys)
|
||||||
@ -111,7 +123,26 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.WriteFiles) > 0 {
|
for _, ccf := range []CloudConfigFile{cfg.Coreos.OEM, cfg.Coreos.Update, cfg.ManageEtcHosts} {
|
||||||
|
f, err := ccf.File(env.Root())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f != nil {
|
||||||
|
cfg.WriteFiles = append(cfg.WriteFiles, *f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ccu := range []CloudConfigUnit{cfg.Coreos.Etcd, cfg.Coreos.Update} {
|
||||||
|
u, err := ccu.Unit(env.Root())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if u != nil {
|
||||||
|
cfg.Coreos.Units = append(cfg.Coreos.Units, *u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, file := range cfg.WriteFiles {
|
for _, file := range cfg.WriteFiles {
|
||||||
file.Path = path.Join(env.Root(), file.Path)
|
file.Path = path.Join(env.Root(), file.Path)
|
||||||
if err := system.WriteFile(&file); err != nil {
|
if err := system.WriteFile(&file); err != nil {
|
||||||
@ -119,25 +150,9 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
log.Printf("Wrote file %s to filesystem", file.Path)
|
log.Printf("Wrote file %s to filesystem", file.Path)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Coreos.Etcd) > 0 {
|
|
||||||
if err := WriteEtcdEnvironment(cfg.Coreos.Etcd, env.Root()); err != nil {
|
|
||||||
log.Fatalf("Failed to write etcd config to filesystem: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Wrote etcd config file to filesystem")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := cfg.Coreos.Update["reboot-strategy"]; ok {
|
|
||||||
if err := WriteLocksmithConfig(s, env.Root()); err != nil {
|
|
||||||
log.Fatalf("Failed to write locksmith config to filesystem: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("Wrote locksmith config file to filesystem")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Coreos.Units) > 0 {
|
|
||||||
commands := make(map[string]string, 0)
|
commands := make(map[string]string, 0)
|
||||||
|
reload := false
|
||||||
for _, unit := range cfg.Coreos.Units {
|
for _, unit := range cfg.Coreos.Units {
|
||||||
dst := system.UnitDestination(&unit, env.Root())
|
dst := system.UnitDestination(&unit, env.Root())
|
||||||
if unit.Content != "" {
|
if unit.Content != "" {
|
||||||
@ -146,6 +161,14 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
||||||
|
reload = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if unit.Mask {
|
||||||
|
log.Printf("Masking unit file %s", unit.Name)
|
||||||
|
if err := system.MaskUnit(unit.Name, env.Root()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if unit.Enable {
|
if unit.Enable {
|
||||||
@ -162,15 +185,15 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
|
|
||||||
if unit.Group() == "network" {
|
if unit.Group() == "network" {
|
||||||
commands["systemd-networkd.service"] = "restart"
|
commands["systemd-networkd.service"] = "restart"
|
||||||
} else {
|
} else if unit.Command != "" {
|
||||||
if unit.Command != "" {
|
|
||||||
commands[unit.Name] = unit.Command
|
commands[unit.Name] = unit.Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if reload {
|
||||||
if err := system.DaemonReload(); err != nil {
|
if err := system.DaemonReload(); err != nil {
|
||||||
log.Fatalf("Failed systemd daemon-reload: %v", err)
|
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for unit, command := range commands {
|
for unit, command := range commands {
|
||||||
@ -181,17 +204,6 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
log.Printf("Result of '%s %s': %s", command, unit, res)
|
log.Printf("Result of '%s %s': %s", command, unit, res)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.ManageEtcHosts != "" {
|
|
||||||
|
|
||||||
if err := WriteEtcHosts(cfg.ManageEtcHosts, env.Root()); err != nil {
|
|
||||||
log.Fatalf("Failed to write /etc/hosts to filesystem: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Wrote /etc/hosts file to filesystem")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ ssh_authorized_keys:
|
|||||||
`
|
`
|
||||||
cfg, err := NewCloudConfig(contents)
|
cfg, err := NewCloudConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Encountered unexpected error :%v", err)
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := cfg.SSHAuthorizedKeys
|
keys := cfg.SSHAuthorizedKeys
|
||||||
@ -162,6 +162,26 @@ func TestCloudConfigSerializationHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDropInIgnored asserts that users are unable to set DropIn=True on units
|
||||||
|
func TestDropInIgnored(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
coreos:
|
||||||
|
units:
|
||||||
|
- name: test
|
||||||
|
dropin: true
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil || len(cfg.Coreos.Units) != 1 {
|
||||||
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(cfg.Coreos.Units) != 1 || cfg.Coreos.Units[0].Name != "test" {
|
||||||
|
t.Fatalf("Expected 1 unit, but got %d: %v", len(cfg.Coreos.Units), cfg.Coreos.Units)
|
||||||
|
}
|
||||||
|
if cfg.Coreos.Units[0].DropIn {
|
||||||
|
t.Errorf("dropin option on unit in cloud-config was not ignored!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCloudConfigUsers(t *testing.T) {
|
func TestCloudConfigUsers(t *testing.T) {
|
||||||
contents := `
|
contents := `
|
||||||
users:
|
users:
|
||||||
|
@ -45,3 +45,16 @@ func (self *Environment) Apply(data string) string {
|
|||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
func normalizeSvcEnv(m map[string]string) map[string]string {
|
||||||
|
out := make(map[string]string, len(m))
|
||||||
|
for key, val := range m {
|
||||||
|
key = strings.ToUpper(key)
|
||||||
|
key = strings.Replace(key, "-", "_", -1)
|
||||||
|
out[key] = val
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
@ -3,26 +3,14 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EtcdEnvironment map[string]string
|
type EtcdEnvironment map[string]string
|
||||||
|
|
||||||
func (ec EtcdEnvironment) normalized() map[string]string {
|
func (ee EtcdEnvironment) String() (out string) {
|
||||||
out := make(map[string]string, len(ec))
|
norm := normalizeSvcEnv(ee)
|
||||||
for key, val := range ec {
|
|
||||||
key = strings.ToUpper(key)
|
|
||||||
key = strings.Replace(key, "-", "_", -1)
|
|
||||||
out[key] = val
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec EtcdEnvironment) String() (out string) {
|
|
||||||
norm := ec.normalized()
|
|
||||||
|
|
||||||
if val, ok := norm["DISCOVERY_URL"]; ok {
|
if val, ok := norm["DISCOVERY_URL"]; ok {
|
||||||
delete(norm, "DISCOVERY_URL")
|
delete(norm, "DISCOVERY_URL")
|
||||||
@ -40,23 +28,23 @@ func (ec EtcdEnvironment) String() (out string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an EtcdEnvironment to the appropriate path on disk for etcd.service
|
// Unit creates a Unit file drop-in for etcd, using any configured
|
||||||
func WriteEtcdEnvironment(env EtcdEnvironment, root string) error {
|
// options and adding a default MachineID if unset.
|
||||||
if _, ok := env["name"]; !ok {
|
func (ee EtcdEnvironment) Unit(root string) (*system.Unit, error) {
|
||||||
|
if _, ok := ee["name"]; !ok {
|
||||||
if machineID := system.MachineID(root); machineID != "" {
|
if machineID := system.MachineID(root); machineID != "" {
|
||||||
env["name"] = machineID
|
ee["name"] = machineID
|
||||||
} else if hostname, err := system.Hostname(); err == nil {
|
} else if hostname, err := system.Hostname(); err == nil {
|
||||||
env["name"] = hostname
|
ee["name"] = hostname
|
||||||
} else {
|
} else {
|
||||||
return errors.New("Unable to determine default etcd name")
|
return nil, errors.New("Unable to determine default etcd name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file := system.File{
|
return &system.Unit{
|
||||||
Path: path.Join(root, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf"),
|
Name: "etcd.service",
|
||||||
RawFilePermissions: "0644",
|
Runtime: true,
|
||||||
Content: env.String(),
|
DropIn: true,
|
||||||
}
|
Content: ee.String(),
|
||||||
|
}, nil
|
||||||
return system.WriteFile(&file)
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEtcdEnvironment(t *testing.T) {
|
func TestEtcdEnvironment(t *testing.T) {
|
||||||
@ -69,8 +70,18 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
u, err := ec.Unit(dir)
|
||||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
if err != nil {
|
||||||
|
t.Fatalf("Generating etcd unit failed: %v", err)
|
||||||
|
}
|
||||||
|
if u == nil {
|
||||||
|
t.Fatalf("Returned nil etcd unit unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := system.UnitDestination(u, dir)
|
||||||
|
os.Stderr.WriteString("writing to " + dir + "\n")
|
||||||
|
if err := system.PlaceUnit(u, dst); err != nil {
|
||||||
|
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
||||||
@ -100,7 +111,7 @@ Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
||||||
ec := EtcdEnvironment{}
|
ee := EtcdEnvironment{}
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
@ -113,8 +124,18 @@ func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
|||||||
t.Fatalf("Failed writing out /etc/machine-id: %v", err)
|
t.Fatalf("Failed writing out /etc/machine-id: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
u, err := ee.Unit(dir)
|
||||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
if err != nil {
|
||||||
|
t.Fatalf("Generating etcd unit failed: %v", err)
|
||||||
|
}
|
||||||
|
if u == nil {
|
||||||
|
t.Fatalf("Returned nil etcd unit unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := system.UnitDestination(u, dir)
|
||||||
|
os.Stderr.WriteString("writing to " + dir + "\n")
|
||||||
|
if err := system.PlaceUnit(u, dst); err != nil {
|
||||||
|
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
||||||
@ -131,8 +152,3 @@ Environment="ETCD_NAME=node007"
|
|||||||
t.Fatalf("File has incorrect contents")
|
t.Fatalf("File has incorrect contents")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rmdir(path string) error {
|
|
||||||
cmd := exec.Command("rm", "-rf", path)
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
@ -2,8 +2,6 @@ package initialize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,73 +11,79 @@ import (
|
|||||||
|
|
||||||
const locksmithUnit = "locksmithd.service"
|
const locksmithUnit = "locksmithd.service"
|
||||||
|
|
||||||
// addStrategy creates an `/etc/coreos/update.conf` file with the requested
|
type UpdateConfig map[string]string
|
||||||
// strategy via rewriting the file on disk or by starting from
|
|
||||||
// `/usr/share/coreos/update.conf`.
|
func (uc UpdateConfig) strategy() string {
|
||||||
func addStrategy(strategy string, root string) error {
|
s, _ := uc["reboot-strategy"]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// File creates an `/etc/coreos/update.conf` file with the requested
|
||||||
|
// strategy, by either rewriting the existing file on disk, or starting
|
||||||
|
// from `/usr/share/coreos/update.conf`
|
||||||
|
func (uc UpdateConfig) File(root string) (*system.File, error) {
|
||||||
|
|
||||||
|
// If no reboot-strategy is set, we don't need to generate a new config
|
||||||
|
if _, ok := uc["reboot-strategy"]; !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var out string
|
||||||
|
|
||||||
etcUpdate := path.Join(root, "etc", "coreos", "update.conf")
|
etcUpdate := path.Join(root, "etc", "coreos", "update.conf")
|
||||||
usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf")
|
usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf")
|
||||||
|
|
||||||
// Ensure /etc/coreos/ exists before attempting to write a file in it
|
|
||||||
os.MkdirAll(path.Join(root, "etc", "coreos"), 0755)
|
|
||||||
|
|
||||||
tmp, err := ioutil.TempFile(path.Join(root, "etc", "coreos"), ".update.conf")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = tmp.Chmod(0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := os.Open(etcUpdate)
|
conf, err := os.Open(etcUpdate)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
conf, err = os.Open(usrUpdate)
|
conf, err = os.Open(usrUpdate)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner := bufio.NewScanner(conf)
|
scanner := bufio.NewScanner(conf)
|
||||||
|
|
||||||
sawStrat := false
|
sawStrat := false
|
||||||
stratLine := "REBOOT_STRATEGY="+strategy
|
stratLine := "REBOOT_STRATEGY=" + uc.strategy()
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if strings.HasPrefix(line, "REBOOT_STRATEGY=") {
|
if strings.HasPrefix(line, "REBOOT_STRATEGY=") {
|
||||||
line = stratLine
|
line = stratLine
|
||||||
sawStrat = true
|
sawStrat = true
|
||||||
}
|
}
|
||||||
fmt.Fprintln(tmp, line)
|
out += line
|
||||||
|
out += "\n"
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sawStrat {
|
if !sawStrat {
|
||||||
fmt.Fprintln(tmp, stratLine)
|
out += stratLine
|
||||||
|
out += "\n"
|
||||||
}
|
}
|
||||||
|
return &system.File{
|
||||||
return os.Rename(tmp.Name(), etcUpdate)
|
Path: path.Join("etc", "coreos", "update.conf"),
|
||||||
|
RawFilePermissions: "0644",
|
||||||
|
Content: out,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteLocksmithConfig updates the `update.conf` file with a REBOOT_STRATEGY for locksmith.
|
// Unit generates a locksmith system.Unit for the cloud-init initializer to
|
||||||
func WriteLocksmithConfig(strategy string, root string) error {
|
// act on appropriately
|
||||||
cmd := "restart"
|
func (uc UpdateConfig) Unit(root string) (*system.Unit, error) {
|
||||||
if strategy == "off" {
|
u := &system.Unit{
|
||||||
err := system.MaskUnit(locksmithUnit, root)
|
Name: locksmithUnit,
|
||||||
if err != nil {
|
Enable: true,
|
||||||
return err
|
Command: "restart",
|
||||||
|
Mask: false,
|
||||||
}
|
}
|
||||||
cmd = "stop"
|
|
||||||
} else {
|
if uc.strategy() == "off" {
|
||||||
return addStrategy(strategy, root)
|
u.Enable = false
|
||||||
|
u.Command = "stop"
|
||||||
|
u.Mask = true
|
||||||
}
|
}
|
||||||
if err := system.DaemonReload(); err != nil {
|
|
||||||
return err
|
return u, nil
|
||||||
}
|
|
||||||
if _, err := system.RunUnitCommand(cmd, locksmithUnit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,9 +44,19 @@ func TestLocksmithEnvironmentWrittenToDisk(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uc := &UpdateConfig{"reboot-strategy": "etcd-lock"}
|
||||||
|
|
||||||
if err := WriteLocksmithConfig("etcd-lock", dir); err != nil {
|
f, err := uc.File(dir)
|
||||||
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
if err != nil {
|
||||||
|
t.Fatalf("Processing UpdateConfig failed: %v", err)
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
t.Fatalf("UpdateConfig generated nil file unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Path = path.Join(dir, f.Path)
|
||||||
|
if err := system.WriteFile(f); err != nil {
|
||||||
|
t.Fatalf("Error writing update config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "coreos", "update.conf")
|
fullPath := path.Join(dir, "etc", "coreos", "update.conf")
|
||||||
@ -76,9 +88,17 @@ func TestLocksmithEnvironmentMasked(t *testing.T) {
|
|||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
setupFixtures(dir)
|
setupFixtures(dir)
|
||||||
|
|
||||||
if err := WriteLocksmithConfig("off", dir); err != nil {
|
uc := &UpdateConfig{"reboot-strategy": "off"}
|
||||||
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
|
||||||
|
u, err := uc.Unit(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Processing UpdateConfig failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if u == nil {
|
||||||
|
t.Fatalf("UpdateConfig generated nil unit unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
system.MaskUnit(u.Name, dir)
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "systemd", "system", "locksmithd.service")
|
fullPath := path.Join(dir, "etc", "systemd", "system", "locksmithd.service")
|
||||||
target, err := os.Readlink(fullPath)
|
target, err := os.Readlink(fullPath)
|
||||||
|
@ -11,8 +11,10 @@ import (
|
|||||||
|
|
||||||
const DefaultIpv4Address = "127.0.0.1"
|
const DefaultIpv4Address = "127.0.0.1"
|
||||||
|
|
||||||
func generateEtcHosts(option string) (out string, err error) {
|
type EtcHosts string
|
||||||
if option != "localhost" {
|
|
||||||
|
func (eh EtcHosts) generateEtcHosts() (out string, err error) {
|
||||||
|
if eh != "localhost" {
|
||||||
return "", errors.New("Invalid option to manage_etc_hosts")
|
return "", errors.New("Invalid option to manage_etc_hosts")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,19 +28,19 @@ func generateEtcHosts(option string) (out string, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an /etc/hosts file
|
func (eh EtcHosts) File(root string) (*system.File, error) {
|
||||||
func WriteEtcHosts(option string, root string) error {
|
if eh == "" {
|
||||||
|
return nil, nil
|
||||||
etcHosts, err := generateEtcHosts(option)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file := system.File{
|
etcHosts, err := eh.generateEtcHosts()
|
||||||
Path: path.Join(root, "etc", "hosts"),
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &system.File{
|
||||||
|
Path: path.Join("etc", "hosts"),
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
Content: etcHosts,
|
Content: etcHosts,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
return system.WriteFile(&file)
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCloudConfigManageEtcHosts(t *testing.T) {
|
func TestCloudConfigManageEtcHosts(t *testing.T) {
|
||||||
@ -25,14 +27,9 @@ manage_etc_hosts: localhost
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManageEtcHostsInvalidValue(t *testing.T) {
|
func TestManageEtcHostsInvalidValue(t *testing.T) {
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
eh := EtcHosts("invalid")
|
||||||
if err != nil {
|
if f, err := eh.File(""); err == nil || f != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("EtcHosts File succeeded with invalid value!")
|
||||||
}
|
|
||||||
defer rmdir(dir)
|
|
||||||
|
|
||||||
if err := WriteEtcHosts("invalid", dir); err == nil {
|
|
||||||
t.Fatalf("WriteEtcHosts succeeded with invalid value: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +38,22 @@ func TestEtcHostsWrittenToDisk(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer rmdir(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
if err := WriteEtcHosts("localhost", dir); err != nil {
|
eh := EtcHosts("localhost")
|
||||||
t.Fatalf("WriteEtcHosts failed: %v", err)
|
|
||||||
|
f, err := eh.File(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error calling File on EtcHosts: %v", err)
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
t.Fatalf("manageEtcHosts returned nil file unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Path = path.Join(dir, f.Path)
|
||||||
|
|
||||||
|
if err := system.WriteFile(f); err != nil {
|
||||||
|
t.Fatalf("Error writing EtcHosts: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "hosts")
|
fullPath := path.Join(dir, "etc", "hosts")
|
||||||
|
@ -16,7 +16,7 @@ type OEMRelease struct {
|
|||||||
BugReportURL string `yaml:"bug-report-url"`
|
BugReportURL string `yaml:"bug-report-url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oem *OEMRelease) String() string {
|
func (oem OEMRelease) String() string {
|
||||||
fields := []string{
|
fields := []string{
|
||||||
fmt.Sprintf("ID=%s", oem.ID),
|
fmt.Sprintf("ID=%s", oem.ID),
|
||||||
fmt.Sprintf("VERSION_ID=%s", oem.VersionID),
|
fmt.Sprintf("VERSION_ID=%s", oem.VersionID),
|
||||||
@ -28,12 +28,14 @@ func (oem *OEMRelease) String() string {
|
|||||||
return strings.Join(fields, "\n") + "\n"
|
return strings.Join(fields, "\n") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteOEMRelease(oem *OEMRelease, root string) error {
|
func (oem OEMRelease) File(root string) (*system.File, error) {
|
||||||
file := system.File{
|
if oem.ID == "" {
|
||||||
Path: path.Join(root, "etc", "oem-release"),
|
return nil, nil
|
||||||
RawFilePermissions: "0644",
|
|
||||||
Content: oem.String(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return system.WriteFile(&file)
|
return &system.File{
|
||||||
|
Path: path.Join("etc", "oem-release"),
|
||||||
|
RawFilePermissions: "0644",
|
||||||
|
Content: oem.String(),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
||||||
@ -21,8 +23,17 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
if err := WriteOEMRelease(&oem, dir); err != nil {
|
f, err := oem.File(dir)
|
||||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
if err != nil {
|
||||||
|
t.Fatalf("Processing of OEMRelease failed: %v", err)
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
t.Fatalf("OEMRelease returned nil file unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Path = path.Join(dir, f.Path)
|
||||||
|
if err := system.WriteFile(f); err != nil {
|
||||||
|
t.Fatalf("Writing of OEMRelease failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "oem-release")
|
fullPath := path.Join(dir, "etc", "oem-release")
|
||||||
|
@ -17,12 +17,21 @@ import (
|
|||||||
// never be used as a true MachineID
|
// never be used as a true MachineID
|
||||||
const fakeMachineID = "42000000000000000000000000000042"
|
const fakeMachineID = "42000000000000000000000000000042"
|
||||||
|
|
||||||
|
// Name for drop-in service configuration files created by cloudconfig
|
||||||
|
const cloudConfigDropIn = "20-cloudinit.conf"
|
||||||
|
|
||||||
type Unit struct {
|
type Unit struct {
|
||||||
Name string
|
Name string
|
||||||
|
Mask bool
|
||||||
Enable bool
|
Enable bool
|
||||||
Runtime bool
|
Runtime bool
|
||||||
Content string
|
Content string
|
||||||
Command string
|
Command string
|
||||||
|
|
||||||
|
// For drop-in units, a cloudinit.conf is generated.
|
||||||
|
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
||||||
|
// until the correct behaviour for multiple drop-in units is determined.
|
||||||
|
DropIn bool `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unit) Type() string {
|
func (u *Unit) Type() string {
|
||||||
@ -42,8 +51,8 @@ func (u *Unit) Group() (group string) {
|
|||||||
|
|
||||||
type Script []byte
|
type Script []byte
|
||||||
|
|
||||||
// UnitDestination builds the appropriate absolte file path for
|
// UnitDestination builds the appropriate absolute file path for
|
||||||
// the given unit. The root argument indicates the effective base
|
// the given Unit. The root argument indicates the effective base
|
||||||
// directory of the system (similar to a chroot).
|
// directory of the system (similar to a chroot).
|
||||||
func UnitDestination(u *Unit, root string) string {
|
func UnitDestination(u *Unit, root string) string {
|
||||||
dir := "etc"
|
dir := "etc"
|
||||||
@ -51,7 +60,11 @@ func UnitDestination(u *Unit, root string) string {
|
|||||||
dir = "run"
|
dir = "run"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.DropIn {
|
||||||
|
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
|
||||||
|
} else {
|
||||||
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaceUnit writes a unit file at the provided destination, creating
|
// PlaceUnit writes a unit file at the provided destination, creating
|
||||||
|
@ -60,6 +60,30 @@ Address=10.209.171.177/19
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnitDestination(t *testing.T) {
|
||||||
|
dir := "/some/dir"
|
||||||
|
name := "foobar.service"
|
||||||
|
|
||||||
|
u := Unit{
|
||||||
|
Name: name,
|
||||||
|
DropIn: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := UnitDestination(&u, dir)
|
||||||
|
expectDst := path.Join(dir, "etc", "systemd", "system", "foobar.service")
|
||||||
|
if dst != expectDst {
|
||||||
|
t.Errorf("UnitDestination returned %s, expected %s", dst, expectDst)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.DropIn = true
|
||||||
|
|
||||||
|
dst = UnitDestination(&u, dir)
|
||||||
|
expectDst = path.Join(dir, "etc", "systemd", "system", "foobar.service.d", cloudConfigDropIn)
|
||||||
|
if dst != expectDst {
|
||||||
|
t.Errorf("UnitDestination returned %s, expected %s", dst, expectDst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPlaceMountUnit(t *testing.T) {
|
func TestPlaceMountUnit(t *testing.T) {
|
||||||
u := Unit{
|
u := Unit{
|
||||||
Name: "media-state.mount",
|
Name: "media-state.mount",
|
||||||
@ -123,6 +147,7 @@ func TestMachineID(t *testing.T) {
|
|||||||
t.Fatalf("File has incorrect contents")
|
t.Fatalf("File has incorrect contents")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaskUnit(t *testing.T) {
|
func TestMaskUnit(t *testing.T) {
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user