Added support for the encoding key in write_files
Supported encodings are base64, gzip, and base64 encoded gzip
This commit is contained in:
parent
9c3cd9e69c
commit
112ba1e31f
@ -374,9 +374,11 @@ Each item in the list may have the following keys:
|
|||||||
- **content**: Data to write at the provided `path`
|
- **content**: Data to write at the provided `path`
|
||||||
- **permissions**: Integer representing file permissions, typically in octal notation (i.e. 0644)
|
- **permissions**: Integer representing file permissions, typically in octal notation (i.e. 0644)
|
||||||
- **owner**: User and group that should own the file written to disk. This is equivalent to the `<user>:<group>` argument to `chown <user>:<group> <path>`.
|
- **owner**: User and group that should own the file written to disk. This is equivalent to the `<user>:<group>` argument to `chown <user>:<group> <path>`.
|
||||||
|
- **encoding**: Optional. The encoding of the data in content. If not specified this defaults to the yaml document encoding (usually utf-8). Supported encoding types are:
|
||||||
|
- **b64, base64**: Base64 encoded content
|
||||||
|
- **gz, gzip**: gzip encoded content, for use with the !!binary tag
|
||||||
|
- **gz+b64, gz+base64, gzip+b64, gzip+base64**: Base64 encoded gzip content
|
||||||
|
|
||||||
Explicitly not implemented is the **encoding** attribute.
|
|
||||||
The **content** field must represent exactly what should be written to disk.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
@ -391,6 +393,24 @@ write_files:
|
|||||||
owner: root
|
owner: root
|
||||||
content: |
|
content: |
|
||||||
Good news, everyone!
|
Good news, everyone!
|
||||||
|
- path: /tmp/like_this
|
||||||
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
|
encoding: gzip
|
||||||
|
content: !!binary |
|
||||||
|
H4sIAKgdh1QAAwtITM5WyK1USMqvUCjPLMlQSMssS1VIya9KzVPIySwszS9SyCpNLwYARQFQ5CcAAAA=
|
||||||
|
- path: /tmp/or_like_this
|
||||||
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
|
encoding: gzip+base64
|
||||||
|
content: |
|
||||||
|
H4sIAKgdh1QAAwtITM5WyK1USMqvUCjPLMlQSMssS1VIya9KzVPIySwszS9SyCpNLwYARQFQ5CcAAAA=
|
||||||
|
- path: /tmp/todolist
|
||||||
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
|
encoding: base64
|
||||||
|
content: |
|
||||||
|
UGFjayBteSBib3ggd2l0aCBmaXZlIGRvemVuIGxpcXVvciBqdWdz
|
||||||
```
|
```
|
||||||
|
|
||||||
### manage_etc_hosts
|
### manage_etc_hosts
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Encoding string `yaml:"-"`
|
Encoding string `yaml:"encoding" valid:"base64,b64,gz,gzip,gz+base64,gzip+base64,gz+b64,gzip+b64"`
|
||||||
Content string `yaml:"content"`
|
Content string `yaml:"content"`
|
||||||
Owner string `yaml:"owner"`
|
Owner string `yaml:"owner"`
|
||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -47,13 +50,64 @@ func (f *File) Permissions() (os.FileMode, error) {
|
|||||||
return os.FileMode(perm), nil
|
return os.FileMode(perm), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DecodeBase64Content(content string) ([]byte, error) {
|
||||||
|
output, err := base64.StdEncoding.DecodeString(content)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to decode base64: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeGzipContent(content string) ([]byte, error) {
|
||||||
|
gzr, err := gzip.NewReader(bytes.NewReader([]byte(content)))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to decode gzip: %v", err)
|
||||||
|
}
|
||||||
|
defer gzr.Close()
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(gzr)
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeContent(content string, encoding string) ([]byte, error) {
|
||||||
|
switch encoding {
|
||||||
|
case "":
|
||||||
|
return []byte(content), nil
|
||||||
|
|
||||||
|
case "b64", "base64":
|
||||||
|
return DecodeBase64Content(content)
|
||||||
|
|
||||||
|
case "gz", "gzip":
|
||||||
|
return DecodeGzipContent(content)
|
||||||
|
|
||||||
|
case "gz+base64", "gzip+base64", "gz+b64", "gzip+b64":
|
||||||
|
gz, err := DecodeBase64Content(content)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeGzipContent(string(gz))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Unsupported encoding %s", encoding)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func WriteFile(f *File, root string) (string, error) {
|
func WriteFile(f *File, root string) (string, error) {
|
||||||
fullpath := path.Join(root, f.Path)
|
fullpath := path.Join(root, f.Path)
|
||||||
dir := path.Dir(fullpath)
|
dir := path.Dir(fullpath)
|
||||||
log.Printf("Writing file to %q", fullpath)
|
log.Printf("Writing file to %q", fullpath)
|
||||||
|
|
||||||
if f.Encoding != "" {
|
content, err := DecodeContent(f.Content, f.Encoding)
|
||||||
return "", fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to decode %s (%v)", f.Path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := EnsureDirectoryExists(dir); err != nil {
|
if err := EnsureDirectoryExists(dir); err != nil {
|
||||||
@ -71,7 +125,7 @@ func WriteFile(f *File, root string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(tmp.Name(), []byte(f.Content), perm); err != nil {
|
if err := ioutil.WriteFile(tmp.Name(), content, perm); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,10 +156,97 @@ func TestWriteFileEncodedContent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
//all of these decode to "bar"
|
||||||
|
content_tests := map[string]string{
|
||||||
|
"base64": "YmFy",
|
||||||
|
"b64": "YmFy",
|
||||||
|
"gz": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
|
||||||
|
"gzip": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
|
||||||
|
"gz+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||||
|
"gzip+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||||
|
"gz+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||||
|
"gzip+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||||
|
}
|
||||||
|
|
||||||
|
for encoding, content := range content_tests {
|
||||||
|
fullPath := path.Join(dir, encoding)
|
||||||
|
|
||||||
|
wf := File{config.File{
|
||||||
|
Path: encoding,
|
||||||
|
Encoding: encoding,
|
||||||
|
Content: content,
|
||||||
|
RawFilePermissions: "0644",
|
||||||
|
}}
|
||||||
|
|
||||||
|
path, err := WriteFile(&wf, dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Processing of WriteFile failed: %v", err)
|
||||||
|
} else if path != fullPath {
|
||||||
|
t.Fatalf("WriteFile returned bad path: want %s, got %s", fullPath, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
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: '%s'", contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteFileInvalidEncodedContent(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
content_encodings := []string{
|
||||||
|
"base64",
|
||||||
|
"b64",
|
||||||
|
"gz",
|
||||||
|
"gzip",
|
||||||
|
"gz+base64",
|
||||||
|
"gzip+base64",
|
||||||
|
"gz+b64",
|
||||||
|
"gzip+b64",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, encoding := range content_encodings {
|
||||||
wf := File{config.File{
|
wf := File{config.File{
|
||||||
Path: path.Join(dir, "tmp", "foo"),
|
Path: path.Join(dir, "tmp", "foo"),
|
||||||
Content: "",
|
Content: "@&*#%invalid data*@&^#*&",
|
||||||
Encoding: "base64",
|
Encoding: encoding,
|
||||||
|
}}
|
||||||
|
|
||||||
|
if _, err := WriteFile(&wf, dir); err == nil {
|
||||||
|
t.Fatalf("Expected error to be raised when writing file with encoding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteFileUnknownEncodedContent(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
wf := File{config.File{
|
||||||
|
Path: path.Join(dir, "tmp", "foo"),
|
||||||
|
Content: "",
|
||||||
|
Encoding: "no-such-encoding",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if _, err := WriteFile(&wf, dir); err == nil {
|
if _, err := WriteFile(&wf, dir); err == nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user