refactor(*): Break apart packages
This commit is contained in:
77
system/file.go
Normal file
77
system/file.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Encoding string
|
||||
Content string
|
||||
Owner string
|
||||
Path string
|
||||
RawFilePermissions string `yaml:"permissions"`
|
||||
}
|
||||
|
||||
func (f *File) Permissions() (os.FileMode, error) {
|
||||
if f.RawFilePermissions == "" {
|
||||
return os.FileMode(0644), nil
|
||||
}
|
||||
|
||||
// Parse string representation of file mode as octal
|
||||
perm, err := strconv.ParseInt(f.RawFilePermissions, 8, 32)
|
||||
if err != nil {
|
||||
return 0, errors.New("Unable to parse file permissions as octal integer")
|
||||
}
|
||||
return os.FileMode(perm), nil
|
||||
}
|
||||
|
||||
|
||||
func WriteFile(f *File) error {
|
||||
if f.Encoding != "" {
|
||||
return fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(f.Path), os.FileMode(0755)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
perm, err := f.Permissions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(f.Path, []byte(f.Content), perm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.Owner != "" {
|
||||
// We shell out since we don't have a way to look up unix groups natively
|
||||
cmd := exec.Command("chown", f.Owner, f.Path)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureDirectoryExists(dir string) error {
|
||||
info, err := os.Stat(dir)
|
||||
if err == nil {
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("%s is not a directory", dir)
|
||||
}
|
||||
} else {
|
||||
err = os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
111
system/file_test.go
Normal file
111
system/file_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteFileUnencodedContent(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
|
||||
fullPath := path.Join(dir, "tmp", "foo")
|
||||
|
||||
wf := File{
|
||||
Path: fullPath,
|
||||
Content: "bar",
|
||||
RawFilePermissions: "0644",
|
||||
}
|
||||
|
||||
if err := WriteFile(&wf); err != nil {
|
||||
t.Fatalf("Processing of WriteFile failed: %v", err)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat file: %v", err)
|
||||
}
|
||||
|
||||
if fi.Mode() != os.FileMode(0644) {
|
||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read expected file: %v", err)
|
||||
}
|
||||
|
||||
if string(contents) != "bar" {
|
||||
t.Fatalf("File has incorrect contents")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFileInvalidPermission(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
|
||||
wf := File{
|
||||
Path: path.Join(dir, "tmp", "foo"),
|
||||
Content: "bar",
|
||||
RawFilePermissions: "pants",
|
||||
}
|
||||
|
||||
if err := WriteFile(&wf); err == nil {
|
||||
t.Fatalf("Expected error to be raised when writing file with invalid permission")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFilePermissions(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
|
||||
fullPath := path.Join(dir, "tmp", "foo")
|
||||
|
||||
wf := File{
|
||||
Path: fullPath,
|
||||
RawFilePermissions: "0755",
|
||||
}
|
||||
|
||||
if err := WriteFile(&wf); err != nil {
|
||||
t.Fatalf("Processing of WriteFile failed: %v", err)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat file: %v", err)
|
||||
}
|
||||
|
||||
if fi.Mode() != os.FileMode(0755) {
|
||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFileEncodedContent(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
|
||||
wf := File{
|
||||
Path: path.Join(dir, "tmp", "foo"),
|
||||
Content: "",
|
||||
Encoding: "base64",
|
||||
}
|
||||
|
||||
if err := WriteFile(&wf); err == nil {
|
||||
t.Fatalf("Expected error to be raised when writing file with encoding")
|
||||
}
|
||||
}
|
59
system/ssh_key.go
Normal file
59
system/ssh_key.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Add the provide SSH public key to the core user's list of
|
||||
// authorized keys
|
||||
func AuthorizeSSHKeys(user string, keysName string, keys []string) error {
|
||||
for i, key := range keys {
|
||||
keys[i] = strings.TrimSpace(key)
|
||||
}
|
||||
|
||||
// join all keys with newlines, ensuring the resulting string
|
||||
// also ends with a newline
|
||||
joined := fmt.Sprintf("%s\n", strings.Join(keys, "\n"))
|
||||
|
||||
cmd := exec.Command("update-ssh-keys", "-u", user, "-a", keysName)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
stdin.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.WriteString(stdin, joined)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdin.Close()
|
||||
stdoutBytes, _ := ioutil.ReadAll(stdout)
|
||||
stderrBytes, _ := ioutil.ReadAll(stderr)
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Call to update-ssh-keys failed with %v: %s %s", err, string(stdoutBytes), string(stderrBytes))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
163
system/systemd.go
Normal file
163
system/systemd.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/coreos/go-systemd/dbus"
|
||||
)
|
||||
|
||||
type Unit struct {
|
||||
Name string
|
||||
Runtime bool
|
||||
Content string
|
||||
}
|
||||
|
||||
func (u *Unit) Type() string {
|
||||
ext := filepath.Ext(u.Name)
|
||||
return strings.TrimLeft(ext, ".")
|
||||
}
|
||||
|
||||
func (u *Unit) Group() (group string) {
|
||||
t := u.Type()
|
||||
if t == "network" || t == "netdev" || t == "link" {
|
||||
group = "network"
|
||||
} else {
|
||||
group = "system"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Script []byte
|
||||
|
||||
func PlaceUnit(u *Unit, root string) (string, error) {
|
||||
dir := "etc"
|
||||
if u.Runtime {
|
||||
dir = "run"
|
||||
}
|
||||
|
||||
dst := path.Join(root, dir, "systemd", u.Group())
|
||||
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dst, os.FileMode(0755)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
dst = path.Join(dst, u.Name)
|
||||
|
||||
file := File{
|
||||
Path: dst,
|
||||
Content: u.Content,
|
||||
RawFilePermissions: "0644",
|
||||
}
|
||||
|
||||
err := WriteFile(&file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func EnableUnitFile(file string, runtime bool) error {
|
||||
conn, err := dbus.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files := []string{file}
|
||||
_, _, err = conn.EnableUnitFiles(files, runtime, true)
|
||||
return err
|
||||
}
|
||||
|
||||
func separateNetworkUnits(units []Unit) ([]Unit, []Unit) {
|
||||
networkUnits := make([]Unit, 0)
|
||||
nonNetworkUnits := make([]Unit, 0)
|
||||
for _, unit := range units {
|
||||
if unit.Group() == "network" {
|
||||
networkUnits = append(networkUnits, unit)
|
||||
} else {
|
||||
nonNetworkUnits = append(nonNetworkUnits, unit)
|
||||
}
|
||||
}
|
||||
return networkUnits, nonNetworkUnits
|
||||
}
|
||||
|
||||
func StartUnits(units []Unit) error {
|
||||
networkUnits, nonNetworkUnits := separateNetworkUnits(units)
|
||||
if len(networkUnits) > 0 {
|
||||
if err := RestartUnitByName("systemd-networkd.service"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, unit := range nonNetworkUnits {
|
||||
if err := RestartUnitByName(unit.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DaemonReload() error {
|
||||
conn, err := dbus.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Reload()
|
||||
return err
|
||||
}
|
||||
|
||||
func RestartUnitByName(name string) error {
|
||||
log.Printf("Restarting unit %s", name)
|
||||
conn, err := dbus.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output, err := conn.RestartUnit(name, "replace")
|
||||
log.Printf("Restart completed with '%s'", output)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func StartUnitByName(name string) error {
|
||||
conn, err := dbus.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.StartUnit(name, "replace")
|
||||
return err
|
||||
}
|
||||
|
||||
func ExecuteScript(scriptPath string) (string, error) {
|
||||
props := []dbus.Property{
|
||||
dbus.PropDescription("Unit generated and executed by coreos-cloudinit on behalf of user"),
|
||||
dbus.PropExecStart([]string{"/bin/bash", scriptPath}, false),
|
||||
}
|
||||
|
||||
base := path.Base(scriptPath)
|
||||
name := fmt.Sprintf("coreos-cloudinit-%s.service", base)
|
||||
|
||||
log.Printf("Creating transient systemd unit '%s'", name)
|
||||
|
||||
conn, err := dbus.New()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = conn.StartTransientUnit(name, "replace", props...)
|
||||
return name, err
|
||||
}
|
||||
|
||||
func SetHostname(hostname string) error {
|
||||
return exec.Command("hostnamectl", "set-hostname", hostname).Run()
|
||||
}
|
102
system/systemd_test.go
Normal file
102
system/systemd_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlaceNetworkUnit(t *testing.T) {
|
||||
u := Unit{
|
||||
Name: "50-eth0.network",
|
||||
Runtime: true,
|
||||
Content: `[Match]
|
||||
Name=eth47
|
||||
|
||||
[Network]
|
||||
Address=10.209.171.177/19
|
||||
`,
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
|
||||
if _, err := PlaceUnit(&u, dir); err != nil {
|
||||
t.Fatalf("PlaceUnit failed: %v", err)
|
||||
}
|
||||
|
||||
fullPath := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat file: %v", err)
|
||||
}
|
||||
|
||||
if fi.Mode() != os.FileMode(0644) {
|
||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read expected file: %v", err)
|
||||
}
|
||||
|
||||
expect := `[Match]
|
||||
Name=eth47
|
||||
|
||||
[Network]
|
||||
Address=10.209.171.177/19
|
||||
`
|
||||
if string(contents) != expect {
|
||||
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaceMountUnit(t *testing.T) {
|
||||
u := Unit{
|
||||
Name: "media-state.mount",
|
||||
Runtime: false,
|
||||
Content: `[Mount]
|
||||
What=/dev/sdb1
|
||||
Where=/media/state
|
||||
`,
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
|
||||
if _, err := PlaceUnit(&u, dir); err != nil {
|
||||
t.Fatalf("PlaceUnit failed: %v", err)
|
||||
}
|
||||
|
||||
fullPath := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat file: %v", err)
|
||||
}
|
||||
|
||||
if fi.Mode() != os.FileMode(0644) {
|
||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read expected file: %v", err)
|
||||
}
|
||||
|
||||
expect := `[Mount]
|
||||
What=/dev/sdb1
|
||||
Where=/media/state
|
||||
`
|
||||
if string(contents) != expect {
|
||||
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expect)
|
||||
}
|
||||
}
|
||||
|
106
system/user.go
Normal file
106
system/user.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string `yaml:"name"`
|
||||
PasswordHash string `yaml:"passwd"`
|
||||
SSHAuthorizedKeys []string `yaml:"ssh-authorized-keys"`
|
||||
GECOS string `yaml:"gecos"`
|
||||
Homedir string `yaml:"homedir"`
|
||||
NoCreateHome bool `yaml:"no-create-home"`
|
||||
PrimaryGroup string `yaml:"primary-group"`
|
||||
Groups []string `yaml:"groups"`
|
||||
NoUserGroup bool `yaml:"no-user-group"`
|
||||
System bool `yaml:"system"`
|
||||
NoLogInit bool `yaml:"no-log-init"`
|
||||
}
|
||||
|
||||
func UserExists(u *User) bool {
|
||||
_, err := user.Lookup(u.Name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func CreateUser(u *User) error {
|
||||
args := []string{}
|
||||
|
||||
if u.PasswordHash != "" {
|
||||
args = append(args, "--password", u.PasswordHash)
|
||||
}
|
||||
|
||||
if u.GECOS != "" {
|
||||
args = append(args, "--comment", fmt.Sprintf("%q", u.GECOS))
|
||||
}
|
||||
|
||||
if u.Homedir != "" {
|
||||
args = append(args, "--home-dir", u.Homedir)
|
||||
}
|
||||
|
||||
if u.NoCreateHome {
|
||||
args = append(args, "--no-create-home")
|
||||
} else {
|
||||
args = append(args, "--create-home")
|
||||
}
|
||||
|
||||
if u.PrimaryGroup != "" {
|
||||
args = append(args, "--primary-group", u.PrimaryGroup)
|
||||
}
|
||||
|
||||
if len(u.Groups) > 0 {
|
||||
args = append(args, "--groups", strings.Join(u.Groups, ","))
|
||||
}
|
||||
|
||||
if u.NoUserGroup {
|
||||
args = append(args, "--no-user-group")
|
||||
}
|
||||
|
||||
if u.System {
|
||||
args = append(args, "--system")
|
||||
}
|
||||
|
||||
if u.NoLogInit {
|
||||
args = append(args, "--no-log-init")
|
||||
}
|
||||
|
||||
args = append(args, u.Name)
|
||||
|
||||
output, err := exec.Command("useradd", args...).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Command 'useradd %s' failed: %v\n%s", strings.Join(args, " "), err, output)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func SetUserPassword(user, hash string) error {
|
||||
cmd := exec.Command("/usr/sbin/chpasswd", "-e")
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
arg := fmt.Sprintf("%s:%s", user, hash)
|
||||
_, err = stdin.Write([]byte(arg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user