2015-01-25 06:32:33 +03:00
|
|
|
// Copyright 2015 CoreOS, Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2014-10-18 02:36:22 +04:00
|
|
|
|
2014-03-18 20:00:41 +04:00
|
|
|
package initialize
|
2014-03-05 04:36:05 +04:00
|
|
|
|
|
|
|
import (
|
2014-05-10 07:33:34 +04:00
|
|
|
"errors"
|
2014-03-13 03:52:32 +04:00
|
|
|
"fmt"
|
2014-03-05 04:36:05 +04:00
|
|
|
"log"
|
2015-04-02 13:34:57 +03:00
|
|
|
"os"
|
2015-04-23 17:40:28 +03:00
|
|
|
"os/exec"
|
2014-06-18 23:08:10 +04:00
|
|
|
"path"
|
2015-04-23 17:40:28 +03:00
|
|
|
"strings"
|
2014-03-05 04:36:05 +04:00
|
|
|
|
2015-11-09 11:24:52 +03:00
|
|
|
"github.com/coreos/coreos-cloudinit/config"
|
|
|
|
"github.com/coreos/coreos-cloudinit/network"
|
|
|
|
"github.com/coreos/coreos-cloudinit/system"
|
2014-03-18 20:00:41 +04:00
|
|
|
)
|
2014-03-06 02:30:38 +04:00
|
|
|
|
2014-05-10 07:33:34 +04: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.
|
2014-09-22 06:22:13 +04:00
|
|
|
File() (*system.File, error)
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// CloudConfigUnit represents a CoreOS specific configuration option that can generate
|
2014-06-06 03:12:40 +04:00
|
|
|
// associated system.Units to be created/enabled appropriately
|
2014-05-10 07:33:34 +04:00
|
|
|
type CloudConfigUnit interface {
|
2014-10-07 02:14:29 +04:00
|
|
|
Units() []system.Unit
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
func isLock(env *Environment) bool {
|
|
|
|
if _, err := os.Stat(path.Join(env.Workspace(), ".lock")); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func Lock(env *Environment) error {
|
|
|
|
if !isLock(env) {
|
|
|
|
fp, err := os.OpenFile(path.Join(env.Workspace(), ".lock"), os.O_WRONLY|os.O_CREATE|os.O_EXCL|os.O_TRUNC, os.FileMode(0644))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return fp.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-05-10 07:33:34 +04: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.
|
2015-01-27 04:35:08 +03:00
|
|
|
func Apply(cfg config.CloudConfig, ifaces []network.InterfaceGenerator, env *Environment) error {
|
2015-04-02 13:34:57 +03:00
|
|
|
var err error
|
|
|
|
|
2015-11-09 21:29:30 +03:00
|
|
|
for _, cmdline := range cfg.RunCMD {
|
|
|
|
prog := strings.Fields(cmdline)[0]
|
|
|
|
args := strings.Fields(cmdline)[1:]
|
|
|
|
exec.Command(prog, args...).Run()
|
|
|
|
}
|
|
|
|
|
2015-04-02 13:34:57 +03:00
|
|
|
if err = os.MkdirAll(env.Workspace(), os.FileMode(0755)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
if !isLock(env) {
|
|
|
|
if cfg.Hostname != "" {
|
|
|
|
if err = system.SetHostname(cfg.Hostname); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Printf("Set hostname to %s", cfg.Hostname)
|
2014-03-13 09:30:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
for _, user := range cfg.Users {
|
|
|
|
if user.Name == "" {
|
|
|
|
log.Printf("User object has no 'name' field, skipping")
|
|
|
|
continue
|
2014-03-23 22:06:43 +04:00
|
|
|
}
|
2014-03-13 21:56:59 +04:00
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
if !isLock(env) {
|
|
|
|
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)
|
2014-03-13 21:56:59 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-11-22 11:51:25 +03:00
|
|
|
|
|
|
|
if err = system.LockUnlockUser(&user); err != nil {
|
|
|
|
log.Printf("Failed lock/unlock user '%s': %v", user.Name, err)
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-03-13 21:56:59 +04:00
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
if len(user.SSHAuthorizedKeys) > 0 {
|
|
|
|
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
2015-04-02 13:34:57 +03:00
|
|
|
if err = system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
2014-03-13 21:56:59 +04:00
|
|
|
}
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
|
|
|
if user.SSHImportGithubUser != "" {
|
|
|
|
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", user.SSHImportGithubUser, user.Name)
|
2015-04-02 13:34:57 +03:00
|
|
|
if err = SSHImportGithubUser(user.Name, user.SSHImportGithubUser); err != nil {
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
2014-03-19 19:54:45 +04:00
|
|
|
}
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
2015-01-21 00:45:52 +03:00
|
|
|
for _, u := range user.SSHImportGithubUsers {
|
|
|
|
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", u, user.Name)
|
2015-04-02 13:34:57 +03:00
|
|
|
if err = SSHImportGithubUser(user.Name, u); err != nil {
|
2015-01-21 00:45:52 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-05-10 07:33:34 +04:00
|
|
|
if user.SSHImportURL != "" {
|
|
|
|
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
2015-04-02 13:34:57 +03:00
|
|
|
if err = SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
2014-03-23 02:26:18 +04:00
|
|
|
}
|
2014-03-13 21:56:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-13 22:39:31 +04:00
|
|
|
if len(cfg.SSHAuthorizedKeys) > 0 {
|
2015-04-02 13:34:57 +03:00
|
|
|
err = system.AuthorizeSSHKeys(cfg.SystemInfo.DefaultUser.Name, env.SSHKeyName(), cfg.SSHAuthorizedKeys)
|
2014-03-05 04:36:05 +04:00
|
|
|
if err == nil {
|
2015-03-26 10:23:27 +03:00
|
|
|
log.Printf("Authorized SSH keys for %s user", cfg.SystemInfo.DefaultUser.Name)
|
2014-03-05 04:36:05 +04:00
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
if !isLock(env) {
|
|
|
|
var writeFiles []system.File
|
|
|
|
for _, file := range cfg.WriteFiles {
|
|
|
|
writeFiles = append(writeFiles, system.File{File: file})
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
2017-11-22 11:51:25 +03:00
|
|
|
|
|
|
|
for _, ccf := range []CloudConfigFile{
|
|
|
|
system.OEM{OEM: cfg.CoreOS.OEM},
|
|
|
|
system.Update{Update: cfg.CoreOS.Update, ReadConfig: system.DefaultReadConfig},
|
|
|
|
system.EtcHosts{EtcHosts: cfg.ManageEtcHosts},
|
|
|
|
system.Flannel{Flannel: cfg.CoreOS.Flannel},
|
|
|
|
} {
|
|
|
|
f, err := ccf.File()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if f != nil {
|
|
|
|
writeFiles = append(writeFiles, *f)
|
|
|
|
}
|
2014-03-12 04:46:58 +04:00
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
var units []system.Unit
|
|
|
|
for _, u := range cfg.CoreOS.Units {
|
|
|
|
units = append(units, system.Unit{Unit: u})
|
|
|
|
}
|
2014-09-22 06:22:38 +04:00
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
for _, ccu := range []CloudConfigUnit{
|
|
|
|
system.Etcd{Etcd: cfg.CoreOS.Etcd},
|
|
|
|
system.Etcd2{Etcd2: cfg.CoreOS.Etcd2},
|
|
|
|
system.Fleet{Fleet: cfg.CoreOS.Fleet},
|
|
|
|
system.Locksmith{Locksmith: cfg.CoreOS.Locksmith},
|
|
|
|
system.Update{Update: cfg.CoreOS.Update, ReadConfig: system.DefaultReadConfig},
|
|
|
|
} {
|
|
|
|
units = append(units, ccu.Units()...)
|
|
|
|
}
|
2014-03-05 04:36:05 +04:00
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
wroteEnvironment := false
|
|
|
|
for _, file := range writeFiles {
|
|
|
|
fullPath, err := system.WriteFile(&file, env.Root())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if path.Clean(file.Path) == "/etc/environment" {
|
|
|
|
wroteEnvironment = true
|
|
|
|
}
|
|
|
|
log.Printf("Wrote file %s to filesystem", fullPath)
|
2014-05-07 04:44:18 +04:00
|
|
|
}
|
2017-11-22 11:51:25 +03:00
|
|
|
|
|
|
|
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-07-11 02:44:52 +04:00
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
if len(ifaces) > 0 {
|
|
|
|
units = append(units, createNetworkingUnits(ifaces)...)
|
|
|
|
if err = system.RestartNetwork(ifaces); err != nil {
|
2014-07-11 02:44:52 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-05-07 04:44:18 +04:00
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
um := system.NewUnitManager(env.Root())
|
|
|
|
if err = processUnits(units, env.Root(), um); err != nil {
|
2014-06-18 23:08:10 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
if cfg.ResizeRootfs {
|
|
|
|
log.Printf("resize root filesystem")
|
|
|
|
if err = system.ResizeRootFS(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-02 13:34:57 +03:00
|
|
|
}
|
|
|
|
|
2017-11-22 11:51:25 +03:00
|
|
|
return Lock(env)
|
2014-10-14 02:44:48 +04:00
|
|
|
}
|
2014-06-06 04:40:53 +04:00
|
|
|
|
2014-10-14 02:44:48 +04:00
|
|
|
func createNetworkingUnits(interfaces []network.InterfaceGenerator) (units []system.Unit) {
|
|
|
|
appendNewUnit := func(units []system.Unit, name, content string) []system.Unit {
|
|
|
|
if content == "" {
|
|
|
|
return units
|
|
|
|
}
|
|
|
|
return append(units, system.Unit{Unit: config.Unit{
|
|
|
|
Name: name,
|
|
|
|
Runtime: true,
|
|
|
|
Content: content,
|
|
|
|
}})
|
|
|
|
}
|
|
|
|
for _, i := range interfaces {
|
|
|
|
units = appendNewUnit(units, fmt.Sprintf("%s.netdev", i.Filename()), i.Netdev())
|
|
|
|
units = appendNewUnit(units, fmt.Sprintf("%s.link", i.Filename()), i.Link())
|
|
|
|
units = appendNewUnit(units, fmt.Sprintf("%s.network", i.Filename()), i.Network())
|
|
|
|
}
|
|
|
|
return units
|
2014-06-06 04:40:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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-09-03 04:11:17 +04:00
|
|
|
type action struct {
|
2014-11-25 03:42:31 +03:00
|
|
|
unit system.Unit
|
2014-09-03 04:11:17 +04:00
|
|
|
command string
|
|
|
|
}
|
|
|
|
actions := make([]action, 0, len(units))
|
2014-05-10 07:33:34 +04:00
|
|
|
reload := false
|
2014-12-02 23:46:35 +03:00
|
|
|
restartNetworkd := false
|
2014-06-06 04:40:53 +04:00
|
|
|
for _, unit := range units {
|
2014-11-26 03:57:15 +03:00
|
|
|
if unit.Name == "" {
|
|
|
|
log.Printf("Skipping unit without name")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
if unit.Content != "" {
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Writing unit %q to filesystem", unit.Name)
|
|
|
|
if err := um.PlaceUnit(unit); err != nil {
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
2014-04-15 20:00:19 +04:00
|
|
|
}
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Wrote unit %q", unit.Name)
|
2014-05-10 07:33:34 +04:00
|
|
|
reload = true
|
|
|
|
}
|
2014-03-20 02:52:24 +04:00
|
|
|
|
2014-11-26 03:57:15 +03:00
|
|
|
for _, dropin := range unit.DropIns {
|
|
|
|
if dropin.Name != "" && dropin.Content != "" {
|
|
|
|
log.Printf("Writing drop-in unit %q to filesystem", dropin.Name)
|
|
|
|
if err := um.PlaceUnitDropIn(unit, dropin); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Printf("Wrote drop-in unit %q", dropin.Name)
|
|
|
|
reload = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
if unit.Mask {
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Masking unit file %q", unit.Name)
|
|
|
|
if err := um.MaskUnit(unit); err != nil {
|
2014-06-04 03:49:26 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if unit.Runtime {
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Ensuring runtime unit file %q is unmasked", unit.Name)
|
|
|
|
if err := um.UnmaskUnit(unit); err != nil {
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
2014-03-13 02:43:51 +04:00
|
|
|
}
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
2014-03-13 02:43:51 +04:00
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
if unit.Enable {
|
|
|
|
if unit.Group() != "network" {
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Enabling unit file %q", unit.Name)
|
|
|
|
if err := um.EnableUnitFile(unit); err != nil {
|
2014-05-10 07:33:34 +04:00
|
|
|
return err
|
2014-03-25 01:12:52 +04:00
|
|
|
}
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Enabled unit %q", unit.Name)
|
2014-05-10 07:33:34 +04:00
|
|
|
} else {
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Skipping enable for network-like unit %q", unit.Name)
|
2014-03-13 02:43:51 +04:00
|
|
|
}
|
|
|
|
}
|
2014-03-20 02:52:24 +04:00
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
if unit.Group() == "network" {
|
2014-12-02 23:46:35 +03:00
|
|
|
restartNetworkd = true
|
2014-05-10 07:33:34 +04:00
|
|
|
} else if unit.Command != "" {
|
2014-11-25 03:42:31 +03:00
|
|
|
actions = append(actions, action{unit, unit.Command})
|
2014-05-06 00:16:07 +04:00
|
|
|
}
|
2014-05-10 07:33:34 +04:00
|
|
|
}
|
2014-05-06 00:16:07 +04:00
|
|
|
|
2014-05-10 07:33:34 +04:00
|
|
|
if reload {
|
2014-06-06 04:40:53 +04:00
|
|
|
if err := um.DaemonReload(); err != nil {
|
2014-11-25 03:42:31 +03:00
|
|
|
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %s", err))
|
2014-03-20 02:52:24 +04:00
|
|
|
}
|
2014-03-13 02:43:51 +04:00
|
|
|
}
|
|
|
|
|
2014-12-02 23:46:35 +03:00
|
|
|
if restartNetworkd {
|
2014-12-03 04:21:31 +03:00
|
|
|
log.Printf("Restarting systemd-networkd")
|
2014-12-02 23:46:35 +03:00
|
|
|
networkd := system.Unit{Unit: config.Unit{Name: "systemd-networkd.service"}}
|
2014-12-03 04:21:31 +03:00
|
|
|
res, err := um.RunUnitCommand(networkd, "restart")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Printf("Restarted systemd-networkd (%s)", res)
|
2014-12-02 23:46:35 +03:00
|
|
|
}
|
|
|
|
|
2014-09-03 04:11:17 +04:00
|
|
|
for _, action := range actions {
|
2014-11-25 03:42:31 +03:00
|
|
|
log.Printf("Calling unit command %q on %q'", action.command, action.unit.Name)
|
|
|
|
res, err := um.RunUnitCommand(action.unit, action.command)
|
2014-05-10 07:33:34 +04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-04-02 02:02:12 +04:00
|
|
|
}
|
2014-12-03 04:21:31 +03:00
|
|
|
log.Printf("Result of %q on %q: %s", action.command, action.unit.Name, res)
|
2014-04-02 02:02:12 +04:00
|
|
|
}
|
|
|
|
|
2014-03-05 04:36:05 +04:00
|
|
|
return nil
|
|
|
|
}
|