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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
@ -10,18 +11,35 @@ import (
|
||||
"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 {
|
||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||
Coreos struct {
|
||||
Etcd EtcdEnvironment
|
||||
Update map[string]string
|
||||
Units []system.Unit
|
||||
OEM OEMRelease
|
||||
Update UpdateConfig
|
||||
Units []system.Unit
|
||||
}
|
||||
WriteFiles []system.File `yaml:"write_files"`
|
||||
Hostname string
|
||||
Users []system.User
|
||||
ManageEtcHosts string `yaml:"manage_etc_hosts"`
|
||||
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
||||
}
|
||||
|
||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||
@ -42,6 +60,9 @@ func (cc CloudConfig) String() string {
|
||||
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 {
|
||||
if cfg.Hostname != "" {
|
||||
if err := system.SetHostname(cfg.Hostname); err != nil {
|
||||
@ -50,54 +71,45 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
||||
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
|
||||
for _, user := range cfg.Users {
|
||||
if user.Name == "" {
|
||||
log.Printf("User object has no 'name' field, skipping")
|
||||
continue
|
||||
}
|
||||
log.Printf("Wrote /etc/oem-release to filesystem")
|
||||
}
|
||||
|
||||
if len(cfg.Users) > 0 {
|
||||
for _, user := range cfg.Users {
|
||||
if user.Name == "" {
|
||||
log.Printf("User object has no 'name' field, skipping")
|
||||
continue
|
||||
if system.UserExists(&user) {
|
||||
log.Printf("User '%s' exists, ignoring creation-time fields", user.Name)
|
||||
if user.PasswordHash != "" {
|
||||
log.Printf("Setting '%s' user's password", user.Name)
|
||||
if err := system.SetUserPassword(user.Name, user.PasswordHash); err != nil {
|
||||
log.Printf("Failed setting '%s' user's password: %v", user.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("Creating user '%s'", user.Name)
|
||||
if err := system.CreateUser(&user); err != nil {
|
||||
log.Printf("Failed creating user '%s': %v", user.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if system.UserExists(&user) {
|
||||
log.Printf("User '%s' exists, ignoring creation-time fields", user.Name)
|
||||
if user.PasswordHash != "" {
|
||||
log.Printf("Setting '%s' user's password", user.Name)
|
||||
if err := system.SetUserPassword(user.Name, user.PasswordHash); err != nil {
|
||||
log.Printf("Failed setting '%s' user's password: %v", user.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("Creating user '%s'", user.Name)
|
||||
if err := system.CreateUser(&user); err != nil {
|
||||
log.Printf("Failed creating user '%s': %v", user.Name, err)
|
||||
return err
|
||||
}
|
||||
if len(user.SSHAuthorizedKeys) > 0 {
|
||||
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
||||
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(user.SSHAuthorizedKeys) > 0 {
|
||||
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
||||
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if user.SSHImportGithubUser != "" {
|
||||
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", user.SSHImportGithubUser, user.Name)
|
||||
if err := SSHImportGithubUser(user.Name, user.SSHImportGithubUser); err != nil {
|
||||
return err
|
||||
}
|
||||
if user.SSHImportGithubUser != "" {
|
||||
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", user.SSHImportGithubUser, user.Name)
|
||||
if err := SSHImportGithubUser(user.Name, user.SSHImportGithubUser); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if user.SSHImportURL != "" {
|
||||
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
||||
if err := SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if user.SSHImportURL != "" {
|
||||
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
||||
if err := SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,86 +123,86 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.WriteFiles) > 0 {
|
||||
for _, file := range cfg.WriteFiles {
|
||||
file.Path = path.Join(env.Root(), file.Path)
|
||||
if err := system.WriteFile(&file); err != nil {
|
||||
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 {
|
||||
file.Path = path.Join(env.Root(), file.Path)
|
||||
if err := system.WriteFile(&file); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Wrote file %s to filesystem", file.Path)
|
||||
}
|
||||
|
||||
commands := make(map[string]string, 0)
|
||||
reload := false
|
||||
for _, unit := range cfg.Coreos.Units {
|
||||
dst := system.UnitDestination(&unit, env.Root())
|
||||
if unit.Content != "" {
|
||||
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
||||
if err := system.PlaceUnit(&unit, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
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("Placed unit %s at %s", unit.Name, dst)
|
||||
reload = true
|
||||
}
|
||||
|
||||
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)
|
||||
if unit.Mask {
|
||||
log.Printf("Masking unit file %s", unit.Name)
|
||||
if err := system.MaskUnit(unit.Name, env.Root()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Printf("Wrote locksmith config file to filesystem")
|
||||
}
|
||||
|
||||
if len(cfg.Coreos.Units) > 0 {
|
||||
commands := make(map[string]string, 0)
|
||||
for _, unit := range cfg.Coreos.Units {
|
||||
dst := system.UnitDestination(&unit, env.Root())
|
||||
if unit.Content != "" {
|
||||
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
||||
if err := system.PlaceUnit(&unit, dst); err != nil {
|
||||
if unit.Enable {
|
||||
if unit.Group() != "network" {
|
||||
log.Printf("Enabling unit file %s", dst)
|
||||
if err := system.EnableUnitFile(dst, unit.Runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
||||
}
|
||||
|
||||
if unit.Enable {
|
||||
if unit.Group() != "network" {
|
||||
log.Printf("Enabling unit file %s", dst)
|
||||
if err := system.EnableUnitFile(dst, unit.Runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Enabled unit %s", unit.Name)
|
||||
} else {
|
||||
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if unit.Group() == "network" {
|
||||
commands["systemd-networkd.service"] = "restart"
|
||||
log.Printf("Enabled unit %s", unit.Name)
|
||||
} else {
|
||||
if unit.Command != "" {
|
||||
commands[unit.Name] = unit.Command
|
||||
}
|
||||
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := system.DaemonReload(); err != nil {
|
||||
log.Fatalf("Failed systemd daemon-reload: %v", err)
|
||||
}
|
||||
|
||||
for unit, command := range commands {
|
||||
log.Printf("Calling unit command '%s %s'", command, unit)
|
||||
res, err := system.RunUnitCommand(command, unit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Result of '%s %s': %s", command, unit, res)
|
||||
if unit.Group() == "network" {
|
||||
commands["systemd-networkd.service"] = "restart"
|
||||
} else if unit.Command != "" {
|
||||
commands[unit.Name] = unit.Command
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.ManageEtcHosts != "" {
|
||||
|
||||
if err := WriteEtcHosts(cfg.ManageEtcHosts, env.Root()); err != nil {
|
||||
log.Fatalf("Failed to write /etc/hosts to filesystem: %v", err)
|
||||
if reload {
|
||||
if err := system.DaemonReload(); err != nil {
|
||||
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Wrote /etc/hosts file to filesystem")
|
||||
|
||||
for unit, command := range commands {
|
||||
log.Printf("Calling unit command '%s %s'", command, unit)
|
||||
res, err := system.RunUnitCommand(command, unit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Result of '%s %s': %s", command, unit, res)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -144,7 +144,7 @@ ssh_authorized_keys:
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error :%v", err)
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
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) {
|
||||
contents := `
|
||||
users:
|
||||
|
@ -45,3 +45,16 @@ func (self *Environment) Apply(data string) string {
|
||||
}
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
type EtcdEnvironment map[string]string
|
||||
|
||||
func (ec EtcdEnvironment) normalized() map[string]string {
|
||||
out := make(map[string]string, len(ec))
|
||||
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()
|
||||
func (ee EtcdEnvironment) String() (out string) {
|
||||
norm := normalizeSvcEnv(ee)
|
||||
|
||||
if val, ok := norm["DISCOVERY_URL"]; ok {
|
||||
delete(norm, "DISCOVERY_URL")
|
||||
@ -40,23 +28,23 @@ func (ec EtcdEnvironment) String() (out string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Write an EtcdEnvironment to the appropriate path on disk for etcd.service
|
||||
func WriteEtcdEnvironment(env EtcdEnvironment, root string) error {
|
||||
if _, ok := env["name"]; !ok {
|
||||
// Unit creates a Unit file drop-in for etcd, using any configured
|
||||
// options and adding a default MachineID if unset.
|
||||
func (ee EtcdEnvironment) Unit(root string) (*system.Unit, error) {
|
||||
if _, ok := ee["name"]; !ok {
|
||||
if machineID := system.MachineID(root); machineID != "" {
|
||||
env["name"] = machineID
|
||||
ee["name"] = machineID
|
||||
} else if hostname, err := system.Hostname(); err == nil {
|
||||
env["name"] = hostname
|
||||
ee["name"] = hostname
|
||||
} else {
|
||||
return errors.New("Unable to determine default etcd name")
|
||||
return nil, errors.New("Unable to determine default etcd name")
|
||||
}
|
||||
}
|
||||
|
||||
file := system.File{
|
||||
Path: path.Join(root, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf"),
|
||||
RawFilePermissions: "0644",
|
||||
Content: env.String(),
|
||||
}
|
||||
|
||||
return system.WriteFile(&file)
|
||||
return &system.Unit{
|
||||
Name: "etcd.service",
|
||||
Runtime: true,
|
||||
DropIn: true,
|
||||
Content: ee.String(),
|
||||
}, nil
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ package initialize
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
func TestEtcdEnvironment(t *testing.T) {
|
||||
@ -69,8 +70,18 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||
u, err := ec.Unit(dir)
|
||||
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")
|
||||
@ -100,7 +111,7 @@ Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
||||
}
|
||||
|
||||
func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
||||
ec := EtcdEnvironment{}
|
||||
ee := EtcdEnvironment{}
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||
u, err := ee.Unit(dir)
|
||||
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")
|
||||
@ -131,8 +152,3 @@ Environment="ETCD_NAME=node007"
|
||||
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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
@ -13,73 +11,79 @@ import (
|
||||
|
||||
const locksmithUnit = "locksmithd.service"
|
||||
|
||||
// addStrategy creates an `/etc/coreos/update.conf` file with the requested
|
||||
// strategy via rewriting the file on disk or by starting from
|
||||
// `/usr/share/coreos/update.conf`.
|
||||
func addStrategy(strategy string, root string) error {
|
||||
type UpdateConfig map[string]string
|
||||
|
||||
func (uc UpdateConfig) strategy() string {
|
||||
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")
|
||||
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)
|
||||
if os.IsNotExist(err) {
|
||||
conf, err = os.Open(usrUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conf)
|
||||
|
||||
sawStrat := false
|
||||
stratLine := "REBOOT_STRATEGY="+strategy
|
||||
stratLine := "REBOOT_STRATEGY=" + uc.strategy()
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "REBOOT_STRATEGY=") {
|
||||
line = stratLine
|
||||
sawStrat = true
|
||||
}
|
||||
fmt.Fprintln(tmp, line)
|
||||
out += line
|
||||
out += "\n"
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !sawStrat {
|
||||
fmt.Fprintln(tmp, stratLine)
|
||||
out += stratLine
|
||||
out += "\n"
|
||||
}
|
||||
|
||||
return os.Rename(tmp.Name(), etcUpdate)
|
||||
return &system.File{
|
||||
Path: path.Join("etc", "coreos", "update.conf"),
|
||||
RawFilePermissions: "0644",
|
||||
Content: out,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WriteLocksmithConfig updates the `update.conf` file with a REBOOT_STRATEGY for locksmith.
|
||||
func WriteLocksmithConfig(strategy string, root string) error {
|
||||
cmd := "restart"
|
||||
if strategy == "off" {
|
||||
err := system.MaskUnit(locksmithUnit, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = "stop"
|
||||
} else {
|
||||
return addStrategy(strategy, root)
|
||||
// Unit generates a locksmith system.Unit for the cloud-init initializer to
|
||||
// act on appropriately
|
||||
func (uc UpdateConfig) Unit(root string) (*system.Unit, error) {
|
||||
u := &system.Unit{
|
||||
Name: locksmithUnit,
|
||||
Enable: true,
|
||||
Command: "restart",
|
||||
Mask: false,
|
||||
}
|
||||
if err := system.DaemonReload(); err != nil {
|
||||
return err
|
||||
|
||||
if uc.strategy() == "off" {
|
||||
u.Enable = false
|
||||
u.Command = "stop"
|
||||
u.Mask = true
|
||||
}
|
||||
if _, err := system.RunUnitCommand(cmd, locksmithUnit); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -42,9 +44,19 @@ func TestLocksmithEnvironmentWrittenToDisk(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
uc := &UpdateConfig{"reboot-strategy": "etcd-lock"}
|
||||
|
||||
if err := WriteLocksmithConfig("etcd-lock", dir); err != nil {
|
||||
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
||||
f, err := uc.File(dir)
|
||||
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")
|
||||
@ -76,9 +88,17 @@ func TestLocksmithEnvironmentMasked(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
setupFixtures(dir)
|
||||
|
||||
if err := WriteLocksmithConfig("off", dir); err != nil {
|
||||
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
||||
uc := &UpdateConfig{"reboot-strategy": "off"}
|
||||
|
||||
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")
|
||||
target, err := os.Readlink(fullPath)
|
||||
|
@ -11,8 +11,10 @@ import (
|
||||
|
||||
const DefaultIpv4Address = "127.0.0.1"
|
||||
|
||||
func generateEtcHosts(option string) (out string, err error) {
|
||||
if option != "localhost" {
|
||||
type EtcHosts string
|
||||
|
||||
func (eh EtcHosts) generateEtcHosts() (out string, err error) {
|
||||
if eh != "localhost" {
|
||||
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 WriteEtcHosts(option string, root string) error {
|
||||
|
||||
etcHosts, err := generateEtcHosts(option)
|
||||
if err != nil {
|
||||
return err
|
||||
func (eh EtcHosts) File(root string) (*system.File, error) {
|
||||
if eh == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
file := system.File{
|
||||
Path: path.Join(root, "etc", "hosts"),
|
||||
etcHosts, err := eh.generateEtcHosts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &system.File{
|
||||
Path: path.Join("etc", "hosts"),
|
||||
RawFilePermissions: "0644",
|
||||
Content: etcHosts,
|
||||
}
|
||||
|
||||
return system.WriteFile(&file)
|
||||
}, nil
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
func TestCloudConfigManageEtcHosts(t *testing.T) {
|
||||
@ -25,14 +27,9 @@ manage_etc_hosts: localhost
|
||||
}
|
||||
|
||||
func TestManageEtcHostsInvalidValue(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer rmdir(dir)
|
||||
|
||||
if err := WriteEtcHosts("invalid", dir); err == nil {
|
||||
t.Fatalf("WriteEtcHosts succeeded with invalid value: %v", err)
|
||||
eh := EtcHosts("invalid")
|
||||
if f, err := eh.File(""); err == nil || f != nil {
|
||||
t.Fatalf("EtcHosts File succeeded with invalid value!")
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,10 +38,22 @@ func TestEtcHostsWrittenToDisk(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if err := WriteEtcHosts("localhost", dir); err != nil {
|
||||
t.Fatalf("WriteEtcHosts failed: %v", err)
|
||||
eh := EtcHosts("localhost")
|
||||
|
||||
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")
|
||||
|
@ -16,7 +16,7 @@ type OEMRelease struct {
|
||||
BugReportURL string `yaml:"bug-report-url"`
|
||||
}
|
||||
|
||||
func (oem *OEMRelease) String() string {
|
||||
func (oem OEMRelease) String() string {
|
||||
fields := []string{
|
||||
fmt.Sprintf("ID=%s", oem.ID),
|
||||
fmt.Sprintf("VERSION_ID=%s", oem.VersionID),
|
||||
@ -28,12 +28,14 @@ func (oem *OEMRelease) String() string {
|
||||
return strings.Join(fields, "\n") + "\n"
|
||||
}
|
||||
|
||||
func WriteOEMRelease(oem *OEMRelease, root string) error {
|
||||
file := system.File{
|
||||
Path: path.Join(root, "etc", "oem-release"),
|
||||
RawFilePermissions: "0644",
|
||||
Content: oem.String(),
|
||||
func (oem OEMRelease) File(root string) (*system.File, error) {
|
||||
if oem.ID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
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"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
||||
@ -21,8 +23,17 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if err := WriteOEMRelease(&oem, dir); err != nil {
|
||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||
f, err := oem.File(dir)
|
||||
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")
|
||||
|
@ -17,12 +17,21 @@ import (
|
||||
// never be used as a true MachineID
|
||||
const fakeMachineID = "42000000000000000000000000000042"
|
||||
|
||||
// Name for drop-in service configuration files created by cloudconfig
|
||||
const cloudConfigDropIn = "20-cloudinit.conf"
|
||||
|
||||
type Unit struct {
|
||||
Name string
|
||||
Mask bool
|
||||
Enable bool
|
||||
Runtime bool
|
||||
Content 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 {
|
||||
@ -42,8 +51,8 @@ func (u *Unit) Group() (group string) {
|
||||
|
||||
type Script []byte
|
||||
|
||||
// UnitDestination builds the appropriate absolte file path for
|
||||
// the given unit. The root argument indicates the effective base
|
||||
// UnitDestination builds the appropriate absolute file path for
|
||||
// the given Unit. The root argument indicates the effective base
|
||||
// directory of the system (similar to a chroot).
|
||||
func UnitDestination(u *Unit, root string) string {
|
||||
dir := "etc"
|
||||
@ -51,7 +60,11 @@ func UnitDestination(u *Unit, root string) string {
|
||||
dir = "run"
|
||||
}
|
||||
|
||||
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
u := Unit{
|
||||
Name: "media-state.mount",
|
||||
@ -123,6 +147,7 @@ func TestMachineID(t *testing.T) {
|
||||
t.Fatalf("File has incorrect contents")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskUnit(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user