github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/snap/parse_snapd_snapcraft.go (about) 1 package snap 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "gopkg.in/yaml.v3" 9 10 "github.com/anchore/syft/syft/artifact" 11 "github.com/anchore/syft/syft/file" 12 "github.com/anchore/syft/syft/pkg" 13 "github.com/anchore/syft/syft/pkg/cataloger/generic" 14 ) 15 16 // snapcraftYaml represents the structure of snapcraft.yaml files found in snapd snaps 17 type snapcraftYaml struct { 18 Name string `yaml:"name"` 19 Version string `yaml:"version"` 20 Summary string `yaml:"summary"` 21 Description string `yaml:"description"` 22 Base string `yaml:"base"` 23 Grade string `yaml:"grade"` 24 Confinement string `yaml:"confinement"` 25 Architectures []string `yaml:"architectures"` 26 Parts map[string]snapcraftPart `yaml:"parts"` 27 } 28 29 // snapcraftPart represents a part in a snapcraft.yaml file 30 type snapcraftPart struct { 31 Plugin string `yaml:"plugin"` 32 Source string `yaml:"source"` 33 SourceType string `yaml:"source-type"` 34 SourceTag string `yaml:"source-tag"` 35 SourceCommit string `yaml:"source-commit"` 36 BuildPackages []string `yaml:"build-packages"` 37 StagePackages []string `yaml:"stage-packages"` 38 BuildSnaps []string `yaml:"build-snaps"` 39 StageSnaps []string `yaml:"stage-snaps"` 40 BuildEnvironment []map[string]string `yaml:"build-environment"` 41 Override map[string]string `yaml:"override-build"` 42 } 43 44 // parseSnapdSnapcraft parses snapcraft.yaml files from snapd snaps 45 func parseSnapdSnapcraft(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 46 var snapcraft snapcraftYaml 47 48 decoder := yaml.NewDecoder(reader) 49 if err := decoder.Decode(&snapcraft); err != nil { 50 return nil, nil, fmt.Errorf("failed to parse snapcraft.yaml: %w", err) 51 } 52 53 snapMetadata := createMetadata(snapcraft) 54 packages := extractPackagesFromParts(snapcraft, snapMetadata, reader.Location) 55 56 return packages, nil, nil 57 } 58 59 // createMetadata creates metadata from snapcraft.yaml 60 func createMetadata(snapcraft snapcraftYaml) pkg.SnapEntry { 61 metadata := pkg.SnapEntry{ 62 SnapType: pkg.SnapTypeSnapd, 63 Base: snapcraft.Base, 64 SnapName: snapcraft.Name, 65 SnapVersion: snapcraft.Version, 66 } 67 68 if len(snapcraft.Architectures) > 0 { 69 metadata.Architecture = snapcraft.Architectures[0] 70 } 71 72 return metadata 73 } 74 75 // extractPackagesFromParts processes all parts to extract packages 76 func extractPackagesFromParts(snapcraft snapcraftYaml, baseMetadata pkg.SnapEntry, location file.Location) []pkg.Package { 77 var packages []pkg.Package 78 79 for _, part := range snapcraft.Parts { 80 buildPackages := processBuildPackages(part.BuildPackages, baseMetadata, location) 81 packages = append(packages, buildPackages...) 82 83 stagePackages := processStagePackages(part.StagePackages, baseMetadata, location) 84 packages = append(packages, stagePackages...) 85 86 snapPackages := processSnapPackages(part.BuildSnaps, part.StageSnaps, baseMetadata, location) 87 packages = append(packages, snapPackages...) 88 } 89 90 return packages 91 } 92 93 // processBuildPackages creates packages from build-packages list 94 func processBuildPackages(buildPackages []string, metadata pkg.SnapEntry, location file.Location) []pkg.Package { 95 var packages []pkg.Package 96 97 for _, pkgName := range buildPackages { 98 if pkgName == "" { 99 continue 100 } 101 102 buildPkg := newDebianPackageFromSnap( 103 pkgName, 104 "unknown", 105 metadata, 106 location, 107 ) 108 packages = append(packages, buildPkg) 109 } 110 111 return packages 112 } 113 114 // processStagePackages creates packages from stage-packages list with version parsing 115 func processStagePackages(stagePackages []string, metadata pkg.SnapEntry, location file.Location) []pkg.Package { 116 var packages []pkg.Package 117 118 for _, pkgEntry := range stagePackages { 119 if pkgEntry == "" { 120 continue 121 } 122 123 name, version := parsePackageWithVersion(pkgEntry) 124 stagePkg := newDebianPackageFromSnap( 125 name, 126 version, 127 metadata, 128 location, 129 ) 130 packages = append(packages, stagePkg) 131 } 132 133 return packages 134 } 135 136 // parsePackageWithVersion extracts package name and version from version-constrained entries 137 func parsePackageWithVersion(pkgEntry string) (string, string) { 138 name := pkgEntry 139 version := "unknown" 140 141 if !strings.ContainsAny(pkgEntry, "=<>") { 142 return name, version 143 } 144 145 // Try to split on version operators 146 operators := []string{">=", "<=", "==", "!=", "=", ">", "<"} 147 for _, op := range operators { 148 if strings.Contains(pkgEntry, op) { 149 parts := strings.SplitN(pkgEntry, op, 2) 150 if len(parts) == 2 { 151 return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 152 } 153 } 154 } 155 156 return name, version 157 } 158 159 // processSnapPackages creates packages from snap dependencies 160 func processSnapPackages(buildSnaps, stageSnaps []string, baseMetadata pkg.SnapEntry, location file.Location) []pkg.Package { 161 var packages []pkg.Package 162 allSnaps := make([]string, 0, len(buildSnaps)+len(stageSnaps)) 163 allSnaps = append(allSnaps, buildSnaps...) 164 allSnaps = append(allSnaps, stageSnaps...) 165 166 for _, snapName := range allSnaps { 167 if snapName == "" { 168 continue 169 } 170 171 snapMetadata := pkg.SnapEntry{ 172 SnapType: pkg.SnapTypeApp, 173 SnapName: snapName, 174 SnapVersion: "unknown", 175 Architecture: baseMetadata.Architecture, 176 } 177 178 snapPkg := newPackage( 179 snapName, 180 "unknown", 181 snapMetadata, 182 location, 183 ) 184 packages = append(packages, snapPkg) 185 } 186 187 return packages 188 }