From c334c413a0cea65c341fb6cc291ad22b6675492c Mon Sep 17 00:00:00 2001 From: Andrew Dewar Date: Mon, 16 Dec 2024 11:52:13 -0700 Subject: [PATCH] Fixes 4932: Add list module streams for a snapshots --- .gitignore | 5 +- .mockery.yaml | 13 +++++ .vscode/launch.json | 7 +++ .vscode/settings.json | 3 ++ internal/test/integration/rpm_test.go | 50 ++++++++++++++++++ mk/includes.mk | 1 + mk/mockery.mk | 17 ++++++ pkg/tangy/interface.go | 2 +- pkg/tangy/rpm.go | 75 +++++++++++++++++++++++++++ pkg/tangy/tangy_mock.go | 32 +++++++++++- 10 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 .mockery.yaml create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 mk/mockery.mk diff --git a/.gitignore b/.gitignore index 436757f..38ffdad 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ Containerfile compose_files/pulp/pulp-oci-images #config -configs/config.yaml \ No newline at end of file +configs/config.yaml + +#mockery binaries +bin diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..91c98d2 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,13 @@ +with-expecter: false +inpackage: True +dir: "./pkg/tangy" +mockname: "Mock{{.InterfaceName}}" +outpkg: "{{.PackageName}}" +filename: "{{.InterfaceName}}_mock.go" +all: True +disable-version-string: True +packages: + github.com/content-services/tang: + config: + filename: "tangy_mock.go" + recursive: True diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1b8aa88 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f25ce44 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.mergeEditor": false +} diff --git a/internal/test/integration/rpm_test.go b/internal/test/integration/rpm_test.go index e7a1cc6..4f56d6c 100644 --- a/internal/test/integration/rpm_test.go +++ b/internal/test/integration/rpm_test.go @@ -31,6 +31,8 @@ const testRepoURL = "https://rverdile.fedorapeople.org/dummy-repos/comps/repo1/" const testRepoURLTwo = "https://rverdile.fedorapeople.org/dummy-repos/comps/repo2/" const testRepoNameWithErrata = "multiple-errata" const testRepoURLWithErrata = "https://stephenw.fedorapeople.org/fakerepos/multiple_errata/" +const rpmNameWithModule = "rpm-with-modules" +const rpmUrlWithModule = "https://fixtures.pulpproject.org/rpm-with-modules-modified/" func (r *RpmSuite) CreateTestRepository(t *testing.T, repoName string, repoUrl string) { _, err := r.client.LookupOrCreateDomain(r.domainName) @@ -372,6 +374,54 @@ func (r *RpmSuite) TestRpmRepositoryVersionErrataListSort() { assert.Equal(r.T(), total, 6) } +func (r *RpmSuite) TestRpmRepositoryVersionModuleStreamsList() { + resp, err := r.client.GetRpmRepositoryByName(r.domainName, testRepoName) + require.NoError(r.T(), err) + firstVersionHref := resp.LatestVersionHref + require.NotNil(r.T(), firstVersionHref) + + // expect empty + emptyList, err := r.tangy.RpmRepositoryVersionModuleStreamsList(context.Background(), []string{*firstVersionHref}, tangy.ModuleStreamListFilters{}, "name ASC") + require.NoError(r.T(), err) + assert.Empty(r.T(), emptyList) + + r.CreateTestRepository(r.T(), rpmNameWithModule, rpmUrlWithModule) + resp, err = r.client.GetRpmRepositoryByName(r.domainName, rpmNameWithModule) + + require.NoError(r.T(), err) + require.NotNil(r.T(), resp.LatestVersionHref) + firstVersionHref = resp.LatestVersionHref + + // Expect populated + singleList, err := r.tangy.RpmRepositoryVersionModuleStreamsList(context.Background(), []string{*firstVersionHref}, tangy.ModuleStreamListFilters{}, "anything!") + + assert.Equal(r.T(), singleList[0].Name, "duck") + require.NoError(r.T(), err) + assert.NotEmpty(r.T(), singleList) + + // Test search + singleList, err = r.tangy.RpmRepositoryVersionModuleStreamsList(context.Background(), []string{*firstVersionHref}, tangy.ModuleStreamListFilters{Search: "Duck"}, "") + require.NoError(r.T(), err) + assert.NotEmpty(r.T(), singleList) + + // Test package name list filter + singleList, err = r.tangy.RpmRepositoryVersionModuleStreamsList(context.Background(), []string{*firstVersionHref}, tangy.ModuleStreamListFilters{RpmNames: []string{"walrus", "kangaroo"}}, "anything DesC") + require.NoError(r.T(), err) + assert.Equal(r.T(), singleList[0].Name, "walrus") + assert.NotEmpty(r.T(), singleList) + + // Test package name list filter + singleList, err = r.tangy.RpmRepositoryVersionModuleStreamsList(context.Background(), []string{*firstVersionHref}, tangy.ModuleStreamListFilters{RpmNames: []string{"walrus", "kangaroo"}}, "name ASC") + require.NoError(r.T(), err) + assert.Equal(r.T(), singleList[0].Name, "kangaroo") + assert.NotEmpty(r.T(), singleList) + + // Confirm no error on not found rpm name + singleList, err = r.tangy.RpmRepositoryVersionModuleStreamsList(context.Background(), []string{*firstVersionHref}, tangy.ModuleStreamListFilters{RpmNames: []string{"banana"}}, "") + require.NoError(r.T(), err) + assert.Empty(r.T(), singleList) +} + func (r *RpmSuite) TestRpmRepositoryVersionPackageListNameFilter() { resp, err := r.client.GetRpmRepositoryByName(r.domainName, testRepoName) require.NoError(r.T(), err) diff --git a/mk/includes.mk b/mk/includes.mk index afe2d9c..75b1f8b 100644 --- a/mk/includes.mk +++ b/mk/includes.mk @@ -22,3 +22,4 @@ include mk/variables.mk include mk/compose.mk include mk/help.mk include mk/test.mk +include mk/mockery.mk diff --git a/mk/mockery.mk b/mk/mockery.mk new file mode 100644 index 0000000..5d8ded7 --- /dev/null +++ b/mk/mockery.mk @@ -0,0 +1,17 @@ +## +# Set of rules to manage podman-compose +# +# Requires 'mk/variables.mk' +## + +MOCKERY_VERSION := $(shell curl -L https://api.github.com/repos/vektra/mockery/releases/latest | jq --raw-output .tag_name | sed 's/^v//') + +GO_OUTPUT ?= $(PROJECT_DIR)/bin + +$(GO_OUTPUT)/mockery: ## Install mockery locally on your GO_OUTPUT (./release) directory + mkdir -p $(GO_OUTPUT) && \ + curl -sSfL https://github.com/vektra/mockery/releases/download/v$(MOCKERY_VERSION)/mockery_$(MOCKERY_VERSION)_$(shell uname -s)_$(shell uname -m).tar.gz | tar -xz -C $(GO_OUTPUT) mockery + +.PHONY: mock ## Run mockery +mock: $(GO_OUTPUT)/mockery ## Install mockery if it isn't already in ./release directory and regenerate mocks + $(GO_OUTPUT)/mockery diff --git a/pkg/tangy/interface.go b/pkg/tangy/interface.go index 4096b1d..3924fe0 100644 --- a/pkg/tangy/interface.go +++ b/pkg/tangy/interface.go @@ -50,12 +50,12 @@ type tangyImpl struct { logger Logger } -//go:generate mockery --name Tangy --filename tangy_mock.go --inpackage type Tangy interface { RpmRepositoryVersionPackageSearch(ctx context.Context, hrefs []string, search string, limit int) ([]RpmPackageSearch, error) RpmRepositoryVersionPackageGroupSearch(ctx context.Context, hrefs []string, search string, limit int) ([]RpmPackageGroupSearch, error) RpmRepositoryVersionEnvironmentSearch(ctx context.Context, hrefs []string, search string, limit int) ([]RpmEnvironmentSearch, error) RpmRepositoryVersionPackageList(ctx context.Context, hrefs []string, filterOpts RpmListFilters, pageOpts PageOptions) ([]RpmListItem, int, error) + RpmRepositoryVersionModuleStreamsList(ctx context.Context, hrefs []string, filterOpts ModuleStreamListFilters, sortBy string) ([]ModuleStreams, error) RpmRepositoryVersionErrataList(ctx context.Context, hrefs []string, filterOpts ErrataListFilters, pageOpts PageOptions) ([]ErrataListItem, int, error) Close() } diff --git a/pkg/tangy/rpm.go b/pkg/tangy/rpm.go index 3699d0f..d8d9580 100644 --- a/pkg/tangy/rpm.go +++ b/pkg/tangy/rpm.go @@ -47,6 +47,16 @@ type RpmListItem struct { Summary string // The summary of the rpm } +type ModuleStreams struct { + Name string // Name of the module + Stream string // Module stream version + Version string // The version of the rpm + Context string // Context of the module + Arch string // The Architecture of the rpm + Description string // Module description + Profiles map[string][]string // Module profile data +} + type ErrataListItem struct { Id string ErrataId string @@ -70,6 +80,11 @@ type RpmListFilters struct { Name string } +type ModuleStreamListFilters struct { + RpmNames []string + Search string +} + type ErrataListFilters struct { Search string Type []string @@ -353,6 +368,66 @@ func (t *tangyImpl) RpmRepositoryVersionErrataList(ctx context.Context, hrefs [] return errata, countTotal, nil } +// RpmRepositoryVersionModuleStreamsList List Modules streams within a repository version, with pagination, search and an optional name filter +func (t *tangyImpl) RpmRepositoryVersionModuleStreamsList(ctx context.Context, hrefs []string, filterOpts ModuleStreamListFilters, sortBy string) ([]ModuleStreams, error) { + if len(hrefs) == 0 { + return []ModuleStreams{}, nil + } + + conn, err := t.pool.Acquire(ctx) + if err != nil { + return nil, err + } + defer conn.Release() + + repoVerMap, err := parseRepositoryVersionHrefsMap(hrefs) + if err != nil { + return nil, fmt.Errorf("error parsing repository version hrefs: %w", err) + } + + orderBy := "rp.name" + + if strings.Contains(strings.ToLower(sortBy), "desc") { + orderBy += " DESC" + } else { + orderBy += " ASC" + } + + orderBy += ", rp.stream, rp.version" + + args := pgx.NamedArgs{ + "nameFilter": "%" + filterOpts.Search + "%", + "rpm_names": filterOpts.RpmNames, + } + query := `Select distinct on (rp.name, rp.stream) rp.name, rp.stream, rp.version, rp.profiles, rp.context, rp.arch, rp.description FROM rpm_modulemd rp + INNER JOIN rpm_modulemd_packages rmp on rmp.modulemd_id = rp.content_ptr_id + INNER JOIN rpm_package pack on pack.content_ptr_id = rmp.package_id ` + + innerUnion := contentIdsInVersions(repoVerMap, &args) + + rpmNameFilter := "" + + if len(filterOpts.RpmNames) > 0 { + rpmNameFilter = " AND pack.name = ANY(@rpm_names)" + } + + filter := rpmNameFilter + " AND rp.name ILIKE CONCAT( '%', @nameFilter::text, '%') " + + rows, err := conn.Query(ctx, query+innerUnion+filter+" ORDER BY "+orderBy+" LIMIT 5000", args) + + if err != nil { + return nil, err + } + + moduleStreams, err := pgx.CollectRows(rows, pgx.RowToStructByName[ModuleStreams]) + + if err != nil { + return nil, err + } + + return moduleStreams, nil +} + // RpmRepositoryVersionPackageList List RPMs within a repository version, with pagination, and an optional name filter func (t *tangyImpl) RpmRepositoryVersionPackageList(ctx context.Context, hrefs []string, filterOpts RpmListFilters, pageOpts PageOptions) ([]RpmListItem, int, error) { if len(hrefs) == 0 { diff --git a/pkg/tangy/tangy_mock.go b/pkg/tangy/tangy_mock.go index e3fad77..f461c88 100644 --- a/pkg/tangy/tangy_mock.go +++ b/pkg/tangy/tangy_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.2. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package tangy @@ -85,6 +85,36 @@ func (_m *MockTangy) RpmRepositoryVersionErrataList(ctx context.Context, hrefs [ return r0, r1, r2 } +// RpmRepositoryVersionModuleStreamsList provides a mock function with given fields: ctx, hrefs, filterOpts, sortBy +func (_m *MockTangy) RpmRepositoryVersionModuleStreamsList(ctx context.Context, hrefs []string, filterOpts ModuleStreamListFilters, sortBy string) ([]ModuleStreams, error) { + ret := _m.Called(ctx, hrefs, filterOpts, sortBy) + + if len(ret) == 0 { + panic("no return value specified for RpmRepositoryVersionModuleStreamsList") + } + + var r0 []ModuleStreams + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string, ModuleStreamListFilters, string) ([]ModuleStreams, error)); ok { + return rf(ctx, hrefs, filterOpts, sortBy) + } + if rf, ok := ret.Get(0).(func(context.Context, []string, ModuleStreamListFilters, string) []ModuleStreams); ok { + r0 = rf(ctx, hrefs, filterOpts, sortBy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]ModuleStreams) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string, ModuleStreamListFilters, string) error); ok { + r1 = rf(ctx, hrefs, filterOpts, sortBy) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // RpmRepositoryVersionPackageGroupSearch provides a mock function with given fields: ctx, hrefs, search, limit func (_m *MockTangy) RpmRepositoryVersionPackageGroupSearch(ctx context.Context, hrefs []string, search string, limit int) ([]RpmPackageGroupSearch, error) { ret := _m.Called(ctx, hrefs, search, limit)