From 2b98d5da8bd27eac0a4a21c16bdd96a1d298078f Mon Sep 17 00:00:00 2001 From: rverdile Date: Wed, 7 May 2025 16:09:00 -0400 Subject: [PATCH] HMS-5913: add package dates from RHEL lifecycle Adds package streams info to package_sources. Pulls package stream info from the roadmap API and adds it to package sources if the RPM name matches. Also adds RHEL EoL as the end date for any package source that is not in the roadmap API. Uses the end date for the latest released version of the matching RHEL major version. --- configs/config.yaml.example | 1 + pkg/cache/cache.go | 3 + pkg/cache/cache_mock.go | 35 ++++ pkg/cache/noop.go | 9 + pkg/cache/redis.go | 21 ++- pkg/clients/roadmap_client/client.go | 2 + pkg/clients/roadmap_client/roadmap.go | 91 ++++++++++ .../roadmap_client/roadmap_client_mock.go | 65 +++++++ pkg/config/config.go | 2 + pkg/dao/rpms.go | 171 ++++++++++++++---- pkg/dao/rpms_test.go | 113 ++++++++++++ 11 files changed, 480 insertions(+), 33 deletions(-) diff --git a/configs/config.yaml.example b/configs/config.yaml.example index 2b0bf6eda..3b3acd35c 100644 --- a/configs/config.yaml.example +++ b/configs/config.yaml.example @@ -134,6 +134,7 @@ clients: rbac: 1m pulp_content_path: 1h subscription_check: 1h + roadmap: 24h feature_service: server: #https://feature.stage.api.redhat.com/features/v1 client_cert: diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index ade0891a7..9daeaf635 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -28,6 +28,9 @@ type Cache interface { GetRoadmapAppstreams(ctx context.Context) ([]byte, error) SetRoadmapAppstreams(ctx context.Context, roadmapAppstreamsResponse []byte) + + GetRoadmapRhelLifecycle(ctx context.Context) ([]byte, error) + SetRoadmapRhelLifecycle(ctx context.Context, rhelLifecyleResponse []byte) } func Initialize() Cache { diff --git a/pkg/cache/cache_mock.go b/pkg/cache/cache_mock.go index ce7bd8aed..7913e7e70 100644 --- a/pkg/cache/cache_mock.go +++ b/pkg/cache/cache_mock.go @@ -135,6 +135,36 @@ func (_m *MockCache) GetRoadmapAppstreams(ctx context.Context) ([]byte, error) { return r0, r1 } +// GetRoadmapRhelLifecycle provides a mock function with given fields: ctx +func (_m *MockCache) GetRoadmapRhelLifecycle(ctx context.Context) ([]byte, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetRoadmapRhelLifecycle") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]byte, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []byte); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetSubscriptionCheck provides a mock function with given fields: ctx func (_m *MockCache) GetSubscriptionCheck(ctx context.Context) (*api.SubscriptionCheckResponse, error) { ret := _m.Called(ctx) @@ -224,6 +254,11 @@ func (_m *MockCache) SetRoadmapAppstreams(ctx context.Context, roadmapAppstreams _m.Called(ctx, roadmapAppstreamsResponse) } +// SetRoadmapRhelLifecycle provides a mock function with given fields: ctx, rhelLifecyleResponse +func (_m *MockCache) SetRoadmapRhelLifecycle(ctx context.Context, rhelLifecyleResponse []byte) { + _m.Called(ctx, rhelLifecyleResponse) +} + // SetSubscriptionCheck provides a mock function with given fields: ctx, response func (_m *MockCache) SetSubscriptionCheck(ctx context.Context, response api.SubscriptionCheckResponse) error { ret := _m.Called(ctx, response) diff --git a/pkg/cache/noop.go b/pkg/cache/noop.go index 01cbd917f..047160221 100644 --- a/pkg/cache/noop.go +++ b/pkg/cache/noop.go @@ -64,3 +64,12 @@ func (c *noOpCache) GetRoadmapAppstreams(ctx context.Context) ([]byte, error) { // SetRoadmapAppstreams a NoOp version to store cached roadmap appstreams check func (c *noOpCache) SetRoadmapAppstreams(ctx context.Context, response []byte) { } + +// GetRoadmapRhelLifecycle a NoOp version to fetch a cached roadmap rhel lifecycle check +func (c *noOpCache) GetRoadmapRhelLifecycle(ctx context.Context) ([]byte, error) { + return nil, NotFound +} + +// SetRoadmapRhelLifecycle a NoOp version to store cached roadmap rhel lifecycle check +func (c *noOpCache) SetRoadmapRhelLifecycle(ctx context.Context, response []byte) { +} diff --git a/pkg/cache/redis.go b/pkg/cache/redis.go index f574bf392..a76155088 100644 --- a/pkg/cache/redis.go +++ b/pkg/cache/redis.go @@ -61,6 +61,11 @@ func roadmapAppstreamsKey(ctx context.Context) string { return fmt.Sprintf("roadmap-appstreams:%v", identity.Identity.OrgID) } +func roadmapRhelLifecycleKey(ctx context.Context) string { + identity := identity.GetIdentity(ctx) + return fmt.Sprintf("roadmap-rhel-lifecycle:%v", identity.Identity.OrgID) +} + // GetAccessList uses the request context to read user information, and then tries to retrieve the role AccessList from the cache func (c *redisCache) GetAccessList(ctx context.Context) (rbac.AccessList, error) { accessList := rbac.AccessList{} @@ -184,7 +189,21 @@ func (c *redisCache) GetRoadmapAppstreams(ctx context.Context) ([]byte, error) { func (c *redisCache) SetRoadmapAppstreams(ctx context.Context, response []byte) { key := roadmapAppstreamsKey(ctx) - c.client.Set(ctx, key, string(response), config.Get().Clients.Redis.Expiration.SubscriptionCheck) + c.client.Set(ctx, key, string(response), config.Get().Clients.Redis.Expiration.Roadmap) +} + +func (c *redisCache) GetRoadmapRhelLifecycle(ctx context.Context) ([]byte, error) { + key := roadmapRhelLifecycleKey(ctx) + buf, err := c.get(ctx, key) + if err != nil { + return nil, fmt.Errorf("redis get error: %w", err) + } + return buf, nil +} + +func (c *redisCache) SetRoadmapRhelLifecycle(ctx context.Context, response []byte) { + key := roadmapRhelLifecycleKey(ctx) + c.client.Set(ctx, key, string(response), config.Get().Clients.Redis.Expiration.Roadmap) } func (c *redisCache) get(ctx context.Context, key string) ([]byte, error) { diff --git a/pkg/clients/roadmap_client/client.go b/pkg/clients/roadmap_client/client.go index 9c8930fab..4ca171c70 100644 --- a/pkg/clients/roadmap_client/client.go +++ b/pkg/clients/roadmap_client/client.go @@ -13,6 +13,8 @@ import ( type RoadmapClient interface { GetAppstreams(ctx context.Context) (AppstreamsResponse, int, error) + GetRhelLifecycle(ctx context.Context) (LifecycleResponse, int, error) + GetRhelLifecycleForLatestMajorVersions(ctx context.Context) (map[int]LifecycleEntity, error) } type roadmapClient struct { diff --git a/pkg/clients/roadmap_client/roadmap.go b/pkg/clients/roadmap_client/roadmap.go index 49a7d43bf..400eb3690 100644 --- a/pkg/clients/roadmap_client/roadmap.go +++ b/pkg/clients/roadmap_client/roadmap.go @@ -35,6 +35,18 @@ type AppstreamEntity struct { Impl string `json:"impl"` } +type LifecycleResponse struct { + Data []LifecycleEntity `json:"data"` +} + +type LifecycleEntity struct { + Name string `json:"name"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Major int `json:"major"` + Minor int `json:"minor"` +} + func encodedIdentity(ctx context.Context) (string, error) { id := identity.GetIdentity(ctx) jsonIdentity, err := json.Marshal(id) @@ -107,3 +119,82 @@ func (rc roadmapClient) GetAppstreams(ctx context.Context) (AppstreamsResponse, return appStreamResp, statusCode, nil } + +func (rc roadmapClient) GetRhelLifecycle(ctx context.Context) (LifecycleResponse, int, error) { + statusCode := http.StatusInternalServerError + server := config.Get().Clients.Roadmap.Server + var err error + var lifecycleResponse LifecycleResponse + var body []byte + + appstreams, err := rc.cache.GetRoadmapRhelLifecycle(ctx) + if err != nil && !errors.Is(err, cache.NotFound) { + log.Error().Err(err).Msg("GetAppstreams - error reading from cache") + } + if appstreams != nil { + err = json.Unmarshal(appstreams, &lifecycleResponse) + if err != nil { + return LifecycleResponse{}, statusCode, fmt.Errorf("error during unmarshal response body: %w", err) + } + return lifecycleResponse, http.StatusOK, nil + } + + fullPath := server + "/lifecycle/rhel" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullPath, nil) + if err != nil { + return LifecycleResponse{}, statusCode, fmt.Errorf("error building request: %w", err) + } + + if config.Get().Clients.Roadmap.Username != "" && config.Get().Clients.Roadmap.Password != "" { + req.SetBasicAuth(config.Get().Clients.Roadmap.Username, config.Get().Clients.Roadmap.Password) + } + + encodedXRHID, err := encodedIdentity(ctx) + if err != nil { + return LifecycleResponse{}, statusCode, fmt.Errorf("error getting encoded XRHID: %w", err) + } + req.Header.Set(api.IdentityHeader, encodedXRHID) + + resp, err := rc.client.Do(req) + if resp != nil { + defer resp.Body.Close() + + body, err = io.ReadAll(resp.Body) + if err != nil { + return LifecycleResponse{}, statusCode, fmt.Errorf("error reading response body: %w", err) + } + if resp.StatusCode != 0 { + statusCode = resp.StatusCode + } + } + if err != nil { + return LifecycleResponse{}, statusCode, fmt.Errorf("error sending request: %w", err) + } + if statusCode < 200 || statusCode >= 300 { + return LifecycleResponse{}, statusCode, fmt.Errorf("unexpected status code with body: %s", string(body)) + } + + rc.cache.SetRoadmapRhelLifecycle(ctx, body) + + err = json.Unmarshal(body, &lifecycleResponse) + if err != nil { + return LifecycleResponse{}, statusCode, fmt.Errorf("error during unmarshal response body: %w", err) + } + + return lifecycleResponse, statusCode, nil +} + +func (rc roadmapClient) GetRhelLifecycleForLatestMajorVersions(ctx context.Context) (map[int]LifecycleEntity, error) { + lifecycleResp, _, err := rc.GetRhelLifecycle(ctx) + if err != nil { + return nil, err + } + + rhelEolMap := make(map[int]LifecycleEntity) + for _, item := range lifecycleResp.Data { + if existing, found := rhelEolMap[item.Major]; !found || (item.Minor > existing.Minor) { + rhelEolMap[item.Major] = item + } + } + return rhelEolMap, nil +} diff --git a/pkg/clients/roadmap_client/roadmap_client_mock.go b/pkg/clients/roadmap_client/roadmap_client_mock.go index ce3e7888b..21af30321 100644 --- a/pkg/clients/roadmap_client/roadmap_client_mock.go +++ b/pkg/clients/roadmap_client/roadmap_client_mock.go @@ -48,6 +48,71 @@ func (_m *MockRoadmapClient) GetAppstreams(ctx context.Context) (AppstreamsRespo return r0, r1, r2 } +// GetRhelLifecycle provides a mock function with given fields: ctx +func (_m *MockRoadmapClient) GetRhelLifecycle(ctx context.Context) (LifecycleResponse, int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetRhelLifecycle") + } + + var r0 LifecycleResponse + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context) (LifecycleResponse, int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) LifecycleResponse); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(LifecycleResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context) int); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(context.Context) error); ok { + r2 = rf(ctx) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetRhelLifecycleForLatestMajorVersions provides a mock function with given fields: ctx +func (_m *MockRoadmapClient) GetRhelLifecycleForLatestMajorVersions(ctx context.Context) (map[int]LifecycleEntity, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetRhelLifecycleForLatestMajorVersions") + } + + var r0 map[int]LifecycleEntity + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (map[int]LifecycleEntity, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) map[int]LifecycleEntity); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[int]LifecycleEntity) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewMockRoadmapClient creates a new instance of MockRoadmapClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockRoadmapClient(t interface { diff --git a/pkg/config/config.go b/pkg/config/config.go index f91775d44..727be8956 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -184,6 +184,7 @@ type Expiration struct { Rbac time.Duration `mapstructure:"rbac"` PulpContentPath time.Duration `mapstructure:"pulp_content_path"` SubscriptionCheck time.Duration `mapstructure:"subscription_check"` + Roadmap time.Duration `mapstructure:"roadmap"` } type Sentry struct { @@ -323,6 +324,7 @@ func setDefaults(v *viper.Viper) { v.SetDefault("clients.redis.expiration.rbac", 1*time.Minute) v.SetDefault("clients.redis.expiration.pulp_content_path", 1*time.Hour) v.SetDefault("clients.redis.expiration.subscription_check", 1*time.Hour) + v.SetDefault("clients.redis.expiration.roadmap", 24*time.Hour) v.SetDefault("clients.feature_service.server", "") v.SetDefault("clients.feature_service.client_cert", "") diff --git a/pkg/dao/rpms.go b/pkg/dao/rpms.go index 86990101a..165815f7f 100644 --- a/pkg/dao/rpms.go +++ b/pkg/dao/rpms.go @@ -3,6 +3,7 @@ package dao import ( "context" "fmt" + "strconv" "strings" "time" @@ -14,6 +15,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/utils" "github.com/content-services/tang/pkg/tangy" "github.com/content-services/yummy/pkg/yum" + "github.com/lib/pq" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -152,7 +154,7 @@ func popularRepoUrls() []string { return urls } -func (r rpmDaoImpl) Search(ctx context.Context, orgID string, request api.ContentUnitSearchRequest) ([]api.SearchRpmResponse, error) { +func (r *rpmDaoImpl) Search(ctx context.Context, orgID string, request api.ContentUnitSearchRequest) ([]api.SearchRpmResponse, error) { // Retrieve the repository id list if orgID == "" { return nil, fmt.Errorf("orgID cannot be an empty string") @@ -195,7 +197,7 @@ func (r rpmDaoImpl) Search(ctx context.Context, orgID string, request api.Conten // https://github.com/go-gorm/gorm/issues/5318 dataResponse := []api.SearchRpmResponse{} db := r.db.WithContext(ctx). - Select("DISTINCT ON(rpms.name) rpms.name as package_name", "rpms.summary"). + Select("DISTINCT ON(rpms.name) rpms.name as package_name", "rpms.summary", "rpms.uuid"). Table(models.TableNameRpm). Joins("inner join repositories_rpms on repositories_rpms.rpm_uuid = rpms.uuid"). Where("repositories_rpms.repository_uuid in (?)", readableRepos) @@ -216,7 +218,7 @@ func (r rpmDaoImpl) Search(ctx context.Context, orgID string, request api.Conten // Add module info to the response if requested if request.IncludePackageSources && len(dataResponse) > 0 { - err := r.addModuleInfo(ctx, dataResponse, repoUUIDs) + err := r.addPackageSources(ctx, dataResponse, repoUUIDs) if err != nil { return nil, err } @@ -225,7 +227,7 @@ func (r rpmDaoImpl) Search(ctx context.Context, orgID string, request api.Conten return dataResponse, nil } -func (r *rpmDaoImpl) addModuleInfo(ctx context.Context, rpmResponse []api.SearchRpmResponse, repoUUIDs []string) error { +func (r *rpmDaoImpl) addPackageSources(ctx context.Context, rpmResponse []api.SearchRpmResponse, repoUUIDs []string) error { var moduleInfo []models.ModuleStream pkgNames := make([]string, len(rpmResponse)) @@ -245,8 +247,12 @@ func (r *rpmDaoImpl) addModuleInfo(ctx context.Context, rpmResponse []api.Search return err } + var unmatchedRPMs []string for i, rpm := range rpmResponse { - var matchedModules []api.PackageSourcesResponse + var packageSources []api.PackageSourcesResponse + var pkgStreamFound bool + var moduleStreamFound bool + for _, m := range moduleInfo { if utils.Contains(m.PackageNames, rpm.PackageName) { module := api.PackageSourcesResponse{ @@ -258,52 +264,153 @@ func (r *rpmDaoImpl) addModuleInfo(ctx context.Context, rpmResponse []api.Search Version: m.Version, Description: m.Description, } - matchedModules = append(matchedModules, module) + packageSources = append(packageSources, module) } } - if len(matchedModules) > 0 { - rpmResponse[i].PackageSources = matchedModules - } else { - rpmResponse[i].PackageSources = []api.PackageSourcesResponse{ - { - Type: "package", - }, + + packageSources, pkgStreamFound, err = r.addRoadmapPackageStreams(ctx, rpm, packageSources) + if err != nil { + return err + } + + packageSources, moduleStreamFound, err = r.addRoadmapModuleStreams(ctx, packageSources) + if err != nil { + return err + } + + if !pkgStreamFound && !moduleStreamFound { + unmatchedRPMs = append(unmatchedRPMs, rpm.PackageName) + } + + rpmResponse[i].PackageSources = packageSources + } + + // If any RPMs not found in roadmap API, get lifecycle information of highest matching RHEL version + // and add RHEL lifecycle information as package source + if len(unmatchedRPMs) > 0 { + packageSourcesMap, err := r.addRoadmapRhelEol(ctx, unmatchedRPMs, repoUUIDs) + if err != nil { + return err + } + + for i, rpm := range rpmResponse { + if packageSource, found := packageSourcesMap[rpm.PackageName]; found { + rpmResponse[i].PackageSources = append(rpmResponse[i].PackageSources, packageSource) } } } + return nil +} - err = r.addLifecycleInfo(ctx, rpmResponse) +// addRoadmapPackageStreams calls the roadmap API to add the package streams associated to the given RPM to packageSources. Also adds +// the lifecycle start_date and end_date +func (r *rpmDaoImpl) addRoadmapPackageStreams(ctx context.Context, rpm api.SearchRpmResponse, packageSources []api.PackageSourcesResponse) ([]api.PackageSourcesResponse, bool, error) { + if !config.RoadmapConfigured() { + return packageSources, false, nil + } + + appstreams, _, err := r.roadmapClient.GetAppstreams(ctx) if err != nil { - return fmt.Errorf("error adding lifecycle info: %w", err) + return packageSources, false, err + } + + var streamFound bool + packageSourcesUpdated := packageSources + for _, appstreamEntity := range appstreams.Data { + if appstreamEntity.Name == rpm.PackageName && appstreamEntity.Impl == "package" { + packageStream := api.PackageSourcesResponse{ + Type: "package", + Name: rpm.PackageName, + Stream: appstreamEntity.Stream, + StartDate: appstreamEntity.StartDate, + EndDate: appstreamEntity.EndDate, + } + packageSourcesUpdated = append(packageSourcesUpdated, packageStream) + streamFound = true + } } - return nil + + return packageSourcesUpdated, streamFound, nil } -func (r *rpmDaoImpl) addLifecycleInfo(ctx context.Context, rpmResponse []api.SearchRpmResponse) error { +// addRoadmapModuleStreams calls the roadmap API to add the module stream lifecycle information to the module +// streams in packageSources +func (r *rpmDaoImpl) addRoadmapModuleStreams(ctx context.Context, packageSources []api.PackageSourcesResponse) ([]api.PackageSourcesResponse, bool, error) { if !config.RoadmapConfigured() { - return nil + return packageSources, false, nil } appstreams, _, err := r.roadmapClient.GetAppstreams(ctx) if err != nil { - return err + return packageSources, false, err + } + + var streamFound bool + packageSourcesUpdated := packageSources + for _, appstreamEntity := range appstreams.Data { + for j, source := range packageSourcesUpdated { + if appstreamEntity.Name == source.Name && + appstreamEntity.Stream == source.Stream && + appstreamEntity.Impl == "dnf_module" { + packageSourcesUpdated[j].StartDate = appstreamEntity.StartDate + packageSourcesUpdated[j].EndDate = appstreamEntity.EndDate + streamFound = true + } + } } + return packageSourcesUpdated, streamFound, nil +} - for i := 0; i < len(rpmResponse); i++ { - for j := 0; j < len(rpmResponse[i].PackageSources); j++ { - for _, appstreamEntity := range appstreams.Data { - packageSource := &rpmResponse[i].PackageSources[j] +func (r *rpmDaoImpl) addRoadmapRhelEol(ctx context.Context, unmatchedRPMs []string, repoUUIDs []string) (map[string]api.PackageSourcesResponse, error) { + type queryRes struct { + Name string + Versions pq.StringArray `gorm:"type:text[]"` + } + var res []queryRes + err := r.db.Debug().WithContext(ctx).Table(models.TableNameRpm). + Select("rpms.name, repository_configurations.versions"). + Joins("INNER JOIN repositories_rpms ON rpms.uuid = repositories_rpms.rpm_uuid"). + Joins("INNER JOIN repository_configurations ON repository_configurations.repository_uuid = repositories_rpms.repository_uuid"). + Where("repository_configurations.org_id = ? AND rpms.name in ? AND repository_configurations.repository_uuid in ?", config.RedHatOrg, unmatchedRPMs, repoUUIDs). + Find(&res).Error - if appstreamEntity.Name == packageSource.Name && - appstreamEntity.Stream == packageSource.Stream && - appstreamEntity.Impl == "dnf_module" { - packageSource.StartDate = appstreamEntity.StartDate - packageSource.EndDate = appstreamEntity.EndDate - } + if err != nil { + return nil, err + } + + packageMap := make(map[string]int) + for _, pkg := range res { + if len(pkg.Versions) == 0 { + continue + } + versionInt, err := strconv.Atoi(pkg.Versions[0]) + if err != nil { + return nil, err + } + if existingVersion, found := packageMap[pkg.Name]; found { + if versionInt > existingVersion { + packageMap[pkg.Name] = versionInt } + } else { + packageMap[pkg.Name] = versionInt } } - return nil + + rhelEolMap, err := r.roadmapClient.GetRhelLifecycleForLatestMajorVersions(ctx) + if err != nil { + return nil, err + } + + packageSourcesMap := make(map[string]api.PackageSourcesResponse) + for name, version := range packageMap { + packageSource := api.PackageSourcesResponse{ + Type: "package", + Name: name, + EndDate: rhelEolMap[version].EndDate, + } + packageSourcesMap[name] = packageSource + } + return packageSourcesMap, nil } func readableRepositoryQuery(dbWithContext *gorm.DB, orgID string, urls []string, uuids []string) *gorm.DB { @@ -567,14 +674,14 @@ func (r *rpmDaoImpl) SearchSnapshotRpms(ctx context.Context, orgId string, reque }) } - // Add module info to the response if requested + // Add package sources to the response if requested if request.IncludePackageSources && len(response) > 0 { var repoUUIDs []string err = r.fetchRepoUUIDsForSnapshots(ctx, orgId, request.UUIDs, &repoUUIDs) if err != nil { return nil, fmt.Errorf("failed to query the db for repo uuids from snapshots: %w", res.Error) } - err = r.addModuleInfo(ctx, response, repoUUIDs) + err = r.addPackageSources(ctx, response, repoUUIDs) if err != nil { return nil, err } diff --git a/pkg/dao/rpms_test.go b/pkg/dao/rpms_test.go index 3f3d07063..ca20fd90d 100644 --- a/pkg/dao/rpms_test.go +++ b/pkg/dao/rpms_test.go @@ -15,6 +15,7 @@ import ( "github.com/content-services/tang/pkg/tangy" "github.com/content-services/yummy/pkg/yum" "github.com/google/uuid" + "github.com/lib/pq" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -225,6 +226,26 @@ func (s *RpmSuite) TestRpmSearch() { s.prepModule(uuids[0], "demo-package", "0", "001") s.prepModule(uuids[0], "demo-package", "1", "456") + redHatRepo := repoPublicTest.DeepCopy() + redHatRepo.URL = "https://www.public.redhat.com" + urls = append(urls, redHatRepo.URL) + if err := s.tx.Create(redHatRepo).Error; err != nil { + s.FailNow("Preparing Repository record: %w", err) + } + + redhatRepoConfig := repoConfigTest1.DeepCopy() + redhatRepoConfig.OrgID = config.RedHatOrg + redhatRepoConfig.Name = "Demo Redhat Repository Config" + redhatRepoConfig.RepositoryUUID = redHatRepo.Base.UUID + redhatRepoConfig.Versions = pq.StringArray{config.El9} + if err := s.tx.Create(redhatRepoConfig).Error; err != nil { + s.FailNow("Preparing RepositoryConfiguration record: %w", err) + } + + rpmRh := repoRpmTest1.DeepCopy() + rpmRh.Name = "test-package-rh" + s.addRepositoryRpm(redHatRepo.URL, *rpmRh) + config.Get().Clients.Roadmap.Server = "http://example.com" expectedRoadmap := roadmap_client.AppstreamsResponse{ Meta: roadmap_client.Meta{}, @@ -243,10 +264,41 @@ func (s *RpmSuite) TestRpmSearch() { EndDate: "02-02-02", Impl: "dnf_module", }, + { + Name: "demo-package", + StartDate: "01-01-01", + EndDate: "02-02-02", + Impl: "package", + }, }, } s.mockRoadmapClient.On("GetAppstreams", context.Background()).Return(expectedRoadmap, 0, nil) + expectedLifecycle := map[int]roadmap_client.LifecycleEntity{ + 10: { + Name: "rhel", + StartDate: "01-01-01", + EndDate: "02-02-02", + Major: 10, + Minor: 2, + }, + 9: { + Name: "rhel", + StartDate: "01-01-01", + EndDate: "02-02-02", + Major: 9, + Minor: 2, + }, + 8: { + Name: "rhel", + StartDate: "01-01-01", + EndDate: "02-02-02", + Major: 8, + Minor: 2, + }, + } + s.mockRoadmapClient.On("GetRhelLifecycleForLatestMajorVersions", context.Background()).Return(expectedLifecycle, nil) + // Test Cases type TestCaseGiven struct { orgId string @@ -551,6 +603,39 @@ func (s *RpmSuite) TestRpmSearch() { StartDate: "01-01-01", EndDate: "02-02-02", }, + { + Type: "package", + Name: "demo-package", + StartDate: "01-01-01", + EndDate: "02-02-02", + }, + }, + }, + }, + }, + { + name: "Package sources correctly get rhel eol", + given: TestCaseGiven{ + orgId: orgIDTest, + input: api.ContentUnitSearchRequest{ + URLs: []string{ + urls[3], + }, + Search: "test-package-rh", + Limit: utils.Ptr(50), + IncludePackageSources: true, + }, + }, + expected: []api.SearchRpmResponse{ + { + PackageName: "test-package-rh", + Summary: "Test package summary", + PackageSources: []api.PackageSourcesResponse{ + { + Type: "package", + Name: "test-package-rh", + EndDate: "02-02-02", + }, }, }, }, @@ -1034,6 +1119,34 @@ func (s *RpmSuite) TestSearchRpmsForSnapshots() { Summary: expected[0].Summary, }}, ret) + config.Get().Clients.Roadmap.Server = "http://example.com" + expectedRoadmap := roadmap_client.AppstreamsResponse{ + Meta: roadmap_client.Meta{}, + Data: []roadmap_client.AppstreamEntity{ + { + Name: "demo-package", + Stream: "0", + StartDate: "01-01-01", + EndDate: "02-02-02", + Impl: "dnf_module", + }, + { + Name: "demo-package", + Stream: "1", + StartDate: "01-01-01", + EndDate: "02-02-02", + Impl: "dnf_module", + }, + { + Name: "demo-package", + StartDate: "01-01-01", + EndDate: "02-02-02", + Impl: "package", + }, + }, + } + s.mockRoadmapClient.On("GetAppstreams", context.Background()).Return(expectedRoadmap, 0, nil) + // ensure module info is in response and correct if requested ret, err = dao.SearchSnapshotRpms(ctx, orgId, api.SnapshotSearchRpmRequest{ UUIDs: []string{snaps[0].UUID},