@@ -5,11 +5,15 @@ import (
5
5
"errors"
6
6
"fmt"
7
7
"io/fs"
8
+ "os"
9
+ "os/exec"
10
+ "path/filepath"
8
11
)
9
12
10
13
// AllFilesystemChecks returns a list of filesystem checks to be performed on the image filesystem.
11
14
func AllFilesystemChecks () []FilesystemCheck {
12
15
return []FilesystemCheck {
16
+ FilesystemHasGoExecutables ("bin/opm" ),
13
17
FilesystemHasDirectories (
14
18
"configs" ,
15
19
"tmp/cache" ,
@@ -21,7 +25,7 @@ func AllFilesystemChecks() []FilesystemCheck {
21
25
func FilesystemHasDirectories (paths ... string ) FilesystemCheck {
22
26
return FilesystemCheck {
23
27
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 {
25
29
var errs []error
26
30
for _ , path := range paths {
27
31
stat , err := fs .Stat (imageFS , path )
@@ -38,3 +42,80 @@ func FilesystemHasDirectories(paths ...string) FilesystemCheck {
38
42
},
39
43
}
40
44
}
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
+ }
0 commit comments