github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/snap/parse_kernel_changelog.go (about) 1 package snap 2 3 import ( 4 "compress/gzip" 5 "context" 6 "fmt" 7 "regexp" 8 "strings" 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 // kernelVersionInfo holds parsed kernel version information 17 type kernelVersionInfo struct { 18 baseVersion string // e.g., "5.4.0-195" 19 releaseVersion string // e.g., "215" 20 fullVersion string // e.g., "5.4.0-195.215" 21 majorVersion string // e.g., "5.4" 22 } 23 24 // parseKernelChangelog parses changelog files from kernel snaps to extract kernel version 25 func parseKernelChangelog(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 26 // The file should be gzipped 27 lines, err := readChangelogLines(reader) 28 if err != nil { 29 return nil, nil, err 30 } 31 32 // pull from first line 33 versionInfo, err := extractKernelVersion(lines[0]) 34 if err != nil { 35 return nil, nil, err 36 } 37 38 snapMetadata := pkg.SnapEntry{ 39 SnapType: pkg.SnapTypeKernel, 40 } 41 42 packages := createMainKernelPackage(versionInfo, snapMetadata, reader.Location) 43 44 // Check for base kernel package 45 basePackage := findBaseKernelPackage(lines, versionInfo, snapMetadata, reader.Location) 46 if basePackage != nil { 47 packages = append(packages, *basePackage) 48 } 49 50 return packages, nil, nil 51 } 52 53 // readChangelogLines reads and decompresses the changelog content 54 func readChangelogLines(reader file.LocationReadCloser) ([]string, error) { 55 gzReader, err := gzip.NewReader(reader) 56 if err != nil { 57 return nil, fmt.Errorf("failed to create gzip reader for changelog: %w", err) 58 } 59 defer gzReader.Close() 60 61 content, err := readAll(gzReader) 62 if err != nil { 63 return nil, fmt.Errorf("failed to read changelog content: %w", err) 64 } 65 66 lines := strings.Split(string(content), "\n") 67 if len(lines) == 0 { 68 return nil, fmt.Errorf("changelog file is empty") 69 } 70 71 // Parse the first line to extract kernel version information 72 // Format: "linux (5.4.0-195.215) focal; urgency=medium" 73 return lines, nil 74 } 75 76 // extractKernelVersion parses version information from the first changelog line 77 func extractKernelVersion(firstLine string) (*kernelVersionInfo, error) { 78 // Format: "linux (5.4.0-195.215) focal; urgency=medium" 79 kernelVersionRegex := regexp.MustCompile(`linux \(([0-9]+\.[0-9]+\.[0-9]+-[0-9]+)\.([0-9]+)\)`) 80 matches := kernelVersionRegex.FindStringSubmatch(firstLine) 81 82 if len(matches) < 3 { 83 return nil, fmt.Errorf("could not parse kernel version from changelog: %s", firstLine) 84 } 85 86 info := &kernelVersionInfo{ 87 baseVersion: matches[1], // e.g., "5.4.0-195" 88 releaseVersion: matches[2], // eg., "215" 89 } 90 // eg "5.4.0-195.215" 91 info.fullVersion = fmt.Sprintf("%s.%s", info.baseVersion, info.releaseVersion) 92 93 // Extract major version; package naming 94 majorVersionRegex := regexp.MustCompile(`([0-9]+\.[0-9]+)\.[0-9]+-[0-9]+`) 95 majorMatches := majorVersionRegex.FindStringSubmatch(info.baseVersion) 96 97 if len(majorMatches) >= 2 { 98 info.majorVersion = majorMatches[1] 99 } else { 100 info.majorVersion = info.baseVersion 101 } 102 103 return info, nil 104 } 105 106 // createMainKernelPackage creates the main kernel package 107 func createMainKernelPackage(versionInfo *kernelVersionInfo, snapMetadata pkg.SnapEntry, location file.Location) []pkg.Package { 108 kernelPackageName := fmt.Sprintf("linux-image-%s-generic", versionInfo.baseVersion) 109 kernelPkg := newDebianPackageFromSnap( 110 kernelPackageName, 111 versionInfo.fullVersion, 112 snapMetadata, 113 location, 114 ) 115 116 return []pkg.Package{kernelPkg} 117 } 118 119 // findBaseKernelPackage searches for and creates base kernel package if present 120 func findBaseKernelPackage(lines []string, versionInfo *kernelVersionInfo, snapMetadata pkg.SnapEntry, location file.Location) *pkg.Package { 121 baseKernelEntry := fmt.Sprintf("%s/linux:", strings.ReplaceAll(versionInfo.releaseVersion, ";", "/")) 122 123 for _, line := range lines { 124 if strings.Contains(line, baseKernelEntry) { 125 return parseBaseKernelLine(line, versionInfo.majorVersion, snapMetadata, location) 126 } 127 } 128 129 return nil 130 } 131 132 // parseBaseKernelLine extracts base kernel version from a changelog line 133 func parseBaseKernelLine(line string, majorVersion string, snapMetadata pkg.SnapEntry, location file.Location) *pkg.Package { 134 baseKernelRegex := regexp.MustCompile(fmt.Sprintf(`(%s-[0-9]+)\.?[0-9]*`, regexp.QuoteMeta(majorVersion))) 135 baseMatches := baseKernelRegex.FindStringSubmatch(line) 136 137 if len(baseMatches) < 2 { 138 return nil 139 } 140 141 baseKernelVersion := baseMatches[1] 142 baseKernelFullRegex := regexp.MustCompile(fmt.Sprintf(`(%s-[0-9]+\.[0-9]+)`, regexp.QuoteMeta(majorVersion))) 143 baseFullMatches := baseKernelFullRegex.FindStringSubmatch(line) 144 145 var baseFullVersion string 146 if len(baseFullMatches) >= 2 { 147 baseFullVersion = baseFullMatches[1] 148 } else { 149 baseFullVersion = baseKernelVersion 150 } 151 152 baseKernelPkg := newDebianPackageFromSnap( 153 fmt.Sprintf("linux-image-%s-generic", baseKernelVersion), 154 baseFullVersion, 155 snapMetadata, 156 location, 157 ) 158 159 return &baseKernelPkg 160 }