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 }