weldr: Expand package name globs in the frozen blueprint

When a blueprint containing package name globs is frozen it was failing
because it could not find the string in the dependency list. This fixes
it by replacing the package glob with all of the matching packages from
the dependency list.

This removes the setPkgEVRA function and adds a new expandBlueprintGlobs
function that handles the package name glob expansion, and replacement of
the version globs with the dependency EVRA.

Also includes testing for the new function.
This commit is contained in:
Brian C. Lane 2023-05-08 11:50:15 -07:00 committed by Brian C. Lane
parent f378ff6367
commit 468c63d433
2 changed files with 185 additions and 64 deletions

View file

@ -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})
}

View file

@ -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()