Merge pull request #5 from bcwaldon/write_files
Implement write_files feature
This commit is contained in:
commit
6803aed2dd
@ -10,7 +10,11 @@ const DefaultSSHKeyName = "coreos-cloudinit"
|
|||||||
|
|
||||||
type CloudConfig struct {
|
type CloudConfig struct {
|
||||||
SSH_Authorized_Keys []string
|
SSH_Authorized_Keys []string
|
||||||
Coreos struct{Etcd struct{ Discovery_URL string }; Fleet struct{ Autostart bool } }
|
Coreos struct {
|
||||||
|
Etcd struct{ Discovery_URL string }
|
||||||
|
Fleet struct{ Autostart bool }
|
||||||
|
}
|
||||||
|
Write_Files []WriteFile
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloudConfig(contents []byte) (*CloudConfig, error) {
|
func NewCloudConfig(contents []byte) (*CloudConfig, error) {
|
||||||
@ -38,6 +42,15 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.Write_Files) > 0 {
|
||||||
|
for _, file := range cfg.Write_Files {
|
||||||
|
if err := ProcessWriteFile("/", &file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Wrote file %s to filesystem", file.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Coreos.Etcd.Discovery_URL != "" {
|
if cfg.Coreos.Etcd.Discovery_URL != "" {
|
||||||
err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL)
|
err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -23,6 +23,10 @@ func TestCloudConfigEmpty(t *testing.T) {
|
|||||||
if cfg.Coreos.Fleet.Autostart {
|
if cfg.Coreos.Fleet.Autostart {
|
||||||
t.Error("Expected AutostartFleet not to be defined")
|
t.Error("Expected AutostartFleet not to be defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.Write_Files) != 0 {
|
||||||
|
t.Error("Expected zero Write_Files")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the parsing of a cloud config file "generally works"
|
// Assert that the parsing of a cloud config file "generally works"
|
||||||
@ -36,6 +40,13 @@ coreos:
|
|||||||
ssh_authorized_keys:
|
ssh_authorized_keys:
|
||||||
- foobar
|
- foobar
|
||||||
- foobaz
|
- foobaz
|
||||||
|
write_files:
|
||||||
|
- content: |
|
||||||
|
penny
|
||||||
|
elroy
|
||||||
|
path: /etc/dogepack.conf
|
||||||
|
permissions: '0644'
|
||||||
|
owner: root:dogepack
|
||||||
`)
|
`)
|
||||||
cfg, err := NewCloudConfig(contents)
|
cfg, err := NewCloudConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,6 +69,27 @@ ssh_authorized_keys:
|
|||||||
if !cfg.Coreos.Fleet.Autostart {
|
if !cfg.Coreos.Fleet.Autostart {
|
||||||
t.Error("Expected AutostartFleet to be true")
|
t.Error("Expected AutostartFleet to be true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.Write_Files) != 1 {
|
||||||
|
t.Error("Failed to parse correct number of write_files")
|
||||||
|
} else {
|
||||||
|
wf := cfg.Write_Files[0]
|
||||||
|
if wf.Content != "penny\nelroy\n" {
|
||||||
|
t.Errorf("WriteFile has incorrect contents '%s'", wf.Content)
|
||||||
|
}
|
||||||
|
if wf.Encoding != "" {
|
||||||
|
t.Errorf("WriteFile has incorrect encoding %s", wf.Encoding)
|
||||||
|
}
|
||||||
|
if wf.Permissions != "0644" {
|
||||||
|
t.Errorf("WriteFile has incorrect permissions %s", wf.Permissions)
|
||||||
|
}
|
||||||
|
if wf.Path != "/etc/dogepack.conf" {
|
||||||
|
t.Errorf("WriteFile has incorrect path %s", wf.Path)
|
||||||
|
}
|
||||||
|
if wf.Owner != "root:dogepack" {
|
||||||
|
t.Errorf("WriteFile has incorrect owner %s", wf.Owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that our interface conversion doesn't panic
|
// Assert that our interface conversion doesn't panic
|
||||||
|
46
cloudinit/write_file.go
Normal file
46
cloudinit/write_file.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package cloudinit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WriteFile struct {
|
||||||
|
Encoding string
|
||||||
|
Content string
|
||||||
|
Owner string
|
||||||
|
Path string
|
||||||
|
Permissions string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessWriteFile(base string, wf *WriteFile) error {
|
||||||
|
fullPath := path.Join(base, wf.Path)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(path.Dir(fullPath), os.FileMode(0744)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse string representation of file mode as octal
|
||||||
|
perm, err := strconv.ParseInt(wf.Permissions, 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Unable to parse file permissions as octal integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(fullPath, []byte(wf.Content), os.FileMode(perm)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if wf.Owner != "" {
|
||||||
|
// We shell out since we don't have a way to look up unix groups natively
|
||||||
|
cmd := exec.Command("chown", wf.Owner, fullPath)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
81
cloudinit/write_file_test.go
Normal file
81
cloudinit/write_file_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package cloudinit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteFileUnencodedContent(t *testing.T) {
|
||||||
|
wf := WriteFile{
|
||||||
|
Path: "/tmp/foo",
|
||||||
|
Content: "bar",
|
||||||
|
Permissions: "0644",
|
||||||
|
}
|
||||||
|
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 := ProcessWriteFile(dir, &wf); err != nil {
|
||||||
|
t.Fatalf("Processing of WriteFile failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := path.Join(dir, "tmp", "foo")
|
||||||
|
|
||||||
|
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) {
|
||||||
|
wf := WriteFile{
|
||||||
|
Path: "/tmp/foo",
|
||||||
|
Content: "bar",
|
||||||
|
Permissions: "pants",
|
||||||
|
}
|
||||||
|
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 := ProcessWriteFile(dir, &wf); err == nil {
|
||||||
|
t.Fatalf("Expected error to be raised when writing file with invalid permission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteFileEncodedContent(t *testing.T) {
|
||||||
|
wf := WriteFile{
|
||||||
|
Path: "/tmp/foo",
|
||||||
|
Content: "",
|
||||||
|
Encoding: "base64",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := ProcessWriteFile(dir, &wf); err == nil {
|
||||||
|
t.Fatalf("Expected error to be raised when writing file with encoding")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user