From abdfe9673069b5f78505ae5a9d9fe26485965913 Mon Sep 17 00:00:00 2001 From: rverdile Date: Thu, 22 Jan 2026 15:47:08 -0500 Subject: [PATCH 1/2] HMS-9958: add minor versions and extended release features to repository parameters (cherry picked from commit 626a7d057ac20021b0d4be1ddd03b1c7de600daa) --- .../src/client/.openapi-generator/FILES | 2 + .../models/ApiRepositoryParameterResponse.ts | 30 +++++++ .../models/ConfigDistributionMinorVersion.ts | 89 +++++++++++++++++++ .../models/ConfigExtendedReleaseFeature.ts | 73 +++++++++++++++ .../test-utils/src/client/models/index.ts | 2 + api/docs.go | 45 ++++++++++ api/openapi.json | 45 ++++++++++ pkg/api/repository_parameters.go | 6 +- pkg/config/value_constraints.go | 33 +++++++ .../snapshotted_repos_importer_test.go | 89 +++++++++++++++++++ pkg/handler/api.go | 2 +- pkg/handler/repository_parameters.go | 54 +++++++++-- pkg/handler/repository_parameters_test.go | 60 ++++++++++++- 13 files changed, 520 insertions(+), 10 deletions(-) create mode 100644 _playwright-tests/test-utils/src/client/models/ConfigDistributionMinorVersion.ts create mode 100644 _playwright-tests/test-utils/src/client/models/ConfigExtendedReleaseFeature.ts create mode 100644 pkg/external_repos/snapshotted_repos_importer_test.go diff --git a/_playwright-tests/test-utils/src/client/.openapi-generator/FILES b/_playwright-tests/test-utils/src/client/.openapi-generator/FILES index f07266bc9..5e571d72d 100644 --- a/_playwright-tests/test-utils/src/client/.openapi-generator/FILES +++ b/_playwright-tests/test-utils/src/client/.openapi-generator/FILES @@ -72,7 +72,9 @@ models/ApiUpload.ts models/ApiUploadResponse.ts models/ApiUrlValidationResponse.ts models/ConfigDistributionArch.ts +models/ConfigDistributionMinorVersion.ts models/ConfigDistributionVersion.ts +models/ConfigExtendedReleaseFeature.ts models/ErrorsErrorResponse.ts models/ErrorsHandlerError.ts models/index.ts diff --git a/_playwright-tests/test-utils/src/client/models/ApiRepositoryParameterResponse.ts b/_playwright-tests/test-utils/src/client/models/ApiRepositoryParameterResponse.ts index 4e0c4187c..fabcf59c6 100644 --- a/_playwright-tests/test-utils/src/client/models/ApiRepositoryParameterResponse.ts +++ b/_playwright-tests/test-utils/src/client/models/ApiRepositoryParameterResponse.ts @@ -20,6 +20,13 @@ import { ConfigDistributionVersionToJSON, ConfigDistributionVersionToJSONTyped, } from './ConfigDistributionVersion'; +import type { ConfigDistributionMinorVersion } from './ConfigDistributionMinorVersion'; +import { + ConfigDistributionMinorVersionFromJSON, + ConfigDistributionMinorVersionFromJSONTyped, + ConfigDistributionMinorVersionToJSON, + ConfigDistributionMinorVersionToJSONTyped, +} from './ConfigDistributionMinorVersion'; import type { ConfigDistributionArch } from './ConfigDistributionArch'; import { ConfigDistributionArchFromJSON, @@ -27,6 +34,13 @@ import { ConfigDistributionArchToJSON, ConfigDistributionArchToJSONTyped, } from './ConfigDistributionArch'; +import type { ConfigExtendedReleaseFeature } from './ConfigExtendedReleaseFeature'; +import { + ConfigExtendedReleaseFeatureFromJSON, + ConfigExtendedReleaseFeatureFromJSONTyped, + ConfigExtendedReleaseFeatureToJSON, + ConfigExtendedReleaseFeatureToJSONTyped, +} from './ConfigExtendedReleaseFeature'; /** * @@ -40,12 +54,24 @@ export interface ApiRepositoryParameterResponse { * @memberof ApiRepositoryParameterResponse */ distributionArches?: Array; + /** + * Minor versions available for repository creation (filtered by subscriptions) + * @type {Array} + * @memberof ApiRepositoryParameterResponse + */ + distributionMinorVersions?: Array; /** * Versions available for repository creation * @type {Array} * @memberof ApiRepositoryParameterResponse */ distributionVersions?: Array; + /** + * Extended release features available (filtered by subscriptions) + * @type {Array} + * @memberof ApiRepositoryParameterResponse + */ + extendedReleaseFeatures?: Array; } /** @@ -66,7 +92,9 @@ export function ApiRepositoryParameterResponseFromJSONTyped(json: any, ignoreDis return { 'distributionArches': json['distribution_arches'] == null ? undefined : ((json['distribution_arches'] as Array).map(ConfigDistributionArchFromJSON)), + 'distributionMinorVersions': json['distribution_minor_versions'] == null ? undefined : ((json['distribution_minor_versions'] as Array).map(ConfigDistributionMinorVersionFromJSON)), 'distributionVersions': json['distribution_versions'] == null ? undefined : ((json['distribution_versions'] as Array).map(ConfigDistributionVersionFromJSON)), + 'extendedReleaseFeatures': json['extended_release_features'] == null ? undefined : ((json['extended_release_features'] as Array).map(ConfigExtendedReleaseFeatureFromJSON)), }; } @@ -82,7 +110,9 @@ export function ApiRepositoryParameterResponseToJSONTyped(value?: ApiRepositoryP return { 'distribution_arches': value['distributionArches'] == null ? undefined : ((value['distributionArches'] as Array).map(ConfigDistributionArchToJSON)), + 'distribution_minor_versions': value['distributionMinorVersions'] == null ? undefined : ((value['distributionMinorVersions'] as Array).map(ConfigDistributionMinorVersionToJSON)), 'distribution_versions': value['distributionVersions'] == null ? undefined : ((value['distributionVersions'] as Array).map(ConfigDistributionVersionToJSON)), + 'extended_release_features': value['extendedReleaseFeatures'] == null ? undefined : ((value['extendedReleaseFeatures'] as Array).map(ConfigExtendedReleaseFeatureToJSON)), }; } diff --git a/_playwright-tests/test-utils/src/client/models/ConfigDistributionMinorVersion.ts b/_playwright-tests/test-utils/src/client/models/ConfigDistributionMinorVersion.ts new file mode 100644 index 000000000..9c31a2449 --- /dev/null +++ b/_playwright-tests/test-utils/src/client/models/ConfigDistributionMinorVersion.ts @@ -0,0 +1,89 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ContentSourcesBackend + * The API for the repositories of the content sources that you can use to create and manage repositories between third-party applications and the [Red Hat Hybrid Cloud Console](https://console.redhat.com). With these repositories, you can build and deploy images using Image Builder for Cloud, on-Premise, and Edge. You can handle tasks, search for required RPMs, fetch a GPGKey from the URL, and list the features within applications. + * + * The version of the OpenAPI document: v1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ConfigDistributionMinorVersion + */ +export interface ConfigDistributionMinorVersion { + /** + * + * @type {Array} + * @memberof ConfigDistributionMinorVersion + */ + featureNames?: Array; + /** + * + * @type {string} + * @memberof ConfigDistributionMinorVersion + */ + label?: string; + /** + * + * @type {string} + * @memberof ConfigDistributionMinorVersion + */ + major?: string; + /** + * + * @type {string} + * @memberof ConfigDistributionMinorVersion + */ + name?: string; +} + +/** + * Check if a given object implements the ConfigDistributionMinorVersion interface. + */ +export function instanceOfConfigDistributionMinorVersion(value: object): value is ConfigDistributionMinorVersion { + return true; +} + +export function ConfigDistributionMinorVersionFromJSON(json: any): ConfigDistributionMinorVersion { + return ConfigDistributionMinorVersionFromJSONTyped(json, false); +} + +export function ConfigDistributionMinorVersionFromJSONTyped(json: any, ignoreDiscriminator: boolean): ConfigDistributionMinorVersion { + if (json == null) { + return json; + } + return { + + 'featureNames': json['feature_names'] == null ? undefined : json['feature_names'], + 'label': json['label'] == null ? undefined : json['label'], + 'major': json['major'] == null ? undefined : json['major'], + 'name': json['name'] == null ? undefined : json['name'], + }; +} + +export function ConfigDistributionMinorVersionToJSON(json: any): ConfigDistributionMinorVersion { + return ConfigDistributionMinorVersionToJSONTyped(json, false); +} + +export function ConfigDistributionMinorVersionToJSONTyped(value?: ConfigDistributionMinorVersion | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'feature_names': value['featureNames'], + 'label': value['label'], + 'major': value['major'], + 'name': value['name'], + }; +} + diff --git a/_playwright-tests/test-utils/src/client/models/ConfigExtendedReleaseFeature.ts b/_playwright-tests/test-utils/src/client/models/ConfigExtendedReleaseFeature.ts new file mode 100644 index 000000000..ed9eb7454 --- /dev/null +++ b/_playwright-tests/test-utils/src/client/models/ConfigExtendedReleaseFeature.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ContentSourcesBackend + * The API for the repositories of the content sources that you can use to create and manage repositories between third-party applications and the [Red Hat Hybrid Cloud Console](https://console.redhat.com). With these repositories, you can build and deploy images using Image Builder for Cloud, on-Premise, and Edge. You can handle tasks, search for required RPMs, fetch a GPGKey from the URL, and list the features within applications. + * + * The version of the OpenAPI document: v1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ConfigExtendedReleaseFeature + */ +export interface ConfigExtendedReleaseFeature { + /** + * + * @type {string} + * @memberof ConfigExtendedReleaseFeature + */ + label?: string; + /** + * + * @type {string} + * @memberof ConfigExtendedReleaseFeature + */ + name?: string; +} + +/** + * Check if a given object implements the ConfigExtendedReleaseFeature interface. + */ +export function instanceOfConfigExtendedReleaseFeature(value: object): value is ConfigExtendedReleaseFeature { + return true; +} + +export function ConfigExtendedReleaseFeatureFromJSON(json: any): ConfigExtendedReleaseFeature { + return ConfigExtendedReleaseFeatureFromJSONTyped(json, false); +} + +export function ConfigExtendedReleaseFeatureFromJSONTyped(json: any, ignoreDiscriminator: boolean): ConfigExtendedReleaseFeature { + if (json == null) { + return json; + } + return { + + 'label': json['label'] == null ? undefined : json['label'], + 'name': json['name'] == null ? undefined : json['name'], + }; +} + +export function ConfigExtendedReleaseFeatureToJSON(json: any): ConfigExtendedReleaseFeature { + return ConfigExtendedReleaseFeatureToJSONTyped(json, false); +} + +export function ConfigExtendedReleaseFeatureToJSONTyped(value?: ConfigExtendedReleaseFeature | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'label': value['label'], + 'name': value['name'], + }; +} + diff --git a/_playwright-tests/test-utils/src/client/models/index.ts b/_playwright-tests/test-utils/src/client/models/index.ts index d2e58fa67..c63a67f17 100644 --- a/_playwright-tests/test-utils/src/client/models/index.ts +++ b/_playwright-tests/test-utils/src/client/models/index.ts @@ -60,6 +60,8 @@ export * from './ApiUpload'; export * from './ApiUploadResponse'; export * from './ApiUrlValidationResponse'; export * from './ConfigDistributionArch'; +export * from './ConfigDistributionMinorVersion'; export * from './ConfigDistributionVersion'; +export * from './ConfigExtendedReleaseFeature'; export * from './ErrorsErrorResponse'; export * from './ErrorsHandlerError'; diff --git a/api/docs.go b/api/docs.go index 2c3e87a31..9b1bc706b 100644 --- a/api/docs.go +++ b/api/docs.go @@ -4370,12 +4370,26 @@ const docTemplate = `{ "$ref": "#/definitions/config.DistributionArch" } }, + "distribution_minor_versions": { + "description": "Minor versions available for repository creation (filtered by subscriptions)", + "type": "array", + "items": { + "$ref": "#/definitions/config.DistributionMinorVersion" + } + }, "distribution_versions": { "description": "Versions available for repository creation", "type": "array", "items": { "$ref": "#/definitions/config.DistributionVersion" } + }, + "extended_release_features": { + "description": "Extended release features available (filtered by subscriptions)", + "type": "array", + "items": { + "$ref": "#/definitions/config.ExtendedReleaseFeature" + } } } }, @@ -5614,6 +5628,26 @@ const docTemplate = `{ } } }, + "config.DistributionMinorVersion": { + "type": "object", + "properties": { + "feature_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "label": { + "type": "string" + }, + "major": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "config.DistributionVersion": { "type": "object", "properties": { @@ -5627,6 +5661,17 @@ const docTemplate = `{ } } }, + "config.ExtendedReleaseFeature": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "errors.ErrorResponse": { "type": "object", "properties": { diff --git a/api/openapi.json b/api/openapi.json index 5bc8cefe3..aa8c5ad05 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -758,12 +758,26 @@ }, "type": "array" }, + "distribution_minor_versions": { + "description": "Minor versions available for repository creation (filtered by subscriptions)", + "items": { + "$ref": "#/components/schemas/config.DistributionMinorVersion" + }, + "type": "array" + }, "distribution_versions": { "description": "Versions available for repository creation", "items": { "$ref": "#/components/schemas/config.DistributionVersion" }, "type": "array" + }, + "extended_release_features": { + "description": "Extended release features available (filtered by subscriptions)", + "items": { + "$ref": "#/components/schemas/config.ExtendedReleaseFeature" + }, + "type": "array" } }, "type": "object" @@ -2003,6 +2017,26 @@ }, "type": "object" }, + "config.DistributionMinorVersion": { + "properties": { + "feature_names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "label": { + "type": "string" + }, + "major": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, "config.DistributionVersion": { "properties": { "label": { @@ -2016,6 +2050,17 @@ }, "type": "object" }, + "config.ExtendedReleaseFeature": { + "properties": { + "label": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, "errors.ErrorResponse": { "properties": { "errors": { diff --git a/pkg/api/repository_parameters.go b/pkg/api/repository_parameters.go index c2e2107b2..38b8a2007 100644 --- a/pkg/api/repository_parameters.go +++ b/pkg/api/repository_parameters.go @@ -12,8 +12,10 @@ type FetchGPGKeyRequest struct { // RepositoryParameterResponse holds data returned by a repositories API response type RepositoryParameterResponse struct { - DistributionVersions []config.DistributionVersion `json:"distribution_versions" ` // Versions available for repository creation - DistributionArches []config.DistributionArch `json:"distribution_arches"` // Architectures available for repository creation + DistributionVersions []config.DistributionVersion `json:"distribution_versions" ` // Versions available for repository creation + DistributionMinorVersions []config.DistributionMinorVersion `json:"distribution_minor_versions" ` // Minor versions available for repository creation (filtered by subscriptions) + DistributionArches []config.DistributionArch `json:"distribution_arches"` // Architectures available for repository creation + ExtendedReleaseFeatures []config.ExtendedReleaseFeature `json:"extended_release_features"` // Extended release features available (filtered by subscriptions) } type RepositoryValidationRequest struct { diff --git a/pkg/config/value_constraints.go b/pkg/config/value_constraints.go index 2f3177719..36438bc27 100644 --- a/pkg/config/value_constraints.go +++ b/pkg/config/value_constraints.go @@ -55,6 +55,13 @@ type DistributionVersion struct { Name string `json:"name"` // Human-readable form of the version Label string `json:"label"` // Static label of the version } + +type DistributionMinorVersion struct { + Name string `json:"name"` + Label string `json:"label"` + Major string `json:"major"` + FeatureNames []string `json:"feature_names"` +} type DistributionArch struct { Name string `json:"name"` // Human-readable form of the architecture Label string `json:"label"` // Static label of the architecture @@ -80,6 +87,21 @@ var DistributionVersions = [...]DistributionVersion{ }, } +type ExtendedReleaseFeature struct { + Name string `json:"name"` + Label string `json:"label"` +} + +var DistributionMinorVersions = [...]DistributionMinorVersion{ + {Name: "el8.6", Label: "8.6", Major: El8, FeatureNames: []string{"RHEL-E4S-x86_64"}}, + {Name: "el8.8", Label: "8.8", Major: El8, FeatureNames: []string{"RHEL-E4S-x86_64"}}, + {Name: "el9.0", Label: "9.0", Major: El9, FeatureNames: []string{"RHEL-E4S-x86_64"}}, + {Name: "el9.2", Label: "9.2", Major: El9, FeatureNames: []string{"RHEL-E4S-x86_64"}}, + {Name: "el9.4", Label: "9.4", Major: El9, FeatureNames: []string{"RHEL-EUS-x86_64", "RHEL-E4S-x86_64"}}, + {Name: "el9.6", Label: "9.6", Major: El9, FeatureNames: []string{"RHEL-EUS-x86_64", "RHEL-E4S-x86_64"}}, + {Name: "el10.0", Label: "10.0", Major: El10, FeatureNames: []string{"RHEL-EUS-x86_64", "RHEL-E4S-x86_64"}}, +} + const ANY_ARCH = "any" const X8664 = "x86_64" const S390x = "s390x" @@ -109,6 +131,17 @@ var DistributionArches = [...]DistributionArch{ }, } +var ExtendedReleaseFeatures = [...]ExtendedReleaseFeature{ + { + Name: "Extended Update Support (EUS)", + Label: "RHEL-EUS-x86_64", + }, + { + Name: "Update Services for SAP Solutions (E4S)", + Label: "RHEL-E4S-x86_64", + }, +} + // Features that do not currently use a subscription check, available to all users var SubscriptionFeaturesIgnored = []string{"RHEL-OS-x86_64", ""} diff --git a/pkg/external_repos/snapshotted_repos_importer_test.go b/pkg/external_repos/snapshotted_repos_importer_test.go new file mode 100644 index 000000000..b86b1eb69 --- /dev/null +++ b/pkg/external_repos/snapshotted_repos_importer_test.go @@ -0,0 +1,89 @@ +package external_repos + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type SnapshotRepoImporterSuite struct { + suite.Suite +} + +func TestSnapshotRepoImporterSuite(t *testing.T) { + suite.Run(t, new(SnapshotRepoImporterSuite)) +} + +func (s *SnapshotRepoImporterSuite) TestDistributionMinorVersionsMatchExtendedReleaseRepos() { + t := s.T() + + // Read e4s and eus repository sets from embedded files + e4sRepos := readEmbeddedExtendedReleaseRepos(t, "snapshotted_repos/e4s-x86_64.json") + eusRepos := readEmbeddedExtendedReleaseRepos(t, "snapshotted_repos/eus-x86_64.json") + + // Extract unique versions from both files + extendedReleaseVersions := make(map[string]map[string]bool) // version -> set of feature names + + for _, repo := range e4sRepos { + version := repo.ExtendedReleaseVersion + if extendedReleaseVersions[version] == nil { + extendedReleaseVersions[version] = make(map[string]bool) + } + extendedReleaseVersions[version][repo.FeatureName] = true + } + + for _, repo := range eusRepos { + version := repo.ExtendedReleaseVersion + if extendedReleaseVersions[version] == nil { + extendedReleaseVersions[version] = make(map[string]bool) + } + extendedReleaseVersions[version][repo.FeatureName] = true + } + + // Verify each version from JSON files exists in DistributionMinorVersions with correct features + for version, features := range extendedReleaseVersions { + found := false + for _, minorVersion := range config.DistributionMinorVersions { + if minorVersion.Label == version { + found = true + // Verify that all features from JSON are present in the config + for feature := range features { + assert.Contains(t, minorVersion.FeatureNames, feature, + fmt.Sprintf("Version %s should have feature %s in config", version, feature)) + } + break + } + } + assert.True(t, found, fmt.Sprintf("Version %s from JSON files should exist in DistributionMinorVersions", version)) + } + + // Verify each version in DistributionMinorVersions exists in the JSON files with correct features + for _, minorVersion := range config.DistributionMinorVersions { + version := minorVersion.Label + jsonFeatures, found := extendedReleaseVersions[version] + assert.True(t, found, fmt.Sprintf("Version %s from config.DistributionMinorVersions should exist in JSON files", version)) + + if found { + // Verify that all features in config are present in the JSON files + for _, configFeature := range minorVersion.FeatureNames { + assert.True(t, jsonFeatures[configFeature], + fmt.Sprintf("Version %s in config has feature %s, but it's not in the JSON files", version, configFeature)) + } + } + } +} + +func readEmbeddedExtendedReleaseRepos(t *testing.T, filePath string) []SnapshottedRepo { + data, err := rhFS.ReadFile(filePath) + assert.Nil(t, err, fmt.Sprintf("Failed to read %s", filePath)) + + var repos []SnapshottedRepo + err = json.Unmarshal(data, &repos) + assert.Nil(t, err, fmt.Sprintf("Failed to unmarshal %s", filePath)) + + return repos +} diff --git a/pkg/handler/api.go b/pkg/handler/api.go index b227b96b3..a151818d8 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -79,7 +79,7 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { daoReg := dao.GetDaoRegistry(db.DB) RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient) - RegisterRepositoryParameterRoutes(group, daoReg) + RegisterRepositoryParameterRoutes(group, daoReg, &fsClient) RegisterRpmRoutes(group, daoReg) RegisterPopularRepositoriesRoutes(group, daoReg) RegisterTaskInfoRoutes(group, daoReg, &taskClient) diff --git a/pkg/handler/repository_parameters.go b/pkg/handler/repository_parameters.go index ee2da2d33..9a8a74663 100644 --- a/pkg/handler/repository_parameters.go +++ b/pkg/handler/repository_parameters.go @@ -3,26 +3,33 @@ package handler import ( "fmt" "net/http" + "slices" "sync" "time" "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/feature_service_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/yummy/pkg/yum" "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" ) const RequestTimeout = time.Second * 3 type RepositoryParameterHandler struct { - dao dao.DaoRegistry + dao dao.DaoRegistry + FeatureServiceClient feature_service_client.FeatureServiceClient } -func RegisterRepositoryParameterRoutes(engine *echo.Group, dao *dao.DaoRegistry) { - rph := RepositoryParameterHandler{dao: *dao} +func RegisterRepositoryParameterRoutes(engine *echo.Group, dao *dao.DaoRegistry, fsClient *feature_service_client.FeatureServiceClient) { + rph := RepositoryParameterHandler{ + dao: *dao, + FeatureServiceClient: *fsClient, + } addRepoRoute(engine, http.MethodGet, "/repository_parameters/", rph.listParameters, rbac.RbacVerbRead) addRepoRoute(engine, http.MethodPost, "/repository_parameters/external_gpg_key/", rph.fetchGpgKey, rbac.RbacVerbWrite) @@ -76,12 +83,49 @@ func (rh *RepositoryParameterHandler) fetchGpgKey(c echo.Context) error { // @Failure 401 {object} ce.ErrorResponse // @Router /repository_parameters/ [get] func (rh *RepositoryParameterHandler) listParameters(c echo.Context) error { + _, orgID := getAccountIdOrgId(c) + + features, err := rh.FeatureServiceClient.GetEntitledFeatures(c.Request().Context(), orgID) + if err != nil { + log.Error().Err(err).Msg("error getting entitled features, proceeding with default") + } + + filteredMinorVersions := filterMinorVersionsByFeatures(features) + filteredExtendedReleaseFeatures := filterExtendedReleaseFeatures(features) + return c.JSON(200, api.RepositoryParameterResponse{ - DistributionVersions: config.DistributionVersions[:], - DistributionArches: config.DistributionArches[:], + DistributionVersions: config.DistributionVersions[:], + DistributionMinorVersions: filteredMinorVersions[:], + DistributionArches: config.DistributionArches[:], + ExtendedReleaseFeatures: filteredExtendedReleaseFeatures[:], }) } +// filterMinorVersionsByFeatures filters minor versions based on entitled features +func filterMinorVersionsByFeatures(entitledFeatures []string) []config.DistributionMinorVersion { + var filtered []config.DistributionMinorVersion + for _, minorVersion := range config.DistributionMinorVersions[:] { + for _, feature := range entitledFeatures { + if slices.Contains(minorVersion.FeatureNames, feature) { + filtered = append(filtered, minorVersion) + break + } + } + } + return filtered +} + +// filterExtendedReleaseFeatures filters extended release features based on entitled features +func filterExtendedReleaseFeatures(entitledFeatures []string) []config.ExtendedReleaseFeature { + var filtered []config.ExtendedReleaseFeature + for _, feature := range config.ExtendedReleaseFeatures[:] { + if slices.Contains(entitledFeatures, feature.Label) { + filtered = append(filtered, feature) + } + } + return filtered +} + // ValidateRepositoryParameters godoc // @summary Validate parameters prior to creating a repository // @Description This validates the parameters before creating a repository. It provides a way to ensure the accuracy and validity of the provided parameters, including a check for the presence of remote yum metadata. Users can perform necessary checks before proceeding with the creation of a repository. diff --git a/pkg/handler/repository_parameters_test.go b/pkg/handler/repository_parameters_test.go index 31885b274..b778b30db 100644 --- a/pkg/handler/repository_parameters_test.go +++ b/pkg/handler/repository_parameters_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/feature_service_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/middleware" @@ -25,6 +26,7 @@ import ( type RepositoryParameterSuite struct { suite.Suite mockDao *dao.MockDaoRegistry + fsMock *feature_service_client.MockFeatureServiceClient } func TestRepositoryParameterSuite(t *testing.T) { @@ -33,6 +35,7 @@ func TestRepositoryParameterSuite(t *testing.T) { func (s *RepositoryParameterSuite) SetupTest() { s.mockDao = dao.GetMockDaoRegistry(s.T()) + s.fsMock = feature_service_client.NewMockFeatureServiceClient(s.T()) } func (s *RepositoryParameterSuite) serveRepositoryParametersRouter(req *http.Request) (int, []byte, error) { @@ -41,7 +44,8 @@ func (s *RepositoryParameterSuite) serveRepositoryParametersRouter(req *http.Req router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) pathPrefix := router.Group(api.FullRootPath()) - RegisterRepositoryParameterRoutes(pathPrefix, s.mockDao.ToDaoRegistry()) + rph := RepositoryParameterHandler{FeatureServiceClient: s.fsMock} + RegisterRepositoryParameterRoutes(pathPrefix, s.mockDao.ToDaoRegistry(), &rph.FeatureServiceClient) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) @@ -58,8 +62,9 @@ func (s *RepositoryParameterSuite) TestListParams() { path := fmt.Sprintf("%s/repository_parameters/", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) setHeaders(t, req) - code, body, err := s.serveRepositoryParametersRouter(req) + s.fsMock.On("GetEntitledFeatures", test.MockCtx(), test_handler.MockOrgId).Return([]string{"RHEL-EUS-x86_64", "RHEL-E4S-x86_64"}, nil) + code, body, err := s.serveRepositoryParametersRouter(req) assert.Nil(t, err) response := api.RepositoryParameterResponse{} @@ -69,6 +74,57 @@ func (s *RepositoryParameterSuite) TestListParams() { assert.Equal(t, http.StatusOK, code) assert.NotEmpty(t, response.DistributionArches) assert.NotEmpty(t, response.DistributionVersions) + + assert.NotEmpty(t, response.DistributionMinorVersions) + assert.NotEmpty(t, response.DistributionMinorVersions[0].Name) + assert.NotEmpty(t, response.DistributionMinorVersions[0].Label) + assert.NotEmpty(t, response.ExtendedReleaseFeatures) +} + +func (s *RepositoryParameterSuite) TestListParamsOnlyEUS() { + t := s.T() + path := fmt.Sprintf("%s/repository_parameters/", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + setHeaders(t, req) + + s.fsMock.On("GetEntitledFeatures", test.MockCtx(), test_handler.MockOrgId).Return([]string{"RHEL-EUS-x86_64"}, nil) + + code, body, err := s.serveRepositoryParametersRouter(req) + assert.Nil(t, err) + + response := api.RepositoryParameterResponse{} + err = json.Unmarshal(body, &response) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, code) + + assert.NotEmpty(t, response.DistributionMinorVersions) + assert.NotEmpty(t, response.DistributionMinorVersions[0].Name) + assert.NotEmpty(t, response.DistributionMinorVersions[0].Label) + + // Only EUS feature should be returned + assert.Equal(t, 1, len(response.ExtendedReleaseFeatures)) + assert.Equal(t, "RHEL-EUS-x86_64", response.ExtendedReleaseFeatures[0].Label) + assert.Equal(t, "Extended Update Support (EUS)", response.ExtendedReleaseFeatures[0].Name) +} + +func (s *RepositoryParameterSuite) TestListParamsNoEntitledFeatures() { + t := s.T() + path := fmt.Sprintf("%s/repository_parameters/", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + setHeaders(t, req) + + s.fsMock.On("GetEntitledFeatures", test.MockCtx(), test_handler.MockOrgId).Return([]string{}, nil) + + code, body, err := s.serveRepositoryParametersRouter(req) + assert.Nil(t, err) + + response := api.RepositoryParameterResponse{} + err = json.Unmarshal(body, &response) + assert.Nil(t, err) + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, 0, len(response.DistributionMinorVersions)) + assert.Equal(t, 0, len(response.ExtendedReleaseFeatures)) } func (s *RepositoryParameterSuite) TestValidate() { From 844e63c26e03b23ffd3ad30384de780b73f6ed35 Mon Sep 17 00:00:00 2001 From: rverdile Date: Mon, 26 Jan 2026 10:52:18 -0500 Subject: [PATCH 2/2] HMS-9959: support extended release versions for templates (cherry picked from commit e2b66b08eee9614911ab23eb18df1d0ad55fb44e) --- .../src/client/models/ApiTemplateRequest.ts | 16 ++++++++ .../src/client/models/ApiTemplateResponse.ts | 16 ++++++++ api/docs.go | 16 ++++++++ api/openapi.json | 16 ++++++++ db/migrations.latest | 2 +- ...add_extended_release_to_templates.down.sql | 7 ++++ ...0_add_extended_release_to_templates.up.sql | 7 ++++ pkg/api/templates.go | 40 ++++++++++-------- pkg/dao/templates.go | 14 +++++++ pkg/models/template.go | 2 + pkg/tasks/update_template_content.go | 18 +++++++- pkg/tasks/update_template_content_test.go | 41 +++++++++++++++++++ 12 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 db/migrations/20260123120000_add_extended_release_to_templates.down.sql create mode 100644 db/migrations/20260123120000_add_extended_release_to_templates.up.sql diff --git a/_playwright-tests/test-utils/src/client/models/ApiTemplateRequest.ts b/_playwright-tests/test-utils/src/client/models/ApiTemplateRequest.ts index 50dd8c724..dfac84c0b 100644 --- a/_playwright-tests/test-utils/src/client/models/ApiTemplateRequest.ts +++ b/_playwright-tests/test-utils/src/client/models/ApiTemplateRequest.ts @@ -37,6 +37,18 @@ export interface ApiTemplateRequest { * @memberof ApiTemplateRequest */ description?: string; + /** + * Extended release type (eus, e4s) + * @type {string} + * @memberof ApiTemplateRequest + */ + extendedRelease?: string; + /** + * Extended release version (9.4, 9.6, etc.) + * @type {string} + * @memberof ApiTemplateRequest + */ + extendedReleaseVersion?: string; /** * Name of the template * @type {string} @@ -87,6 +99,8 @@ export function ApiTemplateRequestFromJSONTyped(json: any, ignoreDiscriminator: 'arch': json['arch'], 'date': json['date'] == null ? undefined : json['date'], 'description': json['description'] == null ? undefined : json['description'], + 'extendedRelease': json['extended_release'] == null ? undefined : json['extended_release'], + 'extendedReleaseVersion': json['extended_release_version'] == null ? undefined : json['extended_release_version'], 'name': json['name'], 'repositoryUuids': json['repository_uuids'], 'useLatest': json['use_latest'] == null ? undefined : json['use_latest'], @@ -108,6 +122,8 @@ export function ApiTemplateRequestToJSONTyped(value?: ApiTemplateRequest | null, 'arch': value['arch'], 'date': value['date'], 'description': value['description'], + 'extended_release': value['extendedRelease'], + 'extended_release_version': value['extendedReleaseVersion'], 'name': value['name'], 'repository_uuids': value['repositoryUuids'], 'use_latest': value['useLatest'], diff --git a/_playwright-tests/test-utils/src/client/models/ApiTemplateResponse.ts b/_playwright-tests/test-utils/src/client/models/ApiTemplateResponse.ts index efe9f907b..003fec0a2 100644 --- a/_playwright-tests/test-utils/src/client/models/ApiTemplateResponse.ts +++ b/_playwright-tests/test-utils/src/client/models/ApiTemplateResponse.ts @@ -64,6 +64,18 @@ export interface ApiTemplateResponse { * @memberof ApiTemplateResponse */ description?: string; + /** + * Extended release type (eus, e4s) + * @type {string} + * @memberof ApiTemplateResponse + */ + extendedRelease?: string; + /** + * Extended release version (9.4, 9.6, etc.) + * @type {string} + * @memberof ApiTemplateResponse + */ + extendedReleaseVersion?: string; /** * Error of last update_latest_snapshot task that updated the template * @type {string} @@ -178,6 +190,8 @@ export function ApiTemplateResponseFromJSONTyped(json: any, ignoreDiscriminator: 'createdBy': json['created_by'] == null ? undefined : json['created_by'], 'date': json['date'] == null ? undefined : json['date'], 'description': json['description'] == null ? undefined : json['description'], + 'extendedRelease': json['extended_release'] == null ? undefined : json['extended_release'], + 'extendedReleaseVersion': json['extended_release_version'] == null ? undefined : json['extended_release_version'], 'lastUpdateSnapshotError': json['last_update_snapshot_error'] == null ? undefined : json['last_update_snapshot_error'], 'lastUpdateTask': json['last_update_task'] == null ? undefined : ApiTaskInfoResponseFromJSON(json['last_update_task']), 'lastUpdateTaskUuid': json['last_update_task_uuid'] == null ? undefined : json['last_update_task_uuid'], @@ -212,6 +226,8 @@ export function ApiTemplateResponseToJSONTyped(value?: Omit