Added support for the encoding key in write_files

Supported encodings are base64, gzip, and base64 encoded gzip
This commit is contained in:
cnelson 2014-12-09 08:10:23 -08:00
parent 9c3cd9e69c
commit 112ba1e31f
4 changed files with 168 additions and 7 deletions

View File

@ -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

View File

@ -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"`

View File

@ -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
} }

View File

@ -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 {