diff --git a/apply/apply.go b/apply/apply.go index 0eb524a69..64b740164 100644 --- a/apply/apply.go +++ b/apply/apply.go @@ -273,7 +273,14 @@ func fmtHcl(fs afero.Fs, path string) error { } func touchFile(dest afero.Fs, path string) error { - _, err := dest.Stat(path) + dir, _ := filepath.Split(path) + ospath := filepath.FromSlash(dir) + err := dest.MkdirAll(ospath, 0755) + if err != nil { + return errs.WrapUserf(err, "couldn't create %s directory", dir) + } + + _, err = dest.Stat(path) if err != nil { // TODO we might not want to do this for all errors log.Infof("%s touched", path) _, err = dest.Create(path) @@ -305,6 +312,13 @@ func removeExtension(path string) string { } func applyTemplate(sourceFile io.Reader, dest afero.Fs, path string, overrides interface{}) error { + dir, _ := filepath.Split(path) + ospath := filepath.FromSlash(dir) + err := dest.MkdirAll(ospath, 0755) + if err != nil { + return errs.WrapUserf(err, "couldn't create %s directory", dir) + } + log.Infof("%s templated", path) writer, err := dest.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { diff --git a/apply/apply_test.go b/apply/apply_test.go index 6ee33043a..4484cea3a 100644 --- a/apply/apply_test.go +++ b/apply/apply_test.go @@ -5,6 +5,8 @@ import ( "os" "strings" "testing" + "math/rand" + "path/filepath" "github.com/chanzuckerberg/fogg/config" "github.com/chanzuckerberg/fogg/templates" @@ -20,6 +22,26 @@ func init() { } log.SetFormatter(formatter) } + +func randomString(n int) string { + var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + + b := make([]rune, n) + for i := range b { + b[i] = letter[rand.Intn(len(letter))] + } + return string(b) +} + +func getNonExistentDirectoryName() string { + nonexistentDir := "noexist-" + randomString(20) + for { + _, err := os.Stat(nonexistentDir) + if os.IsNotExist(err) { return nonexistentDir; } + nonexistentDir = "noexist-" + randomString(20) + } +} + func TestRemoveExtension(t *testing.T) { x := removeExtension("foo") assert.Equal(t, "foo", x) @@ -49,6 +71,24 @@ func TestApplyTemplateBasic(t *testing.T) { assert.Equal(t, "foo", string(r)) } +func TestApplyTemplateBasicNewDirectory(t *testing.T) { + sourceFile := strings.NewReader("foo") + // Potential errors do not show up if using NewMemMapFs; needs real OS fs. + dest := afero.NewOsFs() + nonexistentDir := getNonExistentDirectoryName() + defer dest.RemoveAll(nonexistentDir) + path := filepath.Join(nonexistentDir, "bar") + overrides := struct{ Foo string }{"foo"} + + e := applyTemplate(sourceFile, dest, path, overrides) + assert.Nil(t, e) + f, e := dest.Open(path) + assert.Nil(t, e) + r, e := ioutil.ReadAll(f) + assert.Nil(t, e) + assert.Equal(t, "foo", string(r)) +} + func TestApplyTemplate(t *testing.T) { sourceFile := strings.NewReader("Hello {{.Name}}") dest := afero.NewMemMapFs() @@ -89,6 +129,19 @@ func TestTouchFile(t *testing.T) { } +func TestTouchFileNonExistentDirectory(t *testing.T) { + // Potential errors do not show up if using NewMemMapFs; needs real OS fs. + dest := afero.NewOsFs() + nonexistentDir := getNonExistentDirectoryName() + defer dest.RemoveAll(nonexistentDir) + e := touchFile(dest, filepath.Join(nonexistentDir, "foo")) + assert.Nil(t, e) + r, e := readFile(dest, filepath.Join(nonexistentDir, "foo")) + assert.Nil(t, e) + assert.Equal(t, "", r) + assert.Nil(t, e) +} + func TestCreateFile(t *testing.T) { fs := afero.NewMemMapFs() @@ -118,6 +171,19 @@ func TestCreateFile(t *testing.T) { r, e = readFile(fs, "foo") assert.Nil(t, e) assert.Equal(t, "bar", r) +} + +func TestCreateFileNonExistentDirectory(t *testing.T) { + + // create new file in nonexistent directory + dest := afero.NewOsFs() + + e := createFile(dest, "newdir/foo", strings.NewReader("bar")) + assert.Nil(t, e) + + r, e := readFile(dest, "newdir/foo") + assert.Nil(t, e) + assert.Equal(t, "bar", r) }