Merge pull request #5 from bcwaldon/write_files
Implement write_files feature
This commit is contained in:
		| @@ -10,7 +10,11 @@ const DefaultSSHKeyName = "coreos-cloudinit" | ||||
|  | ||||
| type CloudConfig struct { | ||||
| 	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) { | ||||
| @@ -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 != "" { | ||||
| 		err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL) | ||||
| 		if err == nil { | ||||
|   | ||||
| @@ -23,6 +23,10 @@ func TestCloudConfigEmpty(t *testing.T) { | ||||
| 	if cfg.Coreos.Fleet.Autostart { | ||||
| 		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" | ||||
| @@ -36,6 +40,13 @@ coreos: | ||||
| ssh_authorized_keys: | ||||
|   - foobar | ||||
|   - foobaz | ||||
| write_files: | ||||
|   - content: | | ||||
|       penny | ||||
|       elroy | ||||
|     path: /etc/dogepack.conf | ||||
|     permissions: '0644' | ||||
|     owner: root:dogepack | ||||
| `) | ||||
| 	cfg, err := NewCloudConfig(contents) | ||||
| 	if err != nil { | ||||
| @@ -58,6 +69,27 @@ ssh_authorized_keys: | ||||
| 	if !cfg.Coreos.Fleet.Autostart { | ||||
| 		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 | ||||
|   | ||||
							
								
								
									
										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") | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user