Skip to content

Commit f38ecdb

Browse files
committed
feat(provisionersdk): follow symlinks while archiving
1 parent 67952cf commit f38ecdb

File tree

2 files changed

+106
-40
lines changed

2 files changed

+106
-40
lines changed

provisionersdk/archive.go

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"archive/tar"
55
"bytes"
66
"io"
7+
"io/fs"
78
"os"
89
"path/filepath"
910
"strings"
@@ -31,43 +32,37 @@ func dirHasExt(dir string, ext string) (bool, error) {
3132
return false, nil
3233
}
3334

34-
// Tar archives a Terraform directory.
35-
func Tar(directory string, limit int64) ([]byte, error) {
36-
var buffer bytes.Buffer
37-
tarWriter := tar.NewWriter(&buffer)
38-
totalSize := int64(0)
3935

40-
const tfExt = ".tf"
41-
hasTf, err := dirHasExt(directory, tfExt)
42-
if err != nil {
43-
return nil, err
44-
}
45-
if !hasTf {
46-
absPath, err := filepath.Abs(directory)
47-
if err != nil {
48-
return nil, err
49-
}
36+
type archiver struct {
5037

51-
// Show absolute path to aid in debugging. E.g. showing "." is
52-
// useless.
53-
return nil, xerrors.Errorf(
54-
"%s is not a valid template since it has no %s files",
55-
absPath, tfExt,
56-
)
57-
}
38+
}
5839

59-
err = filepath.Walk(directory, func(file string, fileInfo os.FileInfo, err error) error {
40+
func (a *archiver) walkFn(path string, fileInfo os.FileInfo, err error) error {
6041
if err != nil {
6142
return err
6243
}
63-
var link string
6444
if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
65-
link, err = os.Readlink(file)
45+
// Per https://github.com/coder/coder/issues/5677, we want to
46+
// follow symlinks.
47+
var linkDest string
48+
linkDest, err = os.Readlink(file)
6649
if err != nil {
6750
return err
6851
}
52+
53+
destInfo, err := os.Stat(linkDest)
54+
if err != nil {
55+
return err
56+
}
57+
if destInfo.IsDir() {
58+
return filepath.Walk(linkDest, func(path string, info fs.FileInfo, err error) error {
59+
walkFn(path, info, err)
60+
})
61+
}
62+
return nil
6963
}
70-
header, err := tar.FileInfoHeader(fileInfo, link)
64+
65+
header, err := tar.FileInfoHeader(fileInfo, "")
7166
if err != nil {
7267
return err
7368
}
@@ -76,11 +71,10 @@ func Tar(directory string, limit int64) ([]byte, error) {
7671
return err
7772
}
7873
if strings.HasPrefix(rel, ".") || strings.HasPrefix(filepath.Base(rel), ".") {
74+
// Don't archive hidden files!
7975
if fileInfo.IsDir() && rel != "." {
80-
// Don't archive hidden files!
8176
return filepath.SkipDir
8277
}
83-
// Don't archive hidden files!
8478
return nil
8579
}
8680
if strings.Contains(rel, ".tfstate") {
@@ -109,7 +103,35 @@ func Tar(directory string, limit int64) ([]byte, error) {
109103
return xerrors.Errorf("Archive too big. Must be <= %d bytes", limit)
110104
}
111105
return data.Close()
112-
})
106+
}
107+
}
108+
109+
// Tar archives a Terraform directory.
110+
func Tar(directory string, limit int64) ([]byte, error) {
111+
var buffer bytes.Buffer
112+
tarWriter := tar.NewWriter(&buffer)
113+
totalSize := int64(0)
114+
115+
const tfExt = ".tf"
116+
hasTf, err := dirHasExt(directory, tfExt)
117+
if err != nil {
118+
return nil, err
119+
}
120+
if !hasTf {
121+
absPath, err := filepath.Abs(directory)
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
// Show absolute path to aid in debugging. E.g. showing "." is
127+
// useless.
128+
return nil, xerrors.Errorf(
129+
"%s is not a valid template since it has no %s files",
130+
absPath, tfExt,
131+
)
132+
}
133+
134+
err = filepath.Walk(directory, tarWalkFn())
113135
if err != nil {
114136
return nil, err
115137
}

provisionersdk/archive_test.go

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
func TestTar(t *testing.T) {
1414
t.Parallel()
15+
1516
t.Run("NoTF", func(t *testing.T) {
1617
t.Parallel()
1718
dir := t.TempDir()
@@ -90,15 +91,58 @@ func TestTar(t *testing.T) {
9091

9192
func TestUntar(t *testing.T) {
9293
t.Parallel()
93-
dir := t.TempDir()
94-
file, err := os.CreateTemp(dir, "*.tf")
95-
require.NoError(t, err)
96-
_ = file.Close()
97-
archive, err := provisionersdk.Tar(dir, 1024)
98-
require.NoError(t, err)
99-
dir = t.TempDir()
100-
err = provisionersdk.Untar(dir, archive)
101-
require.NoError(t, err)
102-
_, err = os.Stat(filepath.Join(dir, filepath.Base(file.Name())))
103-
require.NoError(t, err)
94+
t.Run("Normal", func(t *testing.T) {
95+
t.Parallel()
96+
97+
dir := t.TempDir()
98+
file, err := os.CreateTemp(dir, "*.tf")
99+
require.NoError(t, err)
100+
_ = file.Close()
101+
archive, err := provisionersdk.Tar(dir, 1024)
102+
require.NoError(t, err)
103+
dir = t.TempDir()
104+
err = provisionersdk.Untar(dir, archive)
105+
require.NoError(t, err)
106+
_, err = os.Stat(filepath.Join(dir, filepath.Base(file.Name())))
107+
require.NoError(t, err)
108+
})
109+
t.Run("FollowSymlinks", func(t *testing.T) {
110+
t.Parallel()
111+
112+
externalDir := t.TempDir()
113+
externalFile, err := os.CreateTemp(externalDir, "")
114+
require.NoError(t, err)
115+
const externalFileContents = "dogdogdog"
116+
externalFile.WriteString(externalFileContents)
117+
externalFile.Close()
118+
119+
dir := t.TempDir()
120+
121+
file, err := os.CreateTemp(dir, "*.tf")
122+
require.NoError(t, err)
123+
_ = file.Close()
124+
125+
err = os.Symlink(
126+
externalDir,
127+
filepath.Join(dir, "link"),
128+
)
129+
require.NoError(t, err)
130+
131+
checkDir := func(dir string) {
132+
gotContents, err := os.ReadFile(filepath.Join(dir, "link", filepath.Base(externalFile.Name())))
133+
require.NoError(t, err)
134+
require.EqualValues(t, externalFileContents, gotContents)
135+
}
136+
137+
checkDir(dir)
138+
139+
archive, err := provisionersdk.Tar(dir, 1024)
140+
require.NoError(t, err)
141+
142+
extractDir := t.TempDir()
143+
err = provisionersdk.Untar(extractDir, archive)
144+
require.NoError(t, err)
145+
146+
checkDir(extractDir)
147+
})
104148
}

0 commit comments

Comments
 (0)