Skip to content

Commit 38a4d89

Browse files
camilamacedo86ci-robot
authored andcommitted
UPSTREAM: <carry>: [Default Catalog Consistency Test] (feat) add check for executable files in filesystem
Checks if given paths exist and point to executable files or valid symlinks.
1 parent db64fc6 commit 38a4d89

File tree

3 files changed

+86
-5
lines changed

3 files changed

+86
-5
lines changed

openshift/default-catalog-consistency/pkg/check/check.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type Checks struct {
2727
type ImageCheckFunc func(ctx context.Context, root specsgov1.Descriptor, target oras.ReadOnlyTarget) error
2828

2929
// FilesystemCheckFunc define functions to perform tests using the filesystem (i.e. check if files exists in directories)
30-
type FilesystemCheckFunc func(ctx context.Context, imageFS fs.FS) error
30+
type FilesystemCheckFunc func(ctx context.Context, imageFS fs.FS, tmpDir string) error
3131

3232
// CatalogCheckFunc define functions to perform tests using the catalog (i.e. check if the package's metadata in the catalog is valid)
3333
type CatalogCheckFunc func(ctx context.Context, cfg declcfg.DeclarativeConfig) error
@@ -100,7 +100,7 @@ func Check(ctx context.Context, res *extract.ExtractedImage, checks Checks) erro
100100
}
101101
imageFS = os.DirFS(unpackPath)
102102
for _, check := range checks.FilesystemChecks {
103-
if err := check.Fn(ctx, imageFS); err != nil {
103+
if err := check.Fn(ctx, imageFS, res.TmpDir); err != nil {
104104
checkErrors = append(checkErrors, Error{
105105
CheckName: fmt.Sprintf("[FS]:%s", check.Name),
106106
Err: err,

openshift/default-catalog-consistency/pkg/check/filesystem.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ import (
55
"errors"
66
"fmt"
77
"io/fs"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
811
)
912

1013
// AllFilesystemChecks returns a list of filesystem checks to be performed on the image filesystem.
1114
func AllFilesystemChecks() []FilesystemCheck {
1215
return []FilesystemCheck{
16+
FilesystemHasGoExecutables("bin/opm"),
1317
FilesystemHasDirectories(
1418
"configs",
1519
"tmp/cache",
@@ -21,7 +25,7 @@ func AllFilesystemChecks() []FilesystemCheck {
2125
func FilesystemHasDirectories(paths ...string) FilesystemCheck {
2226
return FilesystemCheck{
2327
Name: "FilesystemHasDirectories" + fmt.Sprintf(":(%q) ", paths),
24-
Fn: func(ctx context.Context, imageFS fs.FS) error {
28+
Fn: func(ctx context.Context, imageFS fs.FS, _ string) error {
2529
var errs []error
2630
for _, path := range paths {
2731
stat, err := fs.Stat(imageFS, path)
@@ -38,3 +42,80 @@ func FilesystemHasDirectories(paths ...string) FilesystemCheck {
3842
},
3943
}
4044
}
45+
46+
// FilesystemHasGoExecutables checks that the given paths exist inside the extracted image filesystem.
47+
// It supports regular files and symlinks:
48+
// - If it's a regular file, we just check that it exists.
49+
// - If it's a symlink, we check that the link target exists.
50+
//
51+
// This is useful for verifying container images where binaries may be symlinks,
52+
// In our image builds (see:
53+
// https://github.com/openshift/operator-framework-olm/blob/082d59a819afc43b24e9ca23c531bdfc35418722/operator-registry.Dockerfile#L16-L19),
54+
// the actual binaries are in /bin/registry/*, and /bin/* just has symlinks that point to them.
55+
func FilesystemHasGoExecutables(paths ...string) FilesystemCheck {
56+
return FilesystemCheck{
57+
Name: "FilesystemHasGoExecutables" + fmt.Sprintf(":(%q)", paths),
58+
59+
// We use the `os` package instead of fs.FS because fs.FS does not support Lstat or Readlink,
60+
// which are needed to detect and resolve symlinks correctly.
61+
// Therefore, we are looking to real file paths under the unpacked image (in tmpDir/fs).
62+
Fn: func(_ context.Context, _ fs.FS, tmpDir string) error {
63+
var errs []error
64+
root := filepath.Join(tmpDir, "fs")
65+
66+
for _, rel := range paths {
67+
fullPath := filepath.Join(root, rel)
68+
69+
binaryPath, err := resolveImageSymlink(root, fullPath)
70+
if err != nil {
71+
errs = append(errs, fmt.Errorf("path %q: %w", rel, err))
72+
continue
73+
}
74+
75+
out, err := exec.Command("go", "version", "-m", binaryPath).CombinedOutput()
76+
if err != nil {
77+
errs = append(errs, fmt.Errorf("not a Go binary or unreadable metadata for %q: %v\n%s", rel, err, out))
78+
continue
79+
}
80+
}
81+
82+
return errors.Join(errs...)
83+
},
84+
}
85+
}
86+
87+
// resolveImageSymlink resolve the symlink target.
88+
// Example 1: if the target is absolute like "/bin/registry/opm",
89+
// that means it should exist under the image root at tmpDir/fs/bin/registry/opm.
90+
// Example 2: if the target is relative like "registry/opm" or "../registry/opm",
91+
// resolve it relative to the symlink’s own directory.
92+
func resolveImageSymlink(root, fullPath string) (string, error) {
93+
fi, err := os.Lstat(fullPath)
94+
if err != nil {
95+
return "", fmt.Errorf("error stating file: %w", err)
96+
}
97+
98+
if fi.Mode()&os.ModeSymlink == 0 {
99+
return fullPath, nil
100+
}
101+
102+
target, err := os.Readlink(fullPath)
103+
if err != nil {
104+
return "", fmt.Errorf("unreadable symlink: %w", err)
105+
}
106+
107+
var resolved string
108+
if filepath.IsAbs(target) {
109+
// remove leading "/" and resolve from root
110+
resolved = filepath.Join(root, target[1:])
111+
} else {
112+
// If it's not a symlink, we just check that the file exists.
113+
resolved = filepath.Join(filepath.Dir(fullPath), target)
114+
}
115+
116+
if _, err := os.Stat(resolved); err != nil {
117+
return "", fmt.Errorf("symlink points to missing target %q: %w", resolved, err)
118+
}
119+
120+
return resolved, nil
121+
}

openshift/default-catalog-consistency/pkg/extract/extract.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ func extractLayers(ctx context.Context, layoutPath, fsPath, tag string) error {
179179
hdr.Uid = os.Getuid()
180180
hdr.Gid = os.Getgid()
181181
if hdr.FileInfo().IsDir() {
182-
hdr.Mode = 0700
182+
hdr.Mode = 0755
183183
} else {
184-
hdr.Mode = 0600
184+
hdr.Mode = 0755
185185
}
186186
return true, nil
187187
}))

0 commit comments

Comments
 (0)