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/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} + * @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..bb4dcebec 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" + } } } }, @@ -5355,6 +5369,14 @@ const docTemplate = `{ "description": "Description of the template", "type": "string" }, + "extended_release": { + "description": "Extended release type (eus, e4s)", + "type": "string" + }, + "extended_release_version": { + "description": "Extended release version (9.4, 9.6, etc.)", + "type": "string" + }, "name": { "description": "Name of the template", "type": "string" @@ -5399,6 +5421,14 @@ const docTemplate = `{ "description": "Description of the template", "type": "string" }, + "extended_release": { + "description": "Extended release type (eus, e4s)", + "type": "string" + }, + "extended_release_version": { + "description": "Extended release version (9.4, 9.6, etc.)", + "type": "string" + }, "last_update_snapshot_error": { "description": "Error of last update_latest_snapshot task that updated the template", "type": "string" @@ -5614,6 +5644,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 +5677,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..ae1476466 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" @@ -1737,6 +1751,14 @@ "description": "Description of the template", "type": "string" }, + "extended_release": { + "description": "Extended release type (eus, e4s)", + "type": "string" + }, + "extended_release_version": { + "description": "Extended release version (9.4, 9.6, etc.)", + "type": "string" + }, "name": { "description": "Name of the template", "type": "string" @@ -1787,6 +1809,14 @@ "description": "Description of the template", "type": "string" }, + "extended_release": { + "description": "Extended release type (eus, e4s)", + "type": "string" + }, + "extended_release_version": { + "description": "Extended release version (9.4, 9.6, etc.)", + "type": "string" + }, "last_update_snapshot_error": { "description": "Error of last update_latest_snapshot task that updated the template", "type": "string" @@ -2003,6 +2033,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 +2066,17 @@ }, "type": "object" }, + "config.ExtendedReleaseFeature": { + "properties": { + "label": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, "errors.ErrorResponse": { "properties": { "errors": { diff --git a/db/migrations.latest b/db/migrations.latest index 15779ff8e..69a5f8bde 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260116141110 +20260123120000 diff --git a/db/migrations/20260123120000_add_extended_release_to_templates.down.sql b/db/migrations/20260123120000_add_extended_release_to_templates.down.sql new file mode 100644 index 000000000..48e9aeef7 --- /dev/null +++ b/db/migrations/20260123120000_add_extended_release_to_templates.down.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE templates + DROP COLUMN IF EXISTS extended_release, + DROP COLUMN IF EXISTS extended_release_version; + +COMMIT; diff --git a/db/migrations/20260123120000_add_extended_release_to_templates.up.sql b/db/migrations/20260123120000_add_extended_release_to_templates.up.sql new file mode 100644 index 000000000..2f2bf81e4 --- /dev/null +++ b/db/migrations/20260123120000_add_extended_release_to_templates.up.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE templates + ADD COLUMN IF NOT EXISTS extended_release VARCHAR(10) DEFAULT NULL, + ADD COLUMN IF NOT EXISTS extended_release_version VARCHAR(10) DEFAULT NULL; + +COMMIT; 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/api/templates.go b/pkg/api/templates.go index 1f53468c7..987bf1fce 100644 --- a/pkg/api/templates.go +++ b/pkg/api/templates.go @@ -9,16 +9,18 @@ import ( ) type TemplateRequest struct { - UUID *string `json:"uuid" readonly:"true" swaggerignore:"true"` - Name *string `json:"name" validate:"required"` // Name of the template - Description *string `json:"description"` // Description of the template - RepositoryUUIDS []string `json:"repository_uuids" validate:"required"` // Repositories to add to the template - Arch *string `json:"arch" validate:"required"` // Architecture of the template - Version *string `json:"version" validate:"required"` // Version of the template - Date *EmptiableDate `json:"date"` // Latest date to include snapshots for - OrgID *string `json:"org_id" readonly:"true" swaggerignore:"true"` // Organization ID of the owner - User *string `json:"created_by" readonly:"true" swaggerignore:"true"` // User creating the template - UseLatest *bool `json:"use_latest"` // Use latest snapshot for all repositories in the template + UUID *string `json:"uuid" readonly:"true" swaggerignore:"true"` + Name *string `json:"name" validate:"required"` // Name of the template + Description *string `json:"description"` // Description of the template + RepositoryUUIDS []string `json:"repository_uuids" validate:"required"` // Repositories to add to the template + Arch *string `json:"arch" validate:"required"` // Architecture of the template + Version *string `json:"version" validate:"required"` // Version of the template + ExtendedRelease *string `json:"extended_release"` // Extended release type (eus, e4s) + ExtendedReleaseVersion *string `json:"extended_release_version"` // Extended release version (9.4, 9.6, etc.) + Date *EmptiableDate `json:"date"` // Latest date to include snapshots for + OrgID *string `json:"org_id" readonly:"true" swaggerignore:"true"` // Organization ID of the owner + User *string `json:"created_by" readonly:"true" swaggerignore:"true"` // User creating the template + UseLatest *bool `json:"use_latest"` // Use latest snapshot for all repositories in the template } type TemplateResponse struct { @@ -28,6 +30,8 @@ type TemplateResponse struct { Description string `json:"description"` // Description of the template Arch string `json:"arch"` // Architecture of the template Version string `json:"version"` // Version of the template + ExtendedRelease string `json:"extended_release,omitempty"` // Extended release type (eus, e4s) + ExtendedReleaseVersion string `json:"extended_release_version,omitempty"` // Extended release version (9.4, 9.6, etc.) Date time.Time `json:"date"` // Latest date to include snapshots for RepositoryUUIDS []string `json:"repository_uuids"` // Repositories added to the template Snapshots []SnapshotResponse `json:"snapshots,omitempty" readonly:"true"` // The list of snapshots in use by the template @@ -69,13 +73,15 @@ func (r *TemplateCollectionResponse) SetMetadata(meta ResponseMetadata, links Li } type TemplateFilterData struct { - Name string `json:"name"` // Filter templates by name using an exact match. - Arch string `json:"arch"` // Filter templates by arch using an exact match. - Version string `json:"version"` // Filter templates by version using an exact match. - Search string `json:"search"` // Search string based query to optionally filter on - RepositoryUUIDs []string `json:"repository_uuids"` // List templates that contain one or more of these Repositories - SnapshotUUIDs []string `json:"snapshot_uuids"` // List templates that contain one or more of these Snapshots - UseLatest bool `json:"use_latest"` // List templates that have use_latest set to true + Name string `json:"name"` // Filter templates by name using an exact match. + Arch string `json:"arch"` // Filter templates by arch using an exact match. + Version string `json:"version"` // Filter templates by version using an exact match. + ExtendedRelease string `json:"extended_release"` // Filter templates by extended release type using an exact match. + ExtendedReleaseVersion string `json:"extended_release_version"` // Filter templates by extended release version using an exact match. + Search string `json:"search"` // Search string based query to optionally filter on + RepositoryUUIDs []string `json:"repository_uuids"` // List templates that contain one or more of these Repositories + SnapshotUUIDs []string `json:"snapshot_uuids"` // List templates that contain one or more of these Snapshots + UseLatest bool `json:"use_latest"` // List templates that have use_latest set to true } // Provides defaults if not provided during PUT request 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/dao/templates.go b/pkg/dao/templates.go index 4fba0ca8b..940e1a292 100644 --- a/pkg/dao/templates.go +++ b/pkg/dao/templates.go @@ -400,6 +400,12 @@ func (t templateDaoImpl) filteredDbForList(orgID string, filteredDB *gorm.DB, fi if filterData.Version != "" { filteredDB = filteredDB.Where("version = ?", filterData.Version) } + if filterData.ExtendedRelease != "" { + filteredDB = filteredDB.Where("extended_release = ?", filterData.ExtendedRelease) + } + if filterData.ExtendedReleaseVersion != "" { + filteredDB = filteredDB.Where("extended_release_version = ?", filterData.ExtendedReleaseVersion) + } if filterData.Search != "" { containsSearch := "%" + filterData.Search + "%" filteredDB = filteredDB. @@ -761,6 +767,12 @@ func templatesCreateApiToModel(api api.TemplateRequest, model *models.Template) if api.UseLatest != nil { model.UseLatest = *api.UseLatest } + if api.ExtendedRelease != nil { + model.ExtendedRelease = *api.ExtendedRelease + } + if api.ExtendedReleaseVersion != nil { + model.ExtendedReleaseVersion = *api.ExtendedReleaseVersion + } } func templatesUpdateApiToModel(api api.TemplateUpdateRequest, model *models.Template) { @@ -793,6 +805,8 @@ func templatesModelToApi(model models.Template, apiTemplate *api.TemplateRespons apiTemplate.Description = model.Description apiTemplate.Version = model.Version apiTemplate.Arch = model.Arch + apiTemplate.ExtendedRelease = model.ExtendedRelease + apiTemplate.ExtendedReleaseVersion = model.ExtendedReleaseVersion apiTemplate.Date = model.Date.UTC() apiTemplate.CreatedBy = model.CreatedBy apiTemplate.LastUpdatedBy = model.LastUpdatedBy 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() { diff --git a/pkg/models/template.go b/pkg/models/template.go index 4967033df..895c7ad9d 100644 --- a/pkg/models/template.go +++ b/pkg/models/template.go @@ -16,6 +16,8 @@ type Template struct { Date time.Time `gorm:"default:null"` Version string `gorm:"default:null"` Arch string `gorm:"default:null"` + ExtendedRelease string `json:"extended_release" gorm:"default:null"` + ExtendedReleaseVersion string `json:"extended_release_version" gorm:"default:null"` DeletedAt gorm.DeletedAt `json:"deleted_at"` CreatedBy string LastUpdatedBy string diff --git a/pkg/tasks/update_template_content.go b/pkg/tasks/update_template_content.go index a77faf046..1deee5d36 100644 --- a/pkg/tasks/update_template_content.go +++ b/pkg/tasks/update_template_content.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/url" + "regexp" "slices" "strings" "time" @@ -526,7 +527,11 @@ func (t *UpdateTemplateContent) getContentList() ([]caliri.ContentDTO, []string, func (t *UpdateTemplateContent) getRedHatContentIDs(rhRepos []api.RepositoryResponse) ([]string, error) { labels := []string{} for _, rhRepo := range rhRepos { - labels = append(labels, rhRepo.Label) + label := rhRepo.Label + if rhRepo.ExtendedRelease != "" { + label = normalizeExtendedReleaseLabel(rhRepo.Label) + } + labels = append(labels, label) } contents, err := t.cpClient.FetchContentsByLabel(t.ctx, t.orgId, labels) if err != nil { @@ -590,3 +595,14 @@ func difference(a, b []string) []string { } return diff } + +// normalizeExtendedReleaseLabel normalizes extended release repository labels +// For e4s and eus repositories, the label should use the major version only +// For example: "rhel-8.6-for-x86_64-appstream-e4s-rpms" becomes "rhel-8-for-x86_64-appstream-e4s-rpms" +func normalizeExtendedReleaseLabel(label string) string { + extendedReleasePattern := regexp.MustCompile(`^(rhel-\d+)\.\d+(-for-.+-(e4s|eus)-rpms)$`) + if extendedReleasePattern.MatchString(label) { + return extendedReleasePattern.ReplaceAllString(label, `${1}${2}`) + } + return label +} diff --git a/pkg/tasks/update_template_content_test.go b/pkg/tasks/update_template_content_test.go index 7ab8d8b7c..4f5435f48 100644 --- a/pkg/tasks/update_template_content_test.go +++ b/pkg/tasks/update_template_content_test.go @@ -38,3 +38,44 @@ func (s *UpdateTemplateContentSuite) TestGetDistributionPath() { assert.Equal(s.T(), expectedCustomPath, distPath) assert.Equal(s.T(), expectedName, distName) } + +func (s *UpdateTemplateContentSuite) TestNormalizeExtendedReleaseLabel() { + testCases := []struct { + name string + input string + expected string + }{ + { + name: "e4s label with minor version", + input: "rhel-8.6-for-x86_64-appstream-e4s-rpms", + expected: "rhel-8-for-x86_64-appstream-e4s-rpms", + }, + { + name: "eus label with minor version", + input: "rhel-9.4-for-x86_64-appstream-eus-rpms", + expected: "rhel-9-for-x86_64-appstream-eus-rpms", + }, + { + name: "regular rhel label without extended release", + input: "rhel-8-for-x86_64-appstream-rpms", + expected: "rhel-8-for-x86_64-appstream-rpms", + }, + { + name: "already normalized e4s label", + input: "rhel-9-for-x86_64-appstream-e4s-rpms", + expected: "rhel-9-for-x86_64-appstream-e4s-rpms", + }, + { + name: "custom repository label", + input: "custom-repo-label", + expected: "custom-repo-label", + }, + } + + for _, tc := range testCases { + s.T().Run(tc.name, func(t *testing.T) { + result := normalizeExtendedReleaseLabel(tc.input) + assert.Equal(t, tc.expected, result) + }) + } +}