2014-03-18 09:00:41 -07:00
|
|
|
package initialize
|
2014-03-04 16:36:05 -08:00
|
|
|
|
|
|
|
import (
|
2014-05-09 20:33:34 -07:00
|
|
|
"errors"
|
2014-03-12 16:52:32 -07:00
|
|
|
"fmt"
|
2014-06-18 12:08:10 -07:00
|
|
|
"io/ioutil"
|
2014-03-04 16:36:05 -08:00
|
|
|
"log"
|
2014-06-18 12:08:10 -07:00
|
|
|
"path"
|
2014-03-04 16:36:05 -08:00
|
|
|
|
2014-03-12 19:36:31 -07:00
|
|
|
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
2014-03-04 16:36:05 -08:00
|
|
|
|
2014-06-18 12:08:10 -07:00
|
|
|
"github.com/coreos/coreos-cloudinit/network"
|
2014-03-18 09:00:41 -07:00
|
|
|
"github.com/coreos/coreos-cloudinit/system"
|
|
|
|
)
|
2014-03-05 14:30:38 -08:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
// 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
|
2014-06-05 16:12:40 -07:00
|
|
|
// associated system.Units to be created/enabled appropriately
|
2014-05-09 20:33:34 -07:00
|
|
|
type CloudConfigUnit interface {
|
2014-06-05 16:12:40 -07:00
|
|
|
Units(root string) ([]system.Unit, error)
|
2014-05-09 20:33:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// CloudConfig encapsulates the entire cloud-config configuration file and maps directly to YAML
|
2014-03-04 16:36:05 -08:00
|
|
|
type CloudConfig struct {
|
2014-03-13 11:39:31 -07:00
|
|
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
|
|
|
Coreos struct {
|
2014-05-06 17:44:18 -07:00
|
|
|
Etcd EtcdEnvironment
|
2014-05-09 20:36:26 -07:00
|
|
|
Fleet FleetEnvironment
|
2014-05-06 17:44:18 -07:00
|
|
|
OEM OEMRelease
|
2014-05-09 20:33:34 -07:00
|
|
|
Update UpdateConfig
|
|
|
|
Units []system.Unit
|
2014-03-11 16:13:07 -07:00
|
|
|
}
|
2014-06-18 11:58:18 -07:00
|
|
|
WriteFiles []system.File `yaml:"write_files"`
|
|
|
|
Hostname string
|
|
|
|
Users []system.User
|
|
|
|
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
|
|
|
NetworkConfigPath string
|
2014-03-04 16:36:05 -08:00
|
|
|
}
|
|
|
|
|
2014-05-10 01:06:38 -07:00
|
|
|
type warner func(format string, v ...interface{})
|
|
|
|
|
2014-05-12 15:46:38 -07:00
|
|
|
// warnOnUnrecognizedKeys parses the contents of a cloud-config file and calls
|
|
|
|
// warn(msg, key) for every unrecognized key (i.e. those not present in CloudConfig)
|
2014-05-10 01:06:38 -07:00
|
|
|
func warnOnUnrecognizedKeys(contents string, warn warner) {
|
|
|
|
// Generate a map of all understood cloud config options
|
|
|
|
var cc map[string]interface{}
|
|
|
|
b, _ := goyaml.Marshal(&CloudConfig{})
|
|
|
|
goyaml.Unmarshal(b, &cc)
|
2014-05-12 15:46:38 -07:00
|
|
|
|
2014-05-10 01:06:38 -07:00
|
|
|
// Now unmarshal the entire provided contents
|
|
|
|
var c map[string]interface{}
|
|
|
|
goyaml.Unmarshal([]byte(contents), &c)
|
2014-05-12 15:46:38 -07:00
|
|
|
|
2014-05-10 01:06:38 -07:00
|
|
|
// Check that every key in the contents exists in the cloud config
|
|
|
|
for k, _ := range c {
|
|
|
|
if _, ok := cc[k]; !ok {
|
|
|
|
warn("Warning: unrecognized key %q in provided cloud config - ignoring section", k)
|
|
|
|
}
|
|
|
|
}
|
2014-05-12 15:46:38 -07:00
|
|
|
|
2014-05-12 16:33:28 -07:00
|
|
|
// Check for unrecognized coreos options, if any are set
|
2014-07-22 11:36:58 -07:00
|
|
|
if coreos, ok := c["coreos"]; ok {
|
|
|
|
if set, ok := coreos.(map[interface{}]interface{}); ok {
|
|
|
|
known := cc["coreos"].(map[interface{}]interface{})
|
|
|
|
for k, _ := range set {
|
|
|
|
if key, ok := k.(string); ok {
|
|
|
|
if _, ok := known[key]; !ok {
|
|
|
|
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", key)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", k)
|
|
|
|
}
|
2014-05-12 16:33:28 -07:00
|
|
|
}
|
|
|
|
}
|
2014-05-12 15:46:38 -07:00
|
|
|
}
|
2014-05-12 16:33:28 -07:00
|
|
|
|
|
|
|
// Check for any badly-specified users, if any are set
|
2014-07-22 11:36:58 -07:00
|
|
|
if users, ok := c["users"]; ok {
|
2014-05-12 16:33:28 -07:00
|
|
|
var known map[string]interface{}
|
|
|
|
b, _ := goyaml.Marshal(&system.User{})
|
|
|
|
goyaml.Unmarshal(b, &known)
|
|
|
|
|
2014-07-22 11:36:58 -07:00
|
|
|
if set, ok := users.([]interface{}); ok {
|
|
|
|
for _, u := range set {
|
|
|
|
if user, ok := u.(map[interface{}]interface{}); ok {
|
|
|
|
for k, _ := range user {
|
|
|
|
if key, ok := k.(string); ok {
|
|
|
|
if _, ok := known[key]; !ok {
|
|
|
|
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", key)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", k)
|
|
|
|
}
|
|
|
|
}
|
2014-05-12 16:33:28 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for any badly-specified files, if any are set
|
2014-07-22 11:36:58 -07:00
|
|
|
if files, ok := c["write_files"]; ok {
|
2014-05-12 16:33:28 -07:00
|
|
|
var known map[string]interface{}
|
|
|
|
b, _ := goyaml.Marshal(&system.File{})
|
|
|
|
goyaml.Unmarshal(b, &known)
|
|
|
|
|
2014-07-22 11:36:58 -07:00
|
|
|
if set, ok := files.([]interface{}); ok {
|
|
|
|
for _, f := range set {
|
|
|
|
if file, ok := f.(map[interface{}]interface{}); ok {
|
|
|
|
for k, _ := range file {
|
|
|
|
if key, ok := k.(string); ok {
|
|
|
|
if _, ok := known[key]; !ok {
|
|
|
|
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", key)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", k)
|
|
|
|
}
|
|
|
|
}
|
2014-05-12 16:33:28 -07:00
|
|
|
}
|
|
|
|
}
|
2014-05-12 15:46:38 -07:00
|
|
|
}
|
|
|
|
}
|
2014-05-10 01:06:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
|
|
|
// string of YAML), returning any error encountered. It will ignore unknown
|
|
|
|
// fields but log encountering them.
|
2014-03-21 10:35:18 -07:00
|
|
|
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
2014-03-04 16:36:05 -08:00
|
|
|
var cfg CloudConfig
|
2014-03-21 10:35:18 -07:00
|
|
|
err := goyaml.Unmarshal([]byte(contents), &cfg)
|
2014-05-10 01:06:38 -07:00
|
|
|
if err != nil {
|
|
|
|
return &cfg, err
|
|
|
|
}
|
|
|
|
warnOnUnrecognizedKeys(contents, log.Printf)
|
|
|
|
return &cfg, nil
|
2014-03-04 16:36:05 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cc CloudConfig) String() string {
|
|
|
|
bytes, err := goyaml.Marshal(cc)
|
2014-03-12 16:52:32 -07:00
|
|
|
if err != nil {
|
2014-03-04 16:36:05 -08:00
|
|
|
return ""
|
|
|
|
}
|
2014-03-12 16:52:32 -07:00
|
|
|
|
|
|
|
stringified := string(bytes)
|
|
|
|
stringified = fmt.Sprintf("#cloud-config\n%s", stringified)
|
|
|
|
|
|
|
|
return stringified
|
2014-03-04 16:36:05 -08:00
|
|
|
}
|
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
// 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.
|
2014-03-18 09:00:41 -07:00
|
|
|
func Apply(cfg CloudConfig, env *Environment) error {
|
2014-03-12 22:30:24 -07:00
|
|
|
if cfg.Hostname != "" {
|
2014-03-18 09:00:41 -07:00
|
|
|
if err := system.SetHostname(cfg.Hostname); err != nil {
|
2014-03-12 22:30:24 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Printf("Set hostname to %s", cfg.Hostname)
|
|
|
|
}
|
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
for _, user := range cfg.Users {
|
|
|
|
if user.Name == "" {
|
|
|
|
log.Printf("User object has no 'name' field, skipping")
|
|
|
|
continue
|
2014-03-23 11:06:43 -07:00
|
|
|
}
|
2014-03-13 10:56:59 -07:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
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)
|
2014-03-13 10:56:59 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
} 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
|
|
|
|
}
|
|
|
|
}
|
2014-03-13 10:56:59 -07:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
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
|
2014-03-13 10:56:59 -07:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
}
|
|
|
|
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
|
2014-03-19 08:54:45 -07:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
}
|
|
|
|
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
|
2014-03-22 15:26:18 -07:00
|
|
|
}
|
2014-03-13 10:56:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-13 11:39:31 -07:00
|
|
|
if len(cfg.SSHAuthorizedKeys) > 0 {
|
2014-03-18 09:00:41 -07:00
|
|
|
err := system.AuthorizeSSHKeys("core", env.SSHKeyName(), cfg.SSHAuthorizedKeys)
|
2014-03-04 16:36:05 -08:00
|
|
|
if err == nil {
|
|
|
|
log.Printf("Authorized SSH keys for core user")
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
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)
|
2014-03-11 17:46:58 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-09 20:36:26 -07:00
|
|
|
for _, ccu := range []CloudConfigUnit{cfg.Coreos.Etcd, cfg.Coreos.Fleet, cfg.Coreos.Update} {
|
2014-06-05 16:12:40 -07:00
|
|
|
u, err := ccu.Units(env.Root())
|
2014-05-09 20:33:34 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-06-05 16:12:40 -07:00
|
|
|
cfg.Coreos.Units = append(cfg.Coreos.Units, u...)
|
2014-03-04 16:36:05 -08:00
|
|
|
}
|
|
|
|
|
2014-07-10 15:44:52 -07:00
|
|
|
wroteEnvironment := false
|
2014-05-09 20:33:34 -07:00
|
|
|
for _, file := range cfg.WriteFiles {
|
2014-07-10 15:44:52 -07:00
|
|
|
fullPath, err := system.WriteFile(&file, env.Root())
|
2014-06-05 12:48:32 -07:00
|
|
|
if err != nil {
|
2014-05-09 20:33:34 -07:00
|
|
|
return err
|
2014-05-06 17:44:18 -07:00
|
|
|
}
|
2014-07-10 15:44:52 -07:00
|
|
|
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")
|
|
|
|
}
|
2014-05-06 17:44:18 -07:00
|
|
|
}
|
|
|
|
|
2014-06-18 12:08:10 -07:00
|
|
|
if env.NetconfType() != "" {
|
2014-08-15 10:29:08 -07:00
|
|
|
filename := path.Join(env.ConfigRoot(), cfg.NetworkConfigPath)
|
|
|
|
log.Printf("Attempting to read config from %q\n", filename)
|
|
|
|
netconfBytes, err := ioutil.ReadFile(filename)
|
2014-06-18 12:08:10 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var interfaces []network.InterfaceGenerator
|
|
|
|
switch env.NetconfType() {
|
|
|
|
case "debian":
|
|
|
|
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := system.WriteNetworkdConfigs(interfaces); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := system.RestartNetwork(interfaces); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-05 17:40:53 -07:00
|
|
|
um := system.NewUnitManager(env.Root())
|
|
|
|
return processUnits(cfg.Coreos.Units, env.Root(), um)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// processUnits takes a set of Units and applies them to the given root using
|
|
|
|
// the given UnitManager. This can involve things like writing unit files to
|
|
|
|
// disk, masking/unmasking units, or invoking systemd
|
|
|
|
// commands against units. It returns any error encountered.
|
|
|
|
func processUnits(units []system.Unit, root string, um system.UnitManager) error {
|
2014-05-09 20:33:34 -07:00
|
|
|
commands := make(map[string]string, 0)
|
|
|
|
reload := false
|
2014-06-05 17:40:53 -07:00
|
|
|
for _, unit := range units {
|
|
|
|
dst := unit.Destination(root)
|
2014-05-09 20:33:34 -07:00
|
|
|
if unit.Content != "" {
|
|
|
|
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
2014-06-05 17:40:53 -07:00
|
|
|
if err := um.PlaceUnit(&unit, dst); err != nil {
|
2014-05-09 20:33:34 -07:00
|
|
|
return err
|
2014-04-15 09:00:19 -07:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
|
|
|
reload = true
|
|
|
|
}
|
2014-03-19 15:52:24 -07:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
if unit.Mask {
|
|
|
|
log.Printf("Masking unit file %s", unit.Name)
|
2014-06-05 17:40:53 -07:00
|
|
|
if err := um.MaskUnit(&unit); err != nil {
|
2014-06-03 16:49:26 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if unit.Runtime {
|
|
|
|
log.Printf("Ensuring runtime unit file %s is unmasked", unit.Name)
|
2014-06-05 17:40:53 -07:00
|
|
|
if err := um.UnmaskUnit(&unit); err != nil {
|
2014-05-09 20:33:34 -07:00
|
|
|
return err
|
2014-03-12 15:43:51 -07:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
}
|
2014-03-12 15:43:51 -07:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
if unit.Enable {
|
|
|
|
if unit.Group() != "network" {
|
2014-05-26 15:16:24 -07:00
|
|
|
log.Printf("Enabling unit file %s", unit.Name)
|
2014-06-05 17:40:53 -07:00
|
|
|
if err := um.EnableUnitFile(unit.Name, unit.Runtime); err != nil {
|
2014-05-09 20:33:34 -07:00
|
|
|
return err
|
2014-03-24 14:12:52 -07:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
log.Printf("Enabled unit %s", unit.Name)
|
|
|
|
} else {
|
|
|
|
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
2014-03-12 15:43:51 -07:00
|
|
|
}
|
|
|
|
}
|
2014-03-19 15:52:24 -07:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
if unit.Group() == "network" {
|
|
|
|
commands["systemd-networkd.service"] = "restart"
|
|
|
|
} else if unit.Command != "" {
|
|
|
|
commands[unit.Name] = unit.Command
|
2014-05-05 13:16:07 -07:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
}
|
2014-05-05 13:16:07 -07:00
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
if reload {
|
2014-06-05 17:40:53 -07:00
|
|
|
if err := um.DaemonReload(); err != nil {
|
2014-05-09 20:33:34 -07:00
|
|
|
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
2014-03-19 15:52:24 -07:00
|
|
|
}
|
2014-03-12 15:43:51 -07:00
|
|
|
}
|
|
|
|
|
2014-05-09 20:33:34 -07:00
|
|
|
for unit, command := range commands {
|
|
|
|
log.Printf("Calling unit command '%s %s'", command, unit)
|
2014-06-05 17:40:53 -07:00
|
|
|
res, err := um.RunUnitCommand(command, unit)
|
2014-05-09 20:33:34 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-04-01 16:02:12 -06:00
|
|
|
}
|
2014-05-09 20:33:34 -07:00
|
|
|
log.Printf("Result of '%s %s': %s", command, unit, res)
|
2014-04-01 16:02:12 -06:00
|
|
|
}
|
|
|
|
|
2014-03-04 16:36:05 -08:00
|
|
|
return nil
|
|
|
|
}
|