Skip to content

Commit 2718052

Browse files
authored
Merge branch 'main' into pre-post-merge
2 parents 1c98caa + 08eed45 commit 2718052

8 files changed

Lines changed: 391 additions & 86 deletions

File tree

internal/image/imageos/imageos.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,7 +1618,7 @@ func configUserStartupScript(installRoot string, user config.UserConfig) error {
16181618
func (imageOs *ImageOs) generateSBOM(installRoot string, template *config.ImageTemplate) (string, error) {
16191619
pkgType := imageOs.chrootEnv.GetTargetOsPkgType()
16201620
sBomFNm := rpmutils.GenerateSPDXFileName(template.GetImageName())
1621-
cmd := "rpm -qa --queryformat '%{NAME}\\n'"
1621+
cmd := "rpm -qa"
16221622
if pkgType == "deb" {
16231623
cmd = "dpkg -l | awk '/^ii/ {print $2}'"
16241624
sBomFNm = debutils.GenerateSPDXFileName(template.GetImageName())
@@ -1637,20 +1637,25 @@ func (imageOs *ImageOs) generateSBOM(installRoot string, template *config.ImageT
16371637
// Create a map of normalized package names from installed packages for faster lookup
16381638
installedPkgMap := make(map[string]bool)
16391639
for _, pkg := range installRootPkgs {
1640-
// For DEB packages, remove architecture tag (e.g., ":amd64") if present
1640+
// Remove architecture tag (e.g., ":amd64") if present
16411641
normalizedPkg := pkg
1642-
if pkgType == "deb" {
1643-
if colonIndex := strings.Index(pkg, ":"); colonIndex != -1 {
1644-
normalizedPkg = pkg[:colonIndex]
1645-
}
1642+
if colonIndex := strings.Index(pkg, ":"); colonIndex != -1 {
1643+
normalizedPkg = pkg[:colonIndex]
16461644
}
16471645
installedPkgMap[normalizedPkg] = true
16481646
}
16491647

16501648
var finalPkgs []ospackage.PackageInfo
16511649
for _, pkg := range downloadedPkgs {
1652-
// pkg.Name is the clean canonical package name (e.g., "SymCrypt", "bash")
1653-
if installedPkgMap[pkg.Name] {
1650+
// Normalize package name by removing file extensions
1651+
normalizedName := pkg.Name
1652+
if strings.HasSuffix(normalizedName, ".rpm") {
1653+
normalizedName = strings.TrimSuffix(normalizedName, ".rpm")
1654+
} else if strings.HasSuffix(normalizedName, ".deb") {
1655+
normalizedName = strings.TrimSuffix(normalizedName, ".deb")
1656+
}
1657+
1658+
if installedPkgMap[normalizedName] {
16541659
finalPkgs = append(finalPkgs, pkg)
16551660
}
16561661
}

internal/ospackage/ospackage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package ospackage
22

33
// PackageInfo holds everything you need to fetch + verify one artifact.
44
type PackageInfo struct {
5-
Name string // e.g. "abseil-cpp"
5+
Name string // e.g. file name "abseil-cpp.rpm"
66
Type string // e.g. "rpm", "deb", "apk"
77
Description string // e.g. "Abseil C++ Common Libraries"
88
Origin string // e.g. "Intel", the vendor or supplier of the package
@@ -15,6 +15,7 @@ type PackageInfo struct {
1515
Requires []string // capabilities this package requires
1616
RequiresVer []string // version constraints for the required capabilities
1717
Files []string // list of files in this package (rpm:files)
18+
PkgName string // name of the package
1819
}
1920

2021
// Checksum holds the algorithm and value of a checksum.

internal/ospackage/rpmutils/download.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,16 @@ func DownloadPackagesComplete(pkgList []string, destDir, dotFile string, pkgSour
396396
}
397397
all = append(all, userpkg...)
398398

399+
// Adjust package names to remove any prefixes before PkgName - Azure Linux RPM repos often prefix package file names
400+
for i := range all {
401+
// Find where the package name starts in the full name
402+
if idx := strings.Index(all[i].Name, all[i].PkgName); idx > 0 {
403+
// Remove the prefix by taking substring from where PkgName starts
404+
all[i].Name = all[i].Name[idx:]
405+
}
406+
// If PkgName is not found or is at the beginning, keep the original Name
407+
}
408+
399409
// Match the packages in the template against all the packages
400410
req, err := MatchRequested(pkgList, all)
401411
if err != nil {
@@ -430,8 +440,7 @@ func DownloadPackagesComplete(pkgList []string, destDir, dotFile string, pkgSour
430440
}
431441
}
432442

433-
// Extract URLs and build download list using URL basenames
434-
// (files are saved by URL basename, e.g., "SymCrypt-106.0.1-1.emt3.x86_64.rpm")
443+
// Extract URLs
435444
urls := make([]string, len(sorted_pkgs))
436445
for i, pkg := range sorted_pkgs {
437446
urls[i] = pkg.URL

internal/ospackage/rpmutils/download_test.go

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestMatchRequested(t *testing.T) {
164164
name: "exact name match with .rpm extension",
165165
requests: []string{"test-package"},
166166
all: []ospackage.PackageInfo{
167-
{Name: "test-package", Arch: "x86_64", Version: "1.0-1"},
167+
{Name: "test-package.rpm", Arch: "x86_64"},
168168
},
169169
expectError: false,
170170
expectCount: 1,
@@ -173,8 +173,8 @@ func TestMatchRequested(t *testing.T) {
173173
name: "version prefix match",
174174
requests: []string{"acl"},
175175
all: []ospackage.PackageInfo{
176-
{Name: "acl", Version: "2.3.1-2.el8", Arch: "x86_64"},
177-
{Name: "acl-dev", Arch: "x86_64"}, // Should not match - different package
176+
{Name: "acl-2.3.1-2.el8", Arch: "x86_64"},
177+
{Name: "acl-dev", Arch: "x86_64"}, // Should not match - not a version
178178
},
179179
expectError: false,
180180
expectCount: 1,
@@ -183,7 +183,7 @@ func TestMatchRequested(t *testing.T) {
183183
name: "release prefix match",
184184
requests: []string{"package"},
185185
all: []ospackage.PackageInfo{
186-
{Name: "package", Version: "1.0.0", Arch: "x86_64"},
186+
{Name: "package-1.0.0", Arch: "x86_64"},
187187
},
188188
expectError: false,
189189
expectCount: 1,
@@ -211,12 +211,12 @@ func TestMatchRequested(t *testing.T) {
211211
name: "multiple candidates - pick highest",
212212
requests: []string{"package"},
213213
all: []ospackage.PackageInfo{
214-
{Name: "package", Version: "1.0.0", Arch: "x86_64"},
215-
{Name: "package", Version: "2.0.0", Arch: "x86_64"},
216-
{Name: "package", Version: "1.5.0", Arch: "x86_64"},
214+
{Name: "package-1.0.0", Arch: "x86_64"},
215+
{Name: "package-2.0.0", Arch: "x86_64"},
216+
{Name: "package-1.5.0", Arch: "x86_64"},
217217
},
218218
expectError: false,
219-
expectCount: 1, // Should pick package with version 2.0.0 (exact name match, first found)
219+
expectCount: 1, // Should pick package-2.0.0 (highest lex sort)
220220
},
221221
}
222222

@@ -240,6 +240,136 @@ func TestMatchRequested(t *testing.T) {
240240
}
241241
}
242242

243+
func TestIsAcceptedChar(t *testing.T) {
244+
testCases := []struct {
245+
name string
246+
input string
247+
expected bool
248+
}{
249+
{
250+
name: "empty string",
251+
input: "",
252+
expected: false,
253+
},
254+
{
255+
name: "only digits",
256+
input: "123",
257+
expected: true,
258+
},
259+
{
260+
name: "digits with dash",
261+
input: "1-2-3",
262+
expected: true,
263+
},
264+
{
265+
name: "contains letters",
266+
input: "1a2",
267+
expected: false,
268+
},
269+
{
270+
name: "contains special chars",
271+
input: "1.2",
272+
expected: false,
273+
},
274+
{
275+
name: "only dash",
276+
input: "-",
277+
expected: true,
278+
},
279+
{
280+
name: "mixed valid chars",
281+
input: "123-456-789",
282+
expected: true,
283+
},
284+
}
285+
286+
for _, tc := range testCases {
287+
t.Run(tc.name, func(t *testing.T) {
288+
// Since isAcceptedChar is not exported, we need to test it indirectly through isValidVersionFormat
289+
// or we need to make it exported for testing. For now, let's test it indirectly.
290+
t.Skip("isAcceptedChar is not exported - testing indirectly through isValidVersionFormat")
291+
})
292+
}
293+
}
294+
295+
func TestIsValidVersionFormat(t *testing.T) {
296+
testCases := []struct {
297+
name string
298+
input string
299+
expected bool
300+
}{
301+
{
302+
name: "empty string",
303+
input: "",
304+
expected: false,
305+
},
306+
{
307+
name: "simple version",
308+
input: "1.0.0",
309+
expected: true,
310+
},
311+
{
312+
name: "version with dash",
313+
input: "1-2.el8",
314+
expected: true,
315+
},
316+
{
317+
name: "version without dot",
318+
input: "123",
319+
expected: true,
320+
},
321+
{
322+
name: "invalid version with letters",
323+
input: "abc.def",
324+
expected: false,
325+
},
326+
{
327+
name: "version starting with letter",
328+
input: "a1.0.0",
329+
expected: false,
330+
},
331+
{
332+
name: "complex version",
333+
input: "2-3-1.el8_5",
334+
expected: true,
335+
},
336+
}
337+
338+
for _, tc := range testCases {
339+
t.Run(tc.name, func(t *testing.T) {
340+
// Since isValidVersionFormat is not exported, we'll test it indirectly
341+
// Let's create a test that exercises this through MatchRequested
342+
all := []ospackage.PackageInfo{
343+
{Name: fmt.Sprintf("package-%s", tc.input), Arch: "x86_64"},
344+
}
345+
346+
result, err := rpmutils.MatchRequested([]string{"package"}, all)
347+
348+
if tc.expected {
349+
// If the version format is valid, we should find a match
350+
if err != nil || len(result) == 0 {
351+
t.Errorf("Expected to find match for valid version format %q", tc.input)
352+
}
353+
} else {
354+
// If the version format is invalid, we should not find a match (unless it's exact)
355+
if err == nil && len(result) > 0 && result[0].Name != "package" {
356+
// Only fail if we found a match that wasn't exact
357+
exactMatch := false
358+
for _, pkg := range all {
359+
if pkg.Name == "package" {
360+
exactMatch = true
361+
break
362+
}
363+
}
364+
if !exactMatch {
365+
t.Errorf("Expected no match for invalid version format %q, but got: %v", tc.input, result)
366+
}
367+
}
368+
}
369+
})
370+
}
371+
}
372+
243373
func TestValidate(t *testing.T) {
244374
// Save original global variables
245375
originalRepoCfg := rpmutils.RepoCfg

internal/ospackage/rpmutils/helper.go

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func resolveMultiCandidates(parentPkg ospackage.PackageInfo, candidates []ospack
2727
ver := ""
2828
hasVersionConstraint := false
2929
if len(candidates) > 0 {
30-
op, ver, hasVersionConstraint = extractVersionRequirement(parentPkg.RequiresVer, candidates[0].Name)
30+
op, ver, hasVersionConstraint = extractVersionRequirement(parentPkg.RequiresVer, extractBasePackageNameFromFile(candidates[0].Name))
3131
}
3232

3333
if hasVersionConstraint {
@@ -207,6 +207,42 @@ func compareVersions(v1, v2 string) int {
207207
return cmp
208208
}
209209

210+
// extractBasePackageNameFromFile extracts the base package name from a full package filename
211+
// e.g., "curl-8.8.0-2.azl3.x86_64.rpm" -> "curl"
212+
// e.g., "curl-devel-8.8.0-1.azl3.x86_64.rpm" -> "curl-devel"
213+
func extractBasePackageNameFromFile(fullName string) string {
214+
// Remove .rpm suffix if present
215+
name := strings.TrimSuffix(fullName, ".rpm")
216+
217+
// Split by '-' and find where the version starts
218+
parts := strings.Split(name, "-")
219+
if len(parts) < 2 {
220+
return name
221+
}
222+
223+
// Find the first part that looks like a version (starts with digit)
224+
for i := 1; i < len(parts); i++ {
225+
if len(parts[i]) > 0 && (parts[i][0] >= '0' && parts[i][0] <= '9') {
226+
// get the name
227+
maybe_name := strings.Join(parts[:i], "-")
228+
// check if version is part of the name
229+
// full name contains version, if package name has version,
230+
// it will be repeated in the full name
231+
for j := i + 1; j < len(parts); j++ {
232+
if len(parts[j]) > 0 && strings.Contains(parts[j], parts[i]) {
233+
maybe_name = strings.Join(parts[:j], "-")
234+
break
235+
}
236+
}
237+
// return name or name-version
238+
return maybe_name
239+
}
240+
}
241+
242+
// If no version-like part found, return the whole name
243+
return name
244+
}
245+
210246
// extractBaseNameFromDep takes a potentially complex requirement string
211247
// and returns only the base package/capability name.
212248
func extractBaseNameFromDep(req string) string {
@@ -232,8 +268,9 @@ func findAllCandidates(parent ospackage.PackageInfo, depName string, all []ospac
232268

233269
// First pass: look for exact name (canonical name) matches
234270
for _, pi := range all {
235-
// pi.Name is already the clean package name from XML <name> element
236-
if pi.Name == depName {
271+
// Extract the base package name (everything before the first '-' that starts a version)
272+
baseName := extractBasePackageNameFromFile(pi.Name)
273+
if baseName == depName {
237274
candidates = append(candidates, pi)
238275
}
239276
}
@@ -272,22 +309,29 @@ func findAllCandidates(parent ospackage.PackageInfo, depName string, all []ospac
272309
// ResolvePackage finds the best matching package for a given package name
273310
func ResolveTopPackageConflicts(want string, all []ospackage.PackageInfo) (ospackage.PackageInfo, bool) {
274311
var candidates []ospackage.PackageInfo
275-
exactMatch := false
276312
for _, pi := range all {
277-
// 1) exact name match (e.g., "acct")
313+
// 1) exact name, e.g. acct-205-25.azl3.noarch.rpm
278314
if pi.Name == want {
279315
candidates = append(candidates, pi)
280-
exactMatch = true
316+
break
317+
}
318+
cleanName := extractBasePackageNameFromFile(pi.Name)
319+
// 2) base name, e.g. acct
320+
if cleanName == want {
321+
candidates = append(candidates, pi)
281322
continue
282323
}
283-
// 2) prefix match for version-specific requests (e.g., want = "openvino-2025.3.0")
284-
// Only try prefix match if we haven't found any exact matches
285-
if !exactMatch && strings.HasPrefix(want, pi.Name+"-") && len(want) > len(pi.Name)+1 {
286-
// Extract string after package name and compare with pi.Version
287-
verStr := want[len(pi.Name)+1:]
288-
if strings.Contains(pi.Version, verStr) {
289-
candidates = append(candidates, pi)
290-
continue
324+
// 3) prefix by want-version ("acl-")
325+
// expected pi.Name should look like openvino-2025.3.0-2025.3.0.19807-1.noarch.rpm
326+
// want = openvino-2025.3.0
327+
if strings.HasPrefix(pi.Name, want) {
328+
// Extract string after "-" and compare with pi.Version
329+
if dashIdx := strings.LastIndex(want, "-"); dashIdx != -1 {
330+
verStr := want[dashIdx+1:]
331+
if strings.Contains(pi.Version, verStr) {
332+
candidates = append(candidates, pi)
333+
continue
334+
}
291335
}
292336
}
293337
}
@@ -296,8 +340,8 @@ func ResolveTopPackageConflicts(want string, all []ospackage.PackageInfo) (ospac
296340
return ospackage.PackageInfo{}, false
297341
}
298342

299-
// Single candidate - return directly
300-
if len(candidates) == 1 {
343+
// If we got an exact match in step (1), it's the only candidate
344+
if len(candidates) == 1 && (candidates[0].Name == want || extractBasePackageNameFromFile(candidates[0].Name) == want) {
301345
return candidates[0], true
302346
}
303347

0 commit comments

Comments
 (0)