metadata: handle json with more than 2 levels

Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
This commit is contained in:
Guillaume Rose
2017-11-06 14:56:17 +01:00
parent 815d8da2ed
commit 06e86154b6
4 changed files with 315 additions and 79 deletions

View File

@@ -111,7 +111,7 @@ func main() {
}
if userdata != nil {
if err := processUserData(userdata); err != nil {
if err := processUserData(ConfigPath, userdata); err != nil {
log.Printf("Could not extract user data: %s", err)
}
}
@@ -139,70 +139,82 @@ func main() {
// }
// }
// Will create foobar/foo with mode 0644 and content "hello"
func processUserData(data []byte) error {
func processUserData(basePath string, data []byte) error {
// Always write the raw data to a file
err := ioutil.WriteFile(path.Join(ConfigPath, "userdata"), data, 0644)
err := ioutil.WriteFile(path.Join(basePath, "userdata"), data, 0644)
if err != nil {
log.Printf("Could not write userdata: %s", err)
return err
}
var fd interface{}
if err := json.Unmarshal(data, &fd); err != nil {
var root ConfigFile
if err := json.Unmarshal(data, &root); err != nil {
// Userdata is no JSON, presumably...
log.Printf("Could not unmarshall userdata: %s", err)
// This is not an error
return nil
}
cm, ok := fd.(map[string]interface{})
if !ok {
log.Printf("Could convert JSON to desired format: %s", fd)
return nil
for dir, entry := range root {
writeConfigFiles(path.Join(basePath, dir), entry)
}
for d, val := range cm {
dir := path.Join(ConfigPath, d)
if err := os.MkdirAll(dir, 0755); err != nil {
log.Printf("Failed to create %s: %s", dir, err)
continue
}
files, ok := val.(map[string]interface{})
if !ok {
log.Printf("Could convert JSON for files: %s", val)
continue
}
for f, i := range files {
p := uint64(0644)
var c string
switch fi := i.(type) {
case map[string]interface{}:
if _, ok := fi["perm"]; !ok {
log.Printf("No permission provided %s", f)
continue
}
if _, ok := fi["content"]; !ok {
log.Printf("No content provided %s", f)
continue
}
c = fi["content"].(string)
if p, err = strconv.ParseUint(fi["perm"].(string), 8, 32); err != nil {
log.Printf("Failed to parse permission %s: %s", fi, err)
continue
}
case string:
c = fi
default:
log.Printf("Couldn't convert JSON for items: %s", i)
continue
}
if err := ioutil.WriteFile(path.Join(dir, f), []byte(c), os.FileMode(p)); err != nil {
log.Printf("Failed to write %s/%s: %s", dir, f, err)
continue
}
}
}
return nil
}
func writeConfigFiles(target string, current Entry) {
if isFile(current) {
filemode, err := parseFileMode(current.Perm, 0644)
if err != nil {
log.Printf("Failed to parse permission %s: %s", current, err)
return
}
if err := ioutil.WriteFile(target, []byte(*current.Content), filemode); err != nil {
log.Printf("Failed to write %s: %s", target, err)
return
}
} else if isDirectory(current) {
filemode, err := parseFileMode(current.Perm, 0755)
if err != nil {
log.Printf("Failed to parse permission %s: %s", current, err)
return
}
if err := os.MkdirAll(target, filemode); err != nil {
log.Printf("Failed to create %s: %s", target, err)
return
}
for dir, entry := range current.Entries {
writeConfigFiles(path.Join(target, dir), entry)
}
} else {
log.Printf("%s is invalid", target)
}
}
func isFile(json Entry) bool {
return json.Content != nil && json.Entries == nil
}
func isDirectory(json Entry) bool {
return json.Content == nil && json.Entries != nil
}
func parseFileMode(input string, defaultMode os.FileMode) (os.FileMode, error) {
if input != "" {
perm, err := strconv.ParseUint(input, 8, 32)
if err != nil {
return 0, err
}
return os.FileMode(perm), nil
}
return defaultMode, nil
}
// ConfigFile represents the configuration file
type ConfigFile map[string]Entry
// Entry represents either a directory or a file
type Entry struct {
Perm string `json:"perm,omitempty"`
Content *string `json:"content,omitempty"`
Entries map[string]Entry `json:"entries,omitempty"`
}

217
pkg/metadata/main_test.go Normal file
View File

@@ -0,0 +1,217 @@
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"os"
"path"
"testing"
)
func TestSampleConfig(t *testing.T) {
basePath, err := ioutil.TempDir("", "metadata")
if err != nil {
t.Fatalf("can't make a temp rootdir %v", err)
}
defer os.RemoveAll(basePath)
process(t, basePath, `{
"ssh": {
"entries": {
"sshd_config": {
"perm": "0600",
"content": "PermitRootLogin yes\nPasswordAuthentication no"
}
}
},
"foo": {
"entries": {
"bar": {
"content": "foobar"
},
"baz": {
"perm": "0600",
"content": "bar"
}
}
}
}`)
sshd := path.Join(basePath, "ssh", "sshd_config")
assertContent(t, sshd, "PermitRootLogin yes\nPasswordAuthentication no")
assertPermission(t, sshd, 0600)
bar := path.Join(basePath, "foo", "bar")
assertContent(t, bar, "foobar")
assertPermission(t, bar, 0644)
assertContent(t, path.Join(basePath, "foo", "baz"), "bar")
}
func TestSerialization(t *testing.T) {
bin, err := json.Marshal(ConfigFile{
"ssh": Entry{
Entries: map[string]Entry{
"sshd_config": {
Content: str("PermitRootLogin yes\nPasswordAuthentication no"),
Perm: "0600",
},
},
},
"foo": Entry{
Entries: map[string]Entry{
"bar": {
Content: str("foobar"),
},
"baz": {
Content: str("bar"),
Perm: "0600",
},
},
},
})
if err != nil {
t.Fatalf("Cannot convert to json: %v", err)
}
expected := `{"foo":{"entries":{"bar":{"content":"foobar"},"baz":{"perm":"0600","content":"bar"}}},"ssh":{"entries":{"sshd_config":{"perm":"0600","content":"PermitRootLogin yes\nPasswordAuthentication no"}}}}`
if expected != string(bin) {
t.Fatalf("Expected %v but has %v", expected, string(bin))
}
}
func TestWriteSingleFile(t *testing.T) {
basePath, err := ioutil.TempDir(os.TempDir(), "metadata")
if err != nil {
t.Fatalf("can't make a temp rootdir %v", err)
}
defer os.RemoveAll(basePath)
process(t, basePath, `{
"hostname": {
"content": "foobar"
}
}`)
assertContent(t, path.Join(basePath, "hostname"), "foobar")
}
func TestWriteEmptyFile(t *testing.T) {
basePath, err := ioutil.TempDir(os.TempDir(), "metadata")
if err != nil {
t.Fatalf("can't make a temp rootdir %v", err)
}
defer os.RemoveAll(basePath)
process(t, basePath, `{
"empty": {
"content": ""
}
}`)
assertContent(t, path.Join(basePath, "empty"), "")
}
func TestWriteEmptyDirectory(t *testing.T) {
basePath, err := ioutil.TempDir(os.TempDir(), "metadata")
if err != nil {
t.Fatalf("can't make a temp rootdir %v", err)
}
defer os.RemoveAll(basePath)
process(t, basePath, `{
"empty": {
"entries": {}
}
}`)
if _, err := os.Stat(path.Join(basePath, "empty")); err != nil {
t.Fatalf("empty folder doesn't exist: %v", err)
}
}
func TestSetPermission(t *testing.T) {
basePath, err := ioutil.TempDir(os.TempDir(), "metadata")
if err != nil {
t.Fatalf("can't make a temp rootdir %v", err)
}
defer os.RemoveAll(basePath)
process(t, basePath, `{
"restricted": {
"perm": "0600",
"entries": {
"password": {
"perm": "0600",
"content": "secret"
}
}
}
}`)
assertPermission(t, path.Join(basePath, "restricted"), 0600|os.ModeDir)
assertPermission(t, path.Join(basePath, "restricted", "password"), 0600)
}
func TestDeepTree(t *testing.T) {
basePath, err := ioutil.TempDir("", "metadata")
if err != nil {
t.Fatalf("can't make a temp rootdir %v", err)
}
defer os.RemoveAll(basePath)
process(t, basePath, `{
"level1": {
"entries": {
"level2": {
"entries": {
"file2": {
"content": "depth2"
},
"level3": {
"entries": {
"file3": {
"content": "depth3"
}
}
}
}
}
}
}
}`)
assertContent(t, path.Join(basePath, "level1", "level2", "level3", "file3"), "depth3")
assertContent(t, path.Join(basePath, "level1", "level2", "file2"), "depth2")
}
func str(input string) *string {
return &input
}
func process(t *testing.T, basePath string, json string) {
if err := processUserData(basePath, []byte(json)); err != nil {
t.Fatalf("fail to process json %v", err)
}
}
func assertPermission(t *testing.T, path string, expected os.FileMode) {
fileinfo, err := os.Stat(path)
if err != nil {
t.Fatalf("%v doesn't exist: %v", path, err)
}
if fileinfo.Mode() != expected {
t.Fatalf("%v: expected %v but has %v", path, expected, fileinfo.Mode())
}
}
func assertContent(t *testing.T, path, expected string) {
file, err := ioutil.ReadFile(path)
if err != nil {
t.Fatalf("can't read %v: %v", path, err)
}
if !bytes.Equal(file, []byte(expected)) {
t.Fatalf("%v: expected %v but has %v", path, string(expected), string(file))
}
}