Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions openshift/default-catalog-consistency/pkg/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Checks struct {
type ImageCheckFunc func(ctx context.Context, root specsgov1.Descriptor, target oras.ReadOnlyTarget) error

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

// CatalogCheckFunc define functions to perform tests using the catalog (i.e. check if the package's metadata in the catalog is valid)
type CatalogCheckFunc func(ctx context.Context, cfg declcfg.DeclarativeConfig) error
Expand Down Expand Up @@ -100,7 +100,7 @@ func Check(ctx context.Context, res *extract.ExtractedImage, checks Checks) erro
}
imageFS = os.DirFS(unpackPath)
for _, check := range checks.FilesystemChecks {
if err := check.Fn(ctx, imageFS); err != nil {
if err := check.Fn(ctx, imageFS, res.TmpDir); err != nil {
checkErrors = append(checkErrors, Error{
CheckName: fmt.Sprintf("[FS]:%s", check.Name),
Err: err,
Expand Down
83 changes: 82 additions & 1 deletion openshift/default-catalog-consistency/pkg/check/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import (
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
)

// AllFilesystemChecks returns a list of filesystem checks to be performed on the image filesystem.
func AllFilesystemChecks() []FilesystemCheck {
return []FilesystemCheck{
FilesystemHasGoExecutables("bin/opm"),
FilesystemHasDirectories(
"configs",
"tmp/cache",
Expand All @@ -21,7 +25,7 @@ func AllFilesystemChecks() []FilesystemCheck {
func FilesystemHasDirectories(paths ...string) FilesystemCheck {
return FilesystemCheck{
Name: "FilesystemHasDirectories" + fmt.Sprintf(":(%q) ", paths),
Fn: func(ctx context.Context, imageFS fs.FS) error {
Fn: func(ctx context.Context, imageFS fs.FS, _ string) error {
var errs []error
for _, path := range paths {
stat, err := fs.Stat(imageFS, path)
Expand All @@ -38,3 +42,80 @@ func FilesystemHasDirectories(paths ...string) FilesystemCheck {
},
}
}

// FilesystemHasGoExecutables checks that the given paths exist inside the extracted image filesystem.
// It supports regular files and symlinks:
// - If it's a regular file, we just check that it exists.
// - If it's a symlink, we check that the link target exists.
//
// This is useful for verifying container images where binaries may be symlinks,
// In our image builds (see:
// https://github.com/openshift/operator-framework-olm/blob/082d59a819afc43b24e9ca23c531bdfc35418722/operator-registry.Dockerfile#L16-L19),
// the actual binaries are in /bin/registry/*, and /bin/* just has symlinks that point to them.
func FilesystemHasGoExecutables(paths ...string) FilesystemCheck {
return FilesystemCheck{
Name: "FilesystemHasGoExecutables" + fmt.Sprintf(":(%q)", paths),

// We use the `os` package instead of fs.FS because fs.FS does not support Lstat or Readlink,
// which are needed to detect and resolve symlinks correctly.
// Therefore, we are looking to real file paths under the unpacked image (in tmpDir/fs).
Fn: func(_ context.Context, _ fs.FS, tmpDir string) error {
var errs []error
root := filepath.Join(tmpDir, "fs")

for _, rel := range paths {
fullPath := filepath.Join(root, rel)

binaryPath, err := resolveImageSymlink(root, fullPath)
if err != nil {
errs = append(errs, fmt.Errorf("path %q: %w", rel, err))
continue
}

out, err := exec.Command("go", "version", "-m", binaryPath).CombinedOutput()
if err != nil {
errs = append(errs, fmt.Errorf("not a Go binary or unreadable metadata for %q: %v\n%s", rel, err, out))
continue
}
}

return errors.Join(errs...)
},
}
}

// resolveImageSymlink resolve the symlink target.
// Example 1: if the target is absolute like "/bin/registry/opm",
// that means it should exist under the image root at tmpDir/fs/bin/registry/opm.
// Example 2: if the target is relative like "registry/opm" or "../registry/opm",
// resolve it relative to the symlink’s own directory.
func resolveImageSymlink(root, fullPath string) (string, error) {
fi, err := os.Lstat(fullPath)
if err != nil {
return "", fmt.Errorf("error stating file: %w", err)
}

if fi.Mode()&os.ModeSymlink == 0 {
return fullPath, nil
}

target, err := os.Readlink(fullPath)
if err != nil {
return "", fmt.Errorf("unreadable symlink: %w", err)
}

var resolved string
if filepath.IsAbs(target) {
// remove leading "/" and resolve from root
resolved = filepath.Join(root, target[1:])
} else {
// If it's not a symlink, we just check that the file exists.
resolved = filepath.Join(filepath.Dir(fullPath), target)
}

if _, err := os.Stat(resolved); err != nil {
return "", fmt.Errorf("symlink points to missing target %q: %w", resolved, err)
}

return resolved, nil
}
4 changes: 2 additions & 2 deletions openshift/default-catalog-consistency/pkg/extract/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,9 @@ func extractLayers(ctx context.Context, layoutPath, fsPath, tag string) error {
hdr.Uid = os.Getuid()
hdr.Gid = os.Getgid()
if hdr.FileInfo().IsDir() {
hdr.Mode = 0700
hdr.Mode = 0755
} else {
hdr.Mode = 0600
hdr.Mode = 0755
}
return true, nil
}))
Expand Down