mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
--------- Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
154 lines
5.2 KiB
Go
154 lines
5.2 KiB
Go
package fileresolver
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/anchore/syft/syft/file"
|
|
)
|
|
|
|
var _ file.Resolver = (*ContainerImageModel)(nil)
|
|
var _ file.OCIMediaTypeResolver = (*ContainerImageModel)(nil)
|
|
|
|
// LayerInfo holds information about an OCI model layer file stored on disk.
|
|
type LayerInfo struct {
|
|
TempPath string // Path to the temp file on disk
|
|
MediaType string // OCI media type of the layer
|
|
}
|
|
|
|
// ContainerImageModel is a file.Resolver implementation that provides access to
|
|
// model header and metadata data (GGUF and SafeTensors headers, the model config
|
|
// blob, and companion layers) fetched from OCI model artifacts via range-GET
|
|
// requests. This does not fetch the entire model from the registry, only a
|
|
// sliver of it.
|
|
type ContainerImageModel struct {
|
|
tempDir string // temp directory containing all layer files
|
|
layerFiles map[string]LayerInfo // digest -> layer info (temp path + media type)
|
|
locations map[string]file.Location // digest -> location
|
|
ref string // image reference the artifact was fetched with
|
|
}
|
|
|
|
// NewContainerImageModel creates a new resolver with the given temp directory
|
|
// and layer files. The ref is surfaced through the ImageReference method so
|
|
// catalogers can derive context-level naming hints from the artifact reference
|
|
// when the layer contents don't carry one.
|
|
func NewContainerImageModel(tempDir string, layerFiles map[string]LayerInfo, ref string) *ContainerImageModel {
|
|
// Create locations for all layer files
|
|
// Each location has RealPath="/", FileSystemID=digest, AccessPath="/"
|
|
locations := make(map[string]file.Location, len(layerFiles))
|
|
for digest := range layerFiles {
|
|
// Use NewVirtualLocationFromCoordinates with digest as FileSystemID
|
|
coords := file.NewCoordinates("/", digest)
|
|
locations[digest] = file.NewVirtualLocationFromCoordinates(coords, "/")
|
|
}
|
|
|
|
return &ContainerImageModel{
|
|
tempDir: tempDir,
|
|
layerFiles: layerFiles,
|
|
locations: locations,
|
|
ref: ref,
|
|
}
|
|
}
|
|
|
|
// ImageReference returns the image reference the artifact was fetched with.
|
|
func (r *ContainerImageModel) ImageReference() string {
|
|
return r.ref
|
|
}
|
|
|
|
// FilesByMediaType returns locations for layers matching the given media type patterns.
|
|
// Patterns support glob-style matching (e.g., "application/vnd.docker.ai*").
|
|
func (r *ContainerImageModel) FilesByMediaType(types ...string) ([]file.Location, error) {
|
|
var matches []file.Location
|
|
|
|
for digest, info := range r.layerFiles {
|
|
for _, pattern := range types {
|
|
matched, err := filepath.Match(pattern, info.MediaType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid media type pattern %q: %w", pattern, err)
|
|
}
|
|
if matched {
|
|
if loc, ok := r.locations[digest]; ok {
|
|
matches = append(matches, loc)
|
|
}
|
|
break // Don't add the same location twice
|
|
}
|
|
}
|
|
}
|
|
|
|
return matches, nil
|
|
}
|
|
|
|
// FileContentsByLocation returns the contents of the file at the given location.
|
|
// The location's FileSystemID contains the layer digest, which is used to look up the temp file.
|
|
// This method is used as part of the content selection in the generic cataloger when locations
|
|
// are returned by searching for contents by media type.
|
|
func (r *ContainerImageModel) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
|
|
// Look up the temp file path using the digest stored in FileSystemID
|
|
digest := location.FileSystemID
|
|
info, ok := r.layerFiles[digest]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no file found for digest %q", digest)
|
|
}
|
|
return os.Open(info.TempPath)
|
|
}
|
|
|
|
// FileMetadataByLocation returns metadata for the file at the given location.
|
|
func (r *ContainerImageModel) FileMetadataByLocation(_ file.Location) (m file.Metadata, err error) {
|
|
return m, nil
|
|
}
|
|
|
|
// HasPath checks if the given path exists in the resolver.
|
|
func (r *ContainerImageModel) HasPath(path string) bool {
|
|
// The virtual path is "/" for all files
|
|
if path == "/" && len(r.layerFiles) > 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// FilesByPath returns locations for files matching the given paths.
|
|
func (r *ContainerImageModel) FilesByPath(_ ...string) ([]file.Location, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// FilesByGlob returns locations for files matching the given glob patterns.
|
|
func (r *ContainerImageModel) FilesByGlob(_ ...string) ([]file.Location, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// FilesByMIMEType returns locations for files with the given MIME types.
|
|
// This is not implemented for OCI model artifacts as we don't have MIME type detection.
|
|
func (r *ContainerImageModel) FilesByMIMEType(_ ...string) ([]file.Location, error) {
|
|
// Not implemented - OCI model artifacts don't have MIME type detection
|
|
return nil, nil
|
|
}
|
|
|
|
// RelativeFileByPath returns a file at the given path relative to the reference location.
|
|
// This is not applicable for OCI model artifacts.
|
|
func (r *ContainerImageModel) RelativeFileByPath(_ file.Location, _ string) *file.Location {
|
|
// Not implemented - no layer hierarchy in OCI model artifacts
|
|
return nil
|
|
}
|
|
|
|
// AllLocations returns all file locations in the resolver.
|
|
func (r *ContainerImageModel) AllLocations(ctx context.Context) <-chan file.Location {
|
|
ch := make(chan file.Location)
|
|
|
|
go func() {
|
|
defer close(ch)
|
|
|
|
for _, loc := range r.locations {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case ch <- loc:
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ch
|
|
}
|