diff --git a/internal/weldr/api.go b/internal/weldr/api.go index fc040b793..557f1e965 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -1567,27 +1567,54 @@ func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *h common.PanicOnError(err) } -// setPkgEVRA replaces the version globs in the blueprint with their EVRA values from the dependencies -// -// The dependencies must be pre-sorted for this function to work properly -// It will return an error if it cannot find a package in the dependencies -func setPkgEVRA(dependencies []rpmmd.PackageSpec, packages []blueprint.Package) error { - for pkgIndex, pkg := range packages { - i := sort.Search(len(dependencies), func(i int) bool { - return dependencies[i].Name >= pkg.Name - }) - if i < len(dependencies) && dependencies[i].Name == pkg.Name { - if dependencies[i].Epoch == 0 { - packages[pkgIndex].Version = fmt.Sprintf("%s-%s.%s", dependencies[i].Version, dependencies[i].Release, dependencies[i].Arch) - } else { - packages[pkgIndex].Version = fmt.Sprintf("%d:%s-%s.%s", dependencies[i].Epoch, dependencies[i].Version, dependencies[i].Release, dependencies[i].Arch) +// expandBlueprintGlobs will expand package name globs and versions using the depsolve results +// The result is a sorted list of Package structs with the full package name and version +// It will return an error if it cannot find a non-glob package name in the dependency list +func expandBlueprintGlobs(dependencies []rpmmd.PackageSpec, packages []blueprint.Package) ([]blueprint.Package, error) { + newPackages := make(map[string]blueprint.Package) + + for _, pkg := range packages { + if !strings.ContainsAny(pkg.Name, "*?") { + // No glob characters, find an exact match in dependencies + i := sort.Search(len(dependencies), func(i int) bool { + return dependencies[i].Name >= pkg.Name + }) + if i >= len(dependencies) || dependencies[i].Name != pkg.Name { + // Packages should not be missing from the depsolve results + return nil, fmt.Errorf("%s missing from depsolve results", pkg.Name) + } + newPackages[dependencies[i].GetNEVRA()] = blueprint.Package{ + Name: dependencies[i].Name, + Version: dependencies[i].GetEVRA(), } } else { - // Packages should not be missing from the depsolve results - return fmt.Errorf("%s missing from depsolve results", pkg.Name) + // Add all the packages matching the glob + g, err := glob.Compile(pkg.Name) + if err != nil { + return nil, err + } + + for _, d := range dependencies { + if g.Match(d.Name) { + newPackages[d.GetNEVRA()] = blueprint.Package{ + Name: d.Name, + Version: d.GetEVRA(), + } + } + } } } - return nil + + // Return a sorted slice of the Packages + np := make([]blueprint.Package, 0, len(newPackages)) + for _, pkg := range newPackages { + np = append(np, pkg) + } + sort.Slice(np, func(i, j int) bool { + return np[i].Name < np[j].Name + }) + + return np, nil } func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { @@ -1650,7 +1677,8 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt return dependencies[i].Name < dependencies[j].Name }) - err = setPkgEVRA(dependencies, blueprint.Packages) + // Expand any package name globs and set the package version + newPackages, err := expandBlueprintGlobs(dependencies, blueprint.Packages) if err != nil { rerr := responseError{ ID: "BlueprintsError", @@ -1659,8 +1687,10 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt errors = append(errors, rerr) break } + blueprint.Packages = newPackages - err = setPkgEVRA(dependencies, blueprint.Modules) + // Expand any module name globs and set the module version + newModules, err := expandBlueprintGlobs(dependencies, blueprint.Modules) if err != nil { rerr := responseError{ ID: "BlueprintsError", @@ -1669,6 +1699,7 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt errors = append(errors, rerr) break } + blueprint.Modules = newModules blueprints = append(blueprints, blueprintFrozen{blueprint}) } diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go index a374bbff2..58be8f794 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -34,6 +34,7 @@ import ( "github.com/BurntSushi/toml" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -617,51 +618,6 @@ func TestNonExistentBlueprintsInfoToml(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) } -func TestSetPkgEVRA(t *testing.T) { - - // Sorted list of dependencies - deps := []rpmmd.PackageSpec{ - { - Name: "dep-package1", - Epoch: 0, - Version: "1.33", - Release: "2.fc30", - Arch: "x86_64", - }, - { - Name: "dep-package2", - Epoch: 0, - Version: "2.9", - Release: "1.fc30", - Arch: "x86_64", - }, - { - Name: "dep-package3", - Epoch: 7, - Version: "3.0.3", - Release: "1.fc30", - Arch: "x86_64", - }, - } - pkgs := []blueprint.Package{ - {Name: "dep-package1", Version: "*"}, - {Name: "dep-package2", Version: "*"}, - } - // Replace globs with dependencies - err := setPkgEVRA(deps, pkgs) - require.NoErrorf(t, err, "setPkgEVRA failed") - require.Equalf(t, "1.33-2.fc30.x86_64", pkgs[0].Version, "setPkgEVRA Unexpected pkg version") - require.Equalf(t, "2.9-1.fc30.x86_64", pkgs[1].Version, "setPkgEVRA Unexpected pkg version") - - // Test that a missing package in deps returns an error - pkgs = []blueprint.Package{ - {Name: "dep-package1", Version: "*"}, - {Name: "dep-package0", Version: "*"}, - } - err = setPkgEVRA(deps, pkgs) - require.EqualErrorf(t, err, "dep-package0 missing from depsolve results", "setPkgEVRA missing package failed to return error") -} - func TestBlueprintsFreeze(t *testing.T) { t.Run("json", func(t *testing.T) { var cases = []struct { @@ -2223,6 +2179,140 @@ func TestComposePOST_ImageTypeDenylist(t *testing.T) { } } } +func TestExpandBlueprintNoGlob(t *testing.T) { + packages := []blueprint.Package{ + {Name: "tmux", Version: "3.3a"}, + {Name: "openssh-server", Version: "*"}, + {Name: "grub2", Version: "*"}, + } + // Sorted list of dependencies + dependencies := []rpmmd.PackageSpec{ + { + Name: "grub2", + Epoch: 1, + Version: "2.06", + Release: "94.fc38", + Arch: "noarch", + }, + { + Name: "openssh-server", + Epoch: 0, + Version: "9.0p1", + Release: "15.fc38", + Arch: "x86_64", + }, + { + Name: "tmux", + Epoch: 0, + Version: "3.3a", + Release: "3.fc38", + Arch: "x86_64", + }, + } + + newPackages, err := expandBlueprintGlobs(dependencies, packages) + require.NoError(t, err, "Error expanding globs") + expected := []blueprint.Package{ + {Name: "grub2", Version: "1:2.06-94.fc38.noarch"}, + {Name: "openssh-server", Version: "9.0p1-15.fc38.x86_64"}, + {Name: "tmux", Version: "3.3a-3.fc38.x86_64"}, + } + assert.Equal(t, expected, newPackages) +} + +func TestExpandBlueprintError(t *testing.T) { + // Test that a missing package in deps returns an error + packages := []blueprint.Package{ + {Name: "tmux", Version: "*"}, + {Name: "dep-package0", Version: "*"}, + } + // Sorted list of dependencies + dependencies := []rpmmd.PackageSpec{ + { + Name: "openssh-server", + Epoch: 0, + Version: "9.0p1", + Release: "15.fc38", + Arch: "x86_64", + }, + { + Name: "tmux", + Epoch: 0, + Version: "3.3a", + Release: "3.fc38", + Arch: "x86_64", + }, + } + _, err := expandBlueprintGlobs(dependencies, packages) + require.EqualError(t, err, "dep-package0 missing from depsolve results") +} + +func TestExpandBlueprintGlobs(t *testing.T) { + packages := []blueprint.Package{ + {Name: "tmux", Version: "*"}, + {Name: "openssh-*", Version: "*"}, + {Name: "test-?-*", Version: "*"}, + {Name: "test-three-*", Version: "11.1"}, + {Name: "test-*", Version: "*"}, + } + // Sorted list of dependencies + dependencies := []rpmmd.PackageSpec{ + { + Name: "openssh-clients", + Epoch: 0, + Version: "9.0p1", + Release: "15.fc38", + Arch: "x86_64", + }, + { + Name: "openssh-server", + Epoch: 0, + Version: "9.0p1", + Release: "15.fc38", + Arch: "x86_64", + }, + { + Name: "test-1-one", + Epoch: 0, + Version: "1.0.0", + Release: "1.fc38", + Arch: "x86_64", + }, + { + Name: "test-2-two", + Epoch: 2, + Version: "1.0.0", + Release: "1.fc38", + Arch: "x86_64", + }, + { + Name: "test-three-3", + Epoch: 0, + Version: "11.1", + Release: "1.fc38", + Arch: "x86_64", + }, + { + Name: "tmux", + Epoch: 0, + Version: "3.3a", + Release: "3.fc38", + Arch: "x86_64", + }, + } + + newPackages, err := expandBlueprintGlobs(dependencies, packages) + require.NoError(t, err, "Error expanding globs") + expected := []blueprint.Package{ + {Name: "openssh-clients", Version: "9.0p1-15.fc38.x86_64"}, + {Name: "openssh-server", Version: "9.0p1-15.fc38.x86_64"}, + {Name: "test-1-one", Version: "1.0.0-1.fc38.x86_64"}, + {Name: "test-2-two", Version: "2:1.0.0-1.fc38.x86_64"}, + {Name: "test-three-3", Version: "11.1-1.fc38.x86_64"}, + {Name: "tmux", Version: "3.3a-3.fc38.x86_64"}, + } + assert.Equal(t, expected, newPackages) +} func TestMain(m *testing.M) { setupDNFJSON()