Skip to content

Commit fdf0c32

Browse files
author
Per Goncalves da Silva
committed
Refactor and add missing unit tests
Signed-off-by: Per Goncalves da Silva <pegoncal@redhat.com>
1 parent 9d59ac7 commit fdf0c32

File tree

5 files changed

+235
-53
lines changed

5 files changed

+235
-53
lines changed

internal/fsutil/helpers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88

99
// EnsureEmptyDirectory ensures the directory given by `path` is empty.
1010
// If the directory does not exist, it will be created with permission bits
11-
// given by `perm`.
11+
// given by `perm`. If the directory exists, it will not simply rm -rf && mkdir -p
12+
// as the calling process may not have permissions to delete the directory. E.g.
13+
// in the case of a pod mount. Rather, it will delete the contents of the directory.
1214
func EnsureEmptyDirectory(path string, perm fs.FileMode) error {
1315
entries, err := os.ReadDir(path)
1416
if err != nil && !os.IsNotExist(err) {

internal/fsutil/helpers_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package fsutil_test
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/operator-framework/operator-controller/internal/fsutil"
11+
)
12+
13+
func TestEnsureEmptyDirectory(t *testing.T) {
14+
tempDir := t.TempDir()
15+
dirPath := filepath.Join(tempDir, "testdir")
16+
dirPerms := os.FileMode(0755)
17+
18+
t.Log("Ensure directory is created with the correct perms if it does not already exist")
19+
require.NoError(t, fsutil.EnsureEmptyDirectory(dirPath, dirPerms))
20+
21+
stat, err := os.Stat(dirPath)
22+
require.NoError(t, err)
23+
require.True(t, stat.IsDir())
24+
require.Equal(t, dirPerms, stat.Mode().Perm())
25+
26+
t.Log("Create a file inside directory")
27+
file := filepath.Join(dirPath, "file1")
28+
require.NoError(t, os.WriteFile(file, []byte("test"), 0640))
29+
30+
t.Log("Create a sub-directory inside directory")
31+
subDir := filepath.Join(dirPath, "subdir")
32+
require.NoError(t, os.Mkdir(subDir, dirPerms))
33+
34+
t.Log("Call EnsureEmptyDirectory against directory with different permissions")
35+
require.NoError(t, fsutil.EnsureEmptyDirectory(dirPath, 0640))
36+
37+
t.Log("Ensure directory is now empty")
38+
entries, err := os.ReadDir(dirPath)
39+
require.NoError(t, err)
40+
require.Empty(t, entries)
41+
42+
t.Log("Ensure original directory permissions are unchanged")
43+
stat, err = os.Stat(dirPath)
44+
require.NoError(t, err)
45+
require.Equal(t, dirPerms, stat.Mode().Perm())
46+
}

internal/rukpak/source/containers_image_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func TestUnpackUnexpectedFile(t *testing.T) {
286286
require.True(t, stat.IsDir())
287287

288288
// Unset read-only to allow cleanup
289-
require.NoError(t, source.UnsetReadOnlyRecursive(unpackPath))
289+
require.NoError(t, source.SetWritableRecursive(unpackPath))
290290
}
291291

292292
func TestUnpackCopySucceedsMountFails(t *testing.T) {

internal/rukpak/source/helpers.go

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,26 @@ import (
88
"time"
99
)
1010

11-
// SetReadOnlyRecursive sets directory with path given by `root` as read-only
12-
func SetReadOnlyRecursive(root string) error {
13-
return filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
14-
if err != nil {
15-
return err
16-
}
17-
18-
fi, err := d.Info()
19-
if err != nil {
20-
return err
21-
}
11+
const (
12+
OwnerWritableFileMode os.FileMode = 0700
13+
OwnerWritableDirMode os.FileMode = 0700
14+
OwnerReadOnlyFileMode os.FileMode = 0400
15+
OwnerReadOnlyDirMode os.FileMode = 0500
16+
)
2217

23-
if err := func() error {
24-
switch typ := fi.Mode().Type(); typ {
25-
case os.ModeSymlink:
26-
// do not follow symlinks
27-
// 1. if they resolve to other locations in the root, we'll find them anyway
28-
// 2. if they resolve to other locations outside the root, we don't want to change their permissions
29-
return nil
30-
case os.ModeDir:
31-
return os.Chmod(path, 0500)
32-
case 0: // regular file
33-
return os.Chmod(path, 0400)
34-
default:
35-
return fmt.Errorf("refusing to change ownership of file %q with type %v", path, typ.String())
36-
}
37-
}(); err != nil {
38-
return err
39-
}
40-
return nil
41-
})
18+
// SetReadOnlyRecursive recursively sets files and directories under the path given by `root` as read-only
19+
func SetReadOnlyRecursive(root string) error {
20+
return setModeRecursive(root, OwnerReadOnlyFileMode, OwnerReadOnlyDirMode)
4221
}
4322

44-
// UnsetReadOnlyRecursive unsets directory with path given by `root` as read-only
45-
func UnsetReadOnlyRecursive(root string) error {
46-
return filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
47-
if os.IsNotExist(err) {
48-
return nil
49-
}
50-
if err != nil {
51-
return err
52-
}
53-
if !d.IsDir() {
54-
return nil
55-
}
56-
if err := os.Chmod(path, 0700); err != nil {
57-
return err
58-
}
59-
return nil
60-
})
23+
// SetWritableRecursive recursively sets files and directories under the path given by `root` as writable
24+
func SetWritableRecursive(root string) error {
25+
return setModeRecursive(root, OwnerWritableFileMode, OwnerWritableDirMode)
6126
}
6227

