github.com/anchore/syft@v1.38.2/syft/pkg/apk.go (about)

     1  package pkg
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/go-viper/mapstructure/v2"
    11  	"github.com/scylladb/go-set/strset"
    12  
    13  	"github.com/anchore/syft/syft/file"
    14  )
    15  
    16  const ApkDBGlob = "**/lib/apk/db/installed"
    17  
    18  var _ FileOwner = (*ApkDBEntry)(nil)
    19  
    20  // ApkDBEntry represents all captured data for the alpine linux package manager flat-file store.
    21  // See the following sources for more information:
    22  // - https://wiki.alpinelinux.org/wiki/Apk_spec
    23  // - https://git.alpinelinux.org/apk-tools/tree/src/package.c
    24  // - https://git.alpinelinux.org/apk-tools/tree/src/database.c
    25  type ApkDBEntry struct {
    26  	// Package is the package name as found in the installed file
    27  	Package string `mapstructure:"P" json:"package"`
    28  
    29  	// OriginPackage is the original source package name this binary was built from (used to track which aport/source built this)
    30  	OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
    31  
    32  	// Maintainer is the package maintainer name and email
    33  	Maintainer string `mapstructure:"m" json:"maintainer"`
    34  
    35  	// Version is the package version as found in the installed file
    36  	Version string `mapstructure:"V" json:"version"`
    37  
    38  	// Architecture is the target CPU architecture
    39  	Architecture string `mapstructure:"A" json:"architecture"`
    40  
    41  	// URL is the upstream project URL
    42  	URL string `mapstructure:"U" json:"url"`
    43  
    44  	// Description is a human-readable package description
    45  	Description string `mapstructure:"T" json:"description"`
    46  
    47  	// Size is the package archive size in bytes (.apk file size)
    48  	Size int `mapstructure:"S" json:"size" cyclonedx:"size"`
    49  
    50  	// InstalledSize is the total size of installed files in bytes
    51  	InstalledSize int `mapstructure:"I" json:"installedSize" cyclonedx:"installedSize"`
    52  
    53  	// Dependencies are the runtime dependencies required by this package
    54  	Dependencies []string `mapstructure:"D" json:"pullDependencies" cyclonedx:"pullDependencies"`
    55  
    56  	// Provides are virtual packages provided by this package (for capability-based dependencies)
    57  	Provides []string `mapstructure:"p" json:"provides" cyclonedx:"provides"`
    58  
    59  	// Checksum is the package content checksum for integrity verification
    60  	Checksum string `mapstructure:"C" json:"pullChecksum" cyclonedx:"pullChecksum"`
    61  
    62  	// GitCommit is the git commit hash of the APK port definition in Alpine's aports repository
    63  	GitCommit string `mapstructure:"c" json:"gitCommitOfApkPort" cyclonedx:"gitCommitOfApkPort"`
    64  
    65  	// Files are the files installed by this package
    66  	Files []ApkFileRecord `json:"files"`
    67  }
    68  
    69  // spaceDelimitedStringSlice is an internal helper type for unmarshaling space-delimited strings from JSON into a string slice.
    70  type spaceDelimitedStringSlice []string
    71  
    72  func (m *ApkDBEntry) UnmarshalJSON(data []byte) error {
    73  	var fields []reflect.StructField
    74  	t := reflect.TypeOf(ApkDBEntry{})
    75  	for i := 0; i < t.NumField(); i++ {
    76  		f := t.Field(i)
    77  		if f.Name == "Dependencies" {
    78  			f.Type = reflect.TypeOf(spaceDelimitedStringSlice{})
    79  		}
    80  		fields = append(fields, f)
    81  	}
    82  	apkMetadata := reflect.StructOf(fields)
    83  	inst := reflect.New(apkMetadata)
    84  	if err := json.Unmarshal(data, inst.Interface()); err != nil {
    85  		return err
    86  	}
    87  
    88  	return mapstructure.Decode(inst.Elem().Interface(), m)
    89  }
    90  
    91  func (a *spaceDelimitedStringSlice) UnmarshalJSON(data []byte) error {
    92  	var jsonObj interface{}
    93  
    94  	if err := json.Unmarshal(data, &jsonObj); err != nil {
    95  		return err
    96  	}
    97  
    98  	switch obj := jsonObj.(type) {
    99  	case string:
   100  		if obj == "" {
   101  			*a = nil
   102  		} else {
   103  			*a = strings.Split(obj, " ")
   104  		}
   105  		return nil
   106  	case []interface{}:
   107  		s := make([]string, 0, len(obj))
   108  		for _, v := range obj {
   109  			value, ok := v.(string)
   110  			if !ok {
   111  				return fmt.Errorf("invalid type for string array element: %T", v)
   112  			}
   113  			s = append(s, value)
   114  		}
   115  		*a = s
   116  		return nil
   117  	case nil:
   118  		return nil
   119  	default:
   120  		return fmt.Errorf("invalid type for string array: %T", obj)
   121  	}
   122  }
   123  
   124  // ApkFileRecord represents a single file listing and metadata from a APK DB entry (which may have many of these file records).
   125  type ApkFileRecord struct {
   126  	// Path is the file path relative to the filesystem root
   127  	Path string `json:"path"`
   128  
   129  	// OwnerUID is the file owner user ID
   130  	OwnerUID string `json:"ownerUid,omitempty"`
   131  
   132  	// OwnerGID is the file owner group ID
   133  	OwnerGID string `json:"ownerGid,omitempty"`
   134  
   135  	// Permissions is the file permission mode string (e.g. "0755", "0644")
   136  	Permissions string `json:"permissions,omitempty"`
   137  
   138  	// Digest is the file content hash for integrity verification
   139  	Digest *file.Digest `json:"digest,omitempty"`
   140  }
   141  
   142  func (m ApkDBEntry) OwnedFiles() (result []string) {
   143  	s := strset.New()
   144  	for _, f := range m.Files {
   145  		if f.Path != "" {
   146  			s.Add(f.Path)
   147  		}
   148  	}
   149  	result = s.List()
   150  	sort.Strings(result)
   151  	return result
   152  }