6328
// DeleteReadOnlyRecursive deletes read-only directory with path given by `root`
6429
func DeleteReadOnlyRecursive(root string) error {
65-
if err := UnsetReadOnlyRecursive(root); err != nil {
30+
if err := SetWritableRecursive(root); err != nil {
6631
return fmt.Errorf("error making directory writable for deletion: %w", err)
6732
}
6833
return os.RemoveAll(root)
@@ -73,14 +38,43 @@ func DeleteReadOnlyRecursive(root string) error {
7338
// If `unpackPath` is a file, it will be deleted and false will be returned without an error.
7439
func IsImageUnpacked(unpackPath string) (bool, time.Time, error) {
7540
unpackStat, err := os.Stat(unpackPath)
41+
if errors.Is(err, os.ErrNotExist) {
42+
return false, time.Time{}, nil
43+
}
7644
if err != nil {
77-
if errors.Is(err, os.ErrNotExist) {
78-
return false, time.Time{}, nil
79-
}
8045
return false, time.Time{}, err
8146
}
8247
if !unpackStat.IsDir() {
8348
return false, time.Time{}, os.Remove(unpackPath)
8449
}
8550
return true, unpackStat.ModTime(), nil
8651
}
52+
53+
func setModeRecursive(path string, fileMode os.FileMode, dirMode os.FileMode) error {
54+
return filepath.WalkDir(path, func(path string, d os.DirEntry, err error) error {
55+
if os.IsNotExist(err) {
56+
return nil
57+
}
58+
if err != nil {
59+
return err
60+
}
61+
fi, err := d.Info()
62+
if err != nil {
63+
return err
64+
}
65+
66+
switch typ := fi.Mode().Type(); typ {
67+
case os.ModeSymlink:
68+
// do not follow symlinks
69+
// 1. if they resolve to other locations in the root, we'll find them anyway
70+
// 2. if they resolve to other locations outside the root, we don't want to change their permissions
71+
return nil
72+
case os.ModeDir:
73+
return os.Chmod(path, dirMode)
74+
case 0: // regular file
75+
return os.Chmod(path, fileMode)
76+
default:
77+
return fmt.Errorf("refusing to change ownership of file %q with type %v", path, typ.String())
78+
}
79+
})
80+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package source_test
2+
3+
import (
4+
"io/fs"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/operator-framework/operator-controller/internal/rukpak/source"
12+
)
13+
14+
func TestSetReadOnlyRecursive(t *testing.T) {
15+
tempDir := t.TempDir()
16+
targetFilePath := filepath.Join(tempDir, "target")
17+
nestedDir := filepath.Join(tempDir, "nested")
18+
filePath := filepath.Join(nestedDir, "testfile")
19+
symlinkPath := filepath.Join(nestedDir, "symlink")
20+
21+
t.Log("Create symlink target file outside directory with its own permissions")
22+
require.NoError(t, os.WriteFile(targetFilePath, []byte("something"), 0644))
23+
24+
t.Log("Create a nested directory structure that contains a file and sym. link")
25+
require.NoError(t, os.Mkdir(nestedDir, source.OwnerWritableDirMode))
26+
require.NoError(t, os.WriteFile(filePath, []byte("test"), source.OwnerWritableFileMode))
27+
require.NoError(t, os.Symlink(targetFilePath, symlinkPath))
28+
29+
t.Log("Set directory structure as read-only")
30+
require.NoError(t, source.SetReadOnlyRecursive(nestedDir))
31+
32+
t.Log("Check file permissions")
33+
stat, err := os.Stat(filePath)
34+
require.NoError(t, err)
35+
require.Equal(t, source.OwnerReadOnlyFileMode, stat.Mode().Perm())
36+
37+
t.Log("Check directory permissions")
38+
nestedStat, err := os.Stat(nestedDir)
39+
require.NoError(t, err)
40+
require.Equal(t, source.OwnerReadOnlyDirMode, nestedStat.Mode().Perm())
41+
42+
t.Log("Check symlink target file permissions - should not be affected")
43+
stat, err = os.Stat(targetFilePath)
44+
require.NoError(t, err)
45+
require.Equal(t, fs.FileMode(0644), stat.Mode().Perm())
46+
47+
t.Log("Make directory writable to enable test clean-up")
48+
require.NoError(t, source.SetWritableRecursive(tempDir))
49+
}
50+
51+
func TestSetWritableRecursive(t *testing.T) {
52+
tempDir := t.TempDir()
53+
targetFilePath := filepath.Join(tempDir, "target")
54+
nestedDir := filepath.Join(tempDir, "nested")
55+
filePath := filepath.Join(nestedDir, "testfile")
56+
symlinkPath := filepath.Join(nestedDir, "symlink")
57+
58+
t.Log("Create symlink target file outside directory with its own permissions")
59+
require.NoError(t, os.WriteFile(targetFilePath, []byte("something"), 0644))
60+
61+
t.Log("Create a nested directory (writable) structure that contains a file (read-only) and sym. link")
62+
require.NoError(t, os.Mkdir(nestedDir, source.OwnerWritableDirMode))
63+
require.NoError(t, os.WriteFile(filePath, []byte("test"), source.OwnerReadOnlyFileMode))
64+
require.NoError(t, os.Symlink(targetFilePath, symlinkPath))
65+
66+
t.Log("Make directory read-only")
67+
require.NoError(t, os.Chmod(nestedDir, source.OwnerReadOnlyDirMode))
68+
69+
t.Log("Call SetWritableRecursive")
70+
require.NoError(t, source.SetWritableRecursive(nestedDir))
71+
72+
t.Log("Check file is writable")
73+
stat, err := os.Stat(filePath)
74+
require.NoError(t, err)
75+
require.Equal(t, source.OwnerWritableFileMode, stat.Mode().Perm())
76+
77+
t.Log("Check directory is writable")
78+
nestedStat, err := os.Stat(nestedDir)
79+
require.NoError(t, err)
80+
require.Equal(t, source.OwnerWritableDirMode, nestedStat.Mode().Perm())
81+
82+
t.Log("Check symlink target file permissions - should not be affected")
83+
stat, err = os.Stat(targetFilePath)
84+
require.NoError(t, err)
85+
require.Equal(t, fs.FileMode(0644), stat.Mode().Perm())
86+
}
87+
88+
func TestDeleteReadOnlyRecursive(t *testing.T) {
89+
tempDir := t.TempDir()
90+
nestedDir := filepath.Join(tempDir, "nested")
91+
filePath := filepath.Join(nestedDir, "testfile")
92+
93+
t.Log("Create a nested read-only directory structure that contains a file and sym. link")
94+
require.NoError(t, os.Mkdir(nestedDir, source.OwnerWritableDirMode))
95+
require.NoError(t, os.WriteFile(filePath, []byte("test"), source.OwnerReadOnlyFileMode))
96+
require.NoError(t, os.Chmod(nestedDir, source.OwnerReadOnlyDirMode))
97+
98+
t.Log("Set directory structure as read-only")
99+
require.NoError(t, source.DeleteReadOnlyRecursive(nestedDir))
100+
101+
t.Log("Ensure directory was deleted")
102+
_, err := os.Stat(nestedDir)
103+
require.ErrorIs(t, err, os.ErrNotExist)
104+
}
105+
106+
func TestIsImageUnpacked(t *testing.T) {
107+
tempDir := t.TempDir()
108+
unpackPath := filepath.Join(tempDir, "myimage")
109+
110+
t.Log("Test case: unpack path does not exist")
111+
unpacked, modTime, err := source.IsImageUnpacked(unpackPath)
112+
require.NoError(t, err)
113+
require.False(t, unpacked)
114+
require.True(t, modTime.IsZero())
115+
116+
t.Log("Test case: unpack path points to file")
117+
require.NoError(t, os.WriteFile(unpackPath, []byte("test"), source.OwnerWritableFileMode))
118+
119+
unpacked, modTime, err = source.IsImageUnpacked(filepath.Join(tempDir, "myimage"))
120+
require.NoError(t, err)
121+
require.False(t, unpacked)
122+
require.True(t, modTime.IsZero())
123+
124+
t.Log("Expect file to be deleted")
125+
_, err = os.Stat(unpackPath)
126+
require.ErrorIs(t, err, os.ErrNotExist)
127+
128+
t.Log("Test case: unpack path points to directory (happy path)")
129+
require.NoError(t, os.Mkdir(unpackPath, source.OwnerWritableDirMode))
130+
131+
unpacked, modTime, err = source.IsImageUnpacked(unpackPath)
132+
require.NoError(t, err)
133+
require.True(t, unpacked)
134+
require.False(t, modTime.IsZero())
135+
136+
t.Log("Expect unpack time to match directory mod time")
137+
stat, err := os.Stat(unpackPath)
138+
require.NoError(t, err)
139+
require.Equal(t, stat.ModTime(), modTime)
140+
}

0 commit comments

Comments
 (0)