From 1e072a86234854c49e60ac9d5b4de0428696cca4 Mon Sep 17 00:00:00 2001 From: Tom Koscielniak Date: Mon, 1 Jun 2026 13:38:30 +0200 Subject: [PATCH] api: Connect customization validation to CRC --- api/config/imageBuilder.ts | 1 + .../api/backend/hosted/imageBuilderApi.ts | 219 ++++++++++++------ src/store/api/backend/hosted/index.ts | 1 + src/store/api/backend/index.ts | 6 + .../onprem/composerApi/distribution.ts | 35 +++ .../api/backend/onprem/composerApi/index.ts | 3 + src/store/api/distributions/constants.ts | 86 ++----- .../distributions/distributionDetailsApi.ts | 46 ---- src/store/api/distributions/hooks.tsx | 64 +++-- src/store/api/distributions/index.ts | 3 +- .../tests/distributionDetailsApi.test.ts | 171 -------------- .../api/distributions/tests/hooks.test.tsx | 22 +- .../api/distributions/tests/mocks/fixtures.ts | 3 +- src/store/api/distributions/types.ts | 27 +-- src/store/api/index.ts | 1 - 15 files changed, 278 insertions(+), 410 deletions(-) create mode 100644 src/store/api/backend/onprem/composerApi/distribution.ts delete mode 100644 src/store/api/distributions/distributionDetailsApi.ts delete mode 100644 src/store/api/distributions/tests/distributionDetailsApi.test.ts diff --git a/api/config/imageBuilder.ts b/api/config/imageBuilder.ts index 5c848997f8..86f0b4c53f 100644 --- a/api/config/imageBuilder.ts +++ b/api/config/imageBuilder.ts @@ -27,6 +27,7 @@ const config: ConfigFile = { 'deleteBlueprint', 'getBlueprint', 'getDistributions', + 'getDistribution', 'recommendPackage', 'fixupBlueprint', ], diff --git a/src/store/api/backend/hosted/imageBuilderApi.ts b/src/store/api/backend/hosted/imageBuilderApi.ts index f4e14cbba7..e4d7eb4822 100644 --- a/src/store/api/backend/hosted/imageBuilderApi.ts +++ b/src/store/api/backend/hosted/imageBuilderApi.ts @@ -15,6 +15,18 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), + getDistribution: build.query< + GetDistributionApiResponse, + GetDistributionApiArg + >({ + query: (queryArg) => ({ + url: `/distributions/${queryArg.distro}`, + params: { + image_type: queryArg.imageType, + architecture: queryArg.architecture, + }, + }), + }), getArchitectures: build.query< GetArchitecturesApiResponse, GetArchitecturesApiArg @@ -199,6 +211,16 @@ export type GetDistributionsApiArg = { */ type?: string; }; +export type GetDistributionApiResponse = + /** status 200 Distribution details */ DistributionDetails; +export type GetDistributionApiArg = { + /** Name of the distribution */ + distro: string; + /** Filter by image type. Multiple values can be specified. */ + imageType?: string[]; + /** Filter by architecture. Multiple values can be specified. */ + architecture?: string[]; +}; export type GetArchitecturesApiResponse = /** status 200 a list of available architectures and their associated image types */ Architectures; export type GetArchitecturesApiArg = { @@ -369,6 +391,135 @@ export type DistributionsResponse = ( | BootcDistributionItem )[]; export type DistributionKind = "bootc"; +export type PartitionType = "gpt" | "dos"; +export type Minsize = string; +export type FilesystemTyped = { + type?: "plain" | undefined; + /** The partition type GUID for GPT partitions. For DOS partitions, this field can be used to set the (2 hex digit) partition type. If not set, the type will be automatically set based on the mountpoint or the payload type. + */ + part_type?: string | undefined; + minsize?: Minsize | undefined; + mountpoint?: string | undefined; + label?: string | undefined; + /** The filesystem type. Swap partitions must have an empty mountpoint. + */ + fs_type: "ext4" | "xfs" | "vfat" | "swap"; +}; +export type BtrfsSubvolume = { + /** The name of the subvolume, which defines the location (path) on the root volume + */ + name: string; + /** Mountpoint for the subvolume + */ + mountpoint: string; +}; +export type BtrfsVolume = { + type: "btrfs"; + /** The partition type GUID for GPT partitions. For DOS partitions, this field can be used to set the (2 hex digit) partition type. If not set, the type will be automatically set based on the mountpoint or the payload type. + */ + part_type?: string | undefined; + minsize?: Minsize | undefined; + subvolumes: BtrfsSubvolume[]; +}; +export type LogicalVolume = { + name?: string | undefined; + minsize?: Minsize | undefined; + /** Mountpoint for the logical volume + */ + mountpoint?: string | undefined; + label?: string | undefined; + /** The filesystem type for the logical volume. Swap LVs must have an empty mountpoint. + */ + fs_type: "ext4" | "xfs" | "vfat" | "swap"; +}; +export type VolumeGroup = { + type: "lvm"; + /** The partition type GUID for GPT partitions. For DOS partitions, this field can be used to set the (2 hex digit) partition type. If not set, the type will be automatically set based on the mountpoint or the payload type. + */ + part_type?: string | undefined; + /** Volume group name (will be automatically generated if omitted) + */ + name?: string | undefined; + minsize?: Minsize | undefined; + logical_volumes: LogicalVolume[]; +}; +export type Partition = FilesystemTyped | BtrfsVolume | VolumeGroup; +export type Disk = { + /** Type of the partition table + */ + type?: ("gpt" | "dos") | undefined; + minsize?: Minsize | undefined; + partitions: Partition[]; +}; +export type BootMode = "legacy" | "uefi" | "hybrid" | "none"; +export type ImageTypeInfo = { + /** Image type name */ + name: string; + /** Alternative names for this image type */ + aliases?: string[] | undefined; + /** Canonical filename for the image */ + filename?: string | undefined; + /** MIME type of the image */ + mime_type?: string | undefined; + /** Default OSTree ref for this image type */ + ostree_ref?: string | undefined; + /** ISO label (only for ISO image types) */ + iso_label?: string | undefined; + /** Default image size in bytes */ + default_size?: number | undefined; + /** Partition table type */ + partition_type?: PartitionType | undefined; + base_partition_table?: Disk | undefined; + /** Boot mode for the image */ + boot_mode?: BootMode | undefined; + /** Package set names safe for custom packages via custom repos */ + payload_package_sets?: string[] | undefined; + /** Names of stages that produce the build output */ + exports?: string[] | undefined; + /** Customization options required by this image type */ + required_blueprint_options?: string[] | undefined; + /** Customization options supported by this image type */ + supported_blueprint_options?: string[] | undefined; +}; +export type ArchitectureInfo = { + /** Architecture name */ + name: string; + /** Map of image type names to their details */ + image_types?: + | { + [key: string]: ImageTypeInfo; + } + | undefined; +}; +export type DistributionDetails = { + /** Name of the distribution */ + name: string; + /** Codename of the distribution */ + codename?: string | undefined; + /** Release version used in repo files */ + releasever?: string | undefined; + /** Full OS version including minor version */ + os_version?: string | undefined; + /** Module platform ID for DNF modularity */ + module_platform_id?: string | undefined; + /** Product name */ + product?: string | undefined; + /** Default OSTree reference template */ + ostree_ref?: string | undefined; + /** Map of architecture names to their details */ + architectures?: + | { + [key: string]: ArchitectureInfo; + } + | undefined; +}; +export type HttpError = { + title: string; + detail: string; +}; +export type HttpErrorList = { + errors: HttpError[]; +}; export type Repository = { /** An ID referring to a repository defined in content sources can be used instead of 'baseurl', 'mirrorlist' or 'metalink'. @@ -393,13 +544,6 @@ export type ArchitectureItem = { repositories: Repository[]; }; export type Architectures = ArchitectureItem[]; -export type HttpError = { - title: string; - detail: string; -}; -export type HttpErrorList = { - errors: HttpError[]; -}; export type Distributions = | "rhel-8" | "rhel-8-nightly" @@ -697,65 +841,6 @@ export type Filesystem = { /** size of the filesystem in bytes */ min_size: any; }; -export type Minsize = string; -export type FilesystemTyped = { - type?: "plain" | undefined; - /** The partition type GUID for GPT partitions. For DOS partitions, this field can be used to set the (2 hex digit) partition type. If not set, the type will be automatically set based on the mountpoint or the payload type. - */ - part_type?: string | undefined; - minsize?: Minsize | undefined; - mountpoint?: string | undefined; - label?: string | undefined; - /** The filesystem type. Swap partitions must have an empty mountpoint. - */ - fs_type: "ext4" | "xfs" | "vfat" | "swap"; -}; -export type BtrfsSubvolume = { - /** The name of the subvolume, which defines the location (path) on the root volume - */ - name: string; - /** Mountpoint for the subvolume - */ - mountpoint: string; -}; -export type BtrfsVolume = { - type: "btrfs"; - /** The partition type GUID for GPT partitions. For DOS partitions, this field can be used to set the (2 hex digit) partition type. If not set, the type will be automatically set based on the mountpoint or the payload type. - */ - part_type?: string | undefined; - minsize?: Minsize | undefined; - subvolumes: BtrfsSubvolume[]; -}; -export type LogicalVolume = { - name?: string | undefined; - minsize?: Minsize | undefined; - /** Mountpoint for the logical volume - */ - mountpoint?: string | undefined; - label?: string | undefined; - /** The filesystem type for the logical volume. Swap LVs must have an empty mountpoint. - */ - fs_type: "ext4" | "xfs" | "vfat" | "swap"; -}; -export type VolumeGroup = { - type: "lvm"; - /** The partition type GUID for GPT partitions. For DOS partitions, this field can be used to set the (2 hex digit) partition type. If not set, the type will be automatically set based on the mountpoint or the payload type. - */ - part_type?: string | undefined; - /** Volume group name (will be automatically generated if omitted) - */ - name?: string | undefined; - minsize?: Minsize | undefined; - logical_volumes: LogicalVolume[]; -}; -export type Partition = FilesystemTyped | BtrfsVolume | VolumeGroup; -export type Disk = { - /** Type of the partition table - */ - type?: ("gpt" | "dos") | undefined; - minsize?: Minsize | undefined; - partitions: Partition[]; -}; export type User = { name: string; /** List of groups to add the user to. The 'wheel' group should be added explicitly, as the @@ -1080,6 +1165,8 @@ export type RecommendPackageRequest = { export const { useGetDistributionsQuery, useLazyGetDistributionsQuery, + useGetDistributionQuery, + useLazyGetDistributionQuery, useGetArchitecturesQuery, useLazyGetArchitecturesQuery, useGetBlueprintsQuery, diff --git a/src/store/api/backend/hosted/index.ts b/src/store/api/backend/hosted/index.ts index 85d65c29e4..44f633c880 100644 --- a/src/store/api/backend/hosted/index.ts +++ b/src/store/api/backend/hosted/index.ts @@ -11,6 +11,7 @@ export { useGetBlueprintQuery, useGetBlueprintsQuery, useGetDistributionsQuery, + useGetDistributionQuery, useGetComposesQuery, useGetComposeStatusQuery, useGetOscapCustomizationsForPolicyQuery, diff --git a/src/store/api/backend/index.ts b/src/store/api/backend/index.ts index 774dbbf7e8..370286d7ff 100644 --- a/src/store/api/backend/index.ts +++ b/src/store/api/backend/index.ts @@ -81,6 +81,12 @@ export const useGetComposeStatusQuery = process.env.IS_ON_PREMISE ? composerQueries.useGetComposeStatusQuery : serviceQueries.useGetComposeStatusQuery; +export const useGetDistributionQuery = ( + process.env.IS_ON_PREMISE + ? composerQueries.useGetDistributionQuery + : serviceQueries.useGetDistributionQuery +) as typeof serviceQueries.useGetDistributionQuery; + export const useBackendPrefetch = process.env.IS_ON_PREMISE ? composerApi.usePrefetch : imageBuilderApi.usePrefetch; diff --git a/src/store/api/backend/onprem/composerApi/distribution.ts b/src/store/api/backend/onprem/composerApi/distribution.ts new file mode 100644 index 0000000000..8ad1ac947d --- /dev/null +++ b/src/store/api/backend/onprem/composerApi/distribution.ts @@ -0,0 +1,35 @@ +import { OnPremBuilder, onPremQueryHandler } from '@/store/api/shared'; + +import { + GetDistributionApiArg, + GetDistributionApiResponse, +} from '../../hosted'; + +export const distributionEndpoints = (builder: OnPremBuilder) => ({ + getDistribution: builder.query< + GetDistributionApiResponse, + GetDistributionApiArg + >({ + queryFn: onPremQueryHandler(async ({ queryArgs, baseQuery }) => { + const params: Record = {}; + if (queryArgs.imageType?.length) { + params['image_type'] = queryArgs.imageType; + } + if (queryArgs.architecture?.length) { + params['architecture'] = queryArgs.architecture; + } + + const result = await baseQuery({ + url: `/distributions/${queryArgs.distro}`, + method: 'GET', + params, + }); + + if (result.error) { + throw result.error; + } + + return result.data as GetDistributionApiResponse; + }), + }), +}); diff --git a/src/store/api/backend/onprem/composerApi/index.ts b/src/store/api/backend/onprem/composerApi/index.ts index dc4e869dc0..343c71705e 100644 --- a/src/store/api/backend/onprem/composerApi/index.ts +++ b/src/store/api/backend/onprem/composerApi/index.ts @@ -1,6 +1,7 @@ import { architectureEndpoints } from './architecture'; import { blueprintEndpoints } from './blueprints'; import { composeEndpoints } from './composes'; +import { distributionEndpoints } from './distribution'; import { oscapEndpoints } from './oscap'; import { workerEndpoints } from './worker'; @@ -12,6 +13,7 @@ export const composerApi = emptyComposerApi.injectEndpoints({ ...architectureEndpoints(builder), ...blueprintEndpoints(builder), ...composeEndpoints(builder), + ...distributionEndpoints(builder), ...oscapEndpoints(builder), ...workerEndpoints(builder), }; @@ -33,6 +35,7 @@ export const { useDeleteBlueprintMutation, useExportBlueprintCockpitQuery, useLazyExportBlueprintCockpitQuery, + useGetDistributionQuery, useGetOscapProfilesQuery, useGetOscapCustomizationsQuery, useLazyGetOscapCustomizationsQuery, diff --git a/src/store/api/distributions/constants.ts b/src/store/api/distributions/constants.ts index a84fc246a9..549179c32c 100644 --- a/src/store/api/distributions/constants.ts +++ b/src/store/api/distributions/constants.ts @@ -1,4 +1,4 @@ -import { CustomizationType, ImageTypeInfo } from './types'; +import { CustomizationType } from './types'; export const ALL_CUSTOMIZATIONS = [ 'packages', @@ -18,68 +18,24 @@ export const ALL_CUSTOMIZATIONS = [ 'aap', ] as const satisfies readonly CustomizationType[]; -// We are just mocking the response until we can actually -// get this information from the API. -export const DISTRO_DETAILS: Record = { - aws: { - name: 'aws', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - azure: { - name: 'azure', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - vhd: { - name: 'vhd', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - gcp: { name: 'gcp', supported_blueprint_options: [...ALL_CUSTOMIZATIONS] }, - 'guest-image': { - name: 'guest-image', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - 'image-installer': { - name: 'image-installer', - supported_blueprint_options: [ - // image-installer has everything but filesystem - ...ALL_CUSTOMIZATIONS.filter((c) => c !== 'filesystem'), - ], - }, - 'bootable-container-iso': { - name: 'bootable-container-iso', - supported_blueprint_options: [], - }, - vsphere: { - name: 'vsphere', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - 'vsphere-ova': { - name: 'vsphere-ova', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - wsl: { - name: 'wsl', - supported_blueprint_options: [ - // wsl has everything but filesystem, openscap & kernel - ...ALL_CUSTOMIZATIONS.filter( - (c) => c !== 'filesystem' && c !== 'kernel' && c !== 'openscap', - ), - ], - }, - ami: { name: 'ami', supported_blueprint_options: [...ALL_CUSTOMIZATIONS] }, - oci: { - name: 'oci', - supported_blueprint_options: [...ALL_CUSTOMIZATIONS], - }, - 'network-installer': { - name: 'network-installer', - supported_blueprint_options: ['locale', 'fips'], - }, - 'pxe-tar-xz': { - name: 'pxe-tar-xz', - supported_blueprint_options: [ - // pxe boot has everything but filesystem - ...ALL_CUSTOMIZATIONS.filter((c) => c !== 'filesystem'), - ], - }, +// Mapping backend naming of customizations to frontend naming. +// A single backend key can map to multiple frontend customization types. +export const BACKEND_TO_FRONTEND_OPTIONS: Record< + string, + CustomizationType | CustomizationType[] +> = { + packages: 'packages', + 'customizations.filesystem': 'filesystem', + 'customizations.kernel': 'kernel', + 'customizations.timezone': 'timezone', + 'customizations.locale': 'locale', + 'customizations.firewall': 'firewall', + 'customizations.services': 'services', + 'customizations.hostname': 'hostname', + 'customizations.openscap': 'openscap', + 'customizations.repositories': 'repositories', + 'customizations.user': 'users', + 'customizations.rhsm': 'registration', + 'customizations.fips': 'fips', + 'customizations.files': ['firstBoot', 'aap'], }; diff --git a/src/store/api/distributions/distributionDetailsApi.ts b/src/store/api/distributions/distributionDetailsApi.ts deleted file mode 100644 index 4a8980fdfe..0000000000 --- a/src/store/api/distributions/distributionDetailsApi.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { imageBuilderApi } from '@/store/api/backend'; - -import { DISTRO_DETAILS } from './constants'; -import { - DistributionDetails, - DistributionDetailsCustomizationApi, - DistributionDetailsCustomizationArgs, -} from './types'; - -export const distroDetailsApi = imageBuilderApi.injectEndpoints({ - endpoints: (builder) => ({ - getDistributionDetails: builder.query< - DistributionDetailsCustomizationApi, - DistributionDetailsCustomizationArgs - >({ - queryFn: ({ distro, architecture, imageType }) => { - const data: DistributionDetails = { - name: distro, - architectures: {}, - }; - - // we define this above, so it's not undefined - const architectures = data.architectures!; - for (const arch of architecture) { - architectures[arch] = { - name: arch, - image_types: {}, - }; - for (const it of imageType) { - // eslint complains about this always being truthy, it's not a - // complete list of image types, so there is a chance it is undefined - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (DISTRO_DETAILS[it]) { - architectures[arch].image_types![it] = DISTRO_DETAILS[it]; - } - } - } - data.architectures = architectures; - - return { - data, - }; - }, - }), - }), -}); diff --git a/src/store/api/distributions/hooks.tsx b/src/store/api/distributions/hooks.tsx index 1377fb57c6..bbd2006265 100644 --- a/src/store/api/distributions/hooks.tsx +++ b/src/store/api/distributions/hooks.tsx @@ -1,7 +1,12 @@ import { useMemo } from 'react'; import { simpleTargetNames } from '@/constants'; -import { ImageTypes } from '@/store/api/backend'; +import { + ArchitectureInfo, + ImageTypeInfo, + ImageTypes, + useGetDistributionQuery, +} from '@/store/api/backend'; import { useAppSelector } from '@/store/hooks'; import { selectIsOnPremise } from '@/store/slices/env'; import { @@ -13,14 +18,26 @@ import { import { isImageType } from '@/store/typeGuards'; import isRhel from '@/Utilities/isRhel'; -import { ALL_CUSTOMIZATIONS } from './constants'; -import { distroDetailsApi as api } from './distributionDetailsApi'; -import { - ArchitectureInfo, - CustomizationType, - ImageTypeInfo, - RestrictionStrategy, -} from './types'; +import { ALL_CUSTOMIZATIONS, BACKEND_TO_FRONTEND_OPTIONS } from './constants'; +import { CustomizationType, RestrictionStrategy } from './types'; + +const normalizeOptions = ( + options: string[] | undefined, +): string[] | undefined => { + if (!options) return undefined; + return options + .flatMap((opt) => BACKEND_TO_FRONTEND_OPTIONS[opt] ?? []) + .filter(Boolean); +}; + +const resolveImageTypeKey = ( + key: string, + aliases: string[] | undefined, +): string => { + if (isImageType(key)) return key; + const match = aliases?.find(isImageType); + return match ?? key; +}; const extractImageTypes = ({ architectures, @@ -31,8 +48,6 @@ const extractImageTypes = ({ }): Record => { if ( !architectures || - // eslint complains about this always being falsy, but there are cases - // where this does actually happen and can cause some rendering issues. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !architectures[arch] || !architectures[arch].image_types @@ -40,7 +55,18 @@ const extractImageTypes = ({ return {}; } - return architectures[arch].image_types; + const raw = architectures[arch].image_types; + const normalized: Record = {}; + for (const [key, value] of Object.entries(raw)) { + const frontendKey = resolveImageTypeKey(key, value.aliases); + normalized[frontendKey] = { + ...value, + supported_blueprint_options: normalizeOptions( + value.supported_blueprint_options, + ), + }; + } + return normalized; }; export type SupportContext = { @@ -122,7 +148,7 @@ export const computeRestrictions = ({ return result; }; -// Instead of exporting the hook directly from the `distributionDetailsApi` +// Instead of exporting the hook directly from the backend API, // let's create a wrapper around this to transform the data so that it is easier // to work with where we need it. This way it will be easy to decide whether we // need to hide the customizations or display an alert, without complex conditionals @@ -137,7 +163,7 @@ export const useCustomizationRestrictions = ({ const isOnPremise = useAppSelector(selectIsOnPremise); const isImageMode = useAppSelector(selectIsImageMode); - const { data } = api.useGetDistributionDetailsQuery({ + const { data } = useGetDistributionQuery({ distro: distro, architecture: [arch], imageType: selectedImageTypes, @@ -213,7 +239,7 @@ export const useImageTypeCustomizationSupport = ( const isImageMode = useAppSelector(selectIsImageMode); const selectedImageTypes = useAppSelector(selectImageTypes); - const { data } = api.useGetDistributionDetailsQuery( + const { data } = useGetDistributionQuery( { distro: distro, architecture: [arch], @@ -224,10 +250,10 @@ export const useImageTypeCustomizationSupport = ( }, ); - if (selectedImageTypes.length === 1) { - // if there is only one image type selected the wizard will - // hide the unsupported steps, so we can just return an empty - // array and labels won't be generated. + if (selectedImageTypes.length <= 1) { + // Labels are only meaningful when multiple image types are selected, + // showing per-target support. With 0 or 1 targets the wizard hides + // unsupported steps entirely, so no labels are needed. return []; } diff --git a/src/store/api/distributions/index.ts b/src/store/api/distributions/index.ts index d37d0d9b6d..6a92faf124 100644 --- a/src/store/api/distributions/index.ts +++ b/src/store/api/distributions/index.ts @@ -1,4 +1,3 @@ -export { ALL_CUSTOMIZATIONS, DISTRO_DETAILS } from './constants'; -export { distroDetailsApi } from './distributionDetailsApi'; +export { ALL_CUSTOMIZATIONS } from './constants'; export * from './hooks'; export * from './types'; diff --git a/src/store/api/distributions/tests/distributionDetailsApi.test.ts b/src/store/api/distributions/tests/distributionDetailsApi.test.ts deleted file mode 100644 index 3e980772f9..0000000000 --- a/src/store/api/distributions/tests/distributionDetailsApi.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { ALL_CUSTOMIZATIONS, type CustomizationType, DISTRO_DETAILS } from '..'; - -describe('DISTRO_DETAILS configuration', () => { - describe('unrestricted image types', () => { - it('should have aws supporting all customizations', () => { - const awsSupported = DISTRO_DETAILS['aws'].supported_blueprint_options; - expect(awsSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have azure supporting all customizations', () => { - const azureSupported = - DISTRO_DETAILS['azure'].supported_blueprint_options; - expect(azureSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have gcp supporting all customizations', () => { - const gcpSupported = DISTRO_DETAILS['gcp'].supported_blueprint_options; - expect(gcpSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have guest-image supporting all customizations', () => { - const guestImageSupported = - DISTRO_DETAILS['guest-image'].supported_blueprint_options; - expect(guestImageSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have vsphere supporting all customizations', () => { - const vsphereSupported = - DISTRO_DETAILS['vsphere'].supported_blueprint_options; - expect(vsphereSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have vsphere-ova supporting all customizations', () => { - const vsphereOvaSupported = - DISTRO_DETAILS['vsphere-ova'].supported_blueprint_options; - expect(vsphereOvaSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have ami supporting all customizations', () => { - const amiSupported = DISTRO_DETAILS['ami'].supported_blueprint_options; - expect(amiSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - - it('should have oci supporting all customizations', () => { - const ociSupported = DISTRO_DETAILS['oci'].supported_blueprint_options; - expect(ociSupported).toEqual([...ALL_CUSTOMIZATIONS]); - }); - }); - - describe('network-installer restrictions', () => { - it('should only allow locale and fips for network-installer', () => { - const networkInstallerSupported = - DISTRO_DETAILS['network-installer'].supported_blueprint_options; - - expect(networkInstallerSupported).toEqual(['locale', 'fips']); - }); - }); - - describe('image-installer restrictions', () => { - it('should allow all customizations except filesystem for image-installer', () => { - const imageInstallerSupported = - DISTRO_DETAILS['image-installer'].supported_blueprint_options; - - // Should not contain filesystem - expect(imageInstallerSupported).not.toContain('filesystem'); - - // Should contain all other types - expect(imageInstallerSupported).toContain('packages'); - expect(imageInstallerSupported).toContain('kernel'); - expect(imageInstallerSupported).toContain('users'); - expect(imageInstallerSupported).toContain('locale'); - expect(imageInstallerSupported).toContain('fips'); - - // Should have length of ALL_CUSTOMIZATIONS - 1 (minus filesystem) - expect(imageInstallerSupported).toHaveLength( - ALL_CUSTOMIZATIONS.length - 1, - ); - }); - }); - - describe('wsl restrictions', () => { - it('should allow all customizations except filesystem, kernel, and openscap for wsl', () => { - const wslSupported = DISTRO_DETAILS['wsl'].supported_blueprint_options; - - // Verify it's an array (not an object due to accidental object spread) - expect(Array.isArray(wslSupported)).toBe(true); - - // Should not contain filesystem, kernel, and openscap - expect(wslSupported).not.toContain('filesystem'); - expect(wslSupported).not.toContain('kernel'); - expect(wslSupported).not.toContain('openscap'); - - // Should have length of ALL_CUSTOMIZATIONS - 3 (minus filesystem, kernel, and openscap) - expect(wslSupported).toHaveLength(ALL_CUSTOMIZATIONS.length - 3); - }); - }); - - describe('bootable-container-iso restrictions', () => { - it('should allow no customizations', () => { - const isoSupported = - DISTRO_DETAILS['bootable-container-iso'].supported_blueprint_options; - - // Verify it's an array (not an object due to accidental object spread) - expect(Array.isArray(isoSupported)).toBe(true); - - // Should be empty - expect(isoSupported).toHaveLength(0); - }); - }); -}); - -describe('ALL_CUSTOMIZATIONS', () => { - it('should contain all expected customization types', () => { - const expectedTypes: CustomizationType[] = [ - 'packages', - 'repositories', - 'filesystem', - 'kernel', - 'timezone', - 'locale', - 'firewall', - 'services', - 'hostname', - 'firstBoot', - 'openscap', - 'registration', - 'users', - 'fips', - 'aap', - ]; - - expect([...ALL_CUSTOMIZATIONS]).toEqual(expectedTypes); - }); - - it('should have 15 customization types', () => { - expect(ALL_CUSTOMIZATIONS).toHaveLength(15); - }); -}); - -describe('DISTRO_DETAILS structure', () => { - it('should have correct structure for each entry', () => { - for (const [_, value] of Object.entries(DISTRO_DETAILS)) { - expect(value).toHaveProperty('name'); - expect(value).toHaveProperty('supported_blueprint_options'); - expect(typeof value.name).toBe('string'); - } - }); - - it('should have entries for all expected image types', () => { - const expectedImageTypes = [ - 'aws', - 'azure', - 'gcp', - 'guest-image', - 'image-installer', - 'bootable-container-iso', - 'vsphere', - 'vsphere-ova', - 'wsl', - 'ami', - 'oci', - 'network-installer', - ]; - - for (const imageType of expectedImageTypes) { - expect(DISTRO_DETAILS[imageType]).toBeDefined(); - } - }); -}); diff --git a/src/store/api/distributions/tests/hooks.test.tsx b/src/store/api/distributions/tests/hooks.test.tsx index 7d38f3f35c..ca7a40ded8 100644 --- a/src/store/api/distributions/tests/hooks.test.tsx +++ b/src/store/api/distributions/tests/hooks.test.tsx @@ -1,4 +1,13 @@ -import { describe, expect, it } from 'vitest'; +import { ImageTypeInfo } from '@/store/api/backend'; +import { + ALL_CUSTOMIZATIONS, + computeImageTypeCustomizationSupport, + computeRestrictions, + CustomizationType, + isCustomizationSupported, + RestrictionStrategy, + SupportContext, +} from '@/store/api/distributions'; import { computeRestrictionStrategy, @@ -10,17 +19,6 @@ import { mockImageTypes, } from './mocks'; -import { - ALL_CUSTOMIZATIONS, - computeImageTypeCustomizationSupport, - computeRestrictions, - CustomizationType, - ImageTypeInfo, - isCustomizationSupported, - RestrictionStrategy, - SupportContext, -} from '..'; - describe('useCustomizationRestrictions hook logic', () => { describe('default behavior (package mode, not on-premise)', () => { it('should not hide any customizations by default', () => { diff --git a/src/store/api/distributions/tests/mocks/fixtures.ts b/src/store/api/distributions/tests/mocks/fixtures.ts index 66b21c0ce4..cde2396379 100644 --- a/src/store/api/distributions/tests/mocks/fixtures.ts +++ b/src/store/api/distributions/tests/mocks/fixtures.ts @@ -1,8 +1,7 @@ +import { DistributionDetails, ImageTypeInfo } from '@/store/api/backend'; import { ALL_CUSTOMIZATIONS, type CustomizationType, - type DistributionDetails, - type ImageTypeInfo, type SupportContext, } from '@/store/api/distributions'; diff --git a/src/store/api/distributions/types.ts b/src/store/api/distributions/types.ts index 84dc9ff986..dc1ee32902 100644 --- a/src/store/api/distributions/types.ts +++ b/src/store/api/distributions/types.ts @@ -1,4 +1,4 @@ -import { Distributions, ImageTypes } from '@/store/api/backend'; +import { ImageTypes } from '@/store/api/backend'; export type CustomizationType = | 'packages' @@ -21,31 +21,6 @@ export type CustomizationRestrictions = Partial< Record >; -export type DistributionDetails = { - name: string; - architectures?: Record; -}; - -export type ArchitectureInfo = { - name: string; - image_types?: Record; -}; - -export type ImageTypeInfo = { - name: string; - aliases?: string[]; - required_blueprint_options?: string[]; - supported_blueprint_options?: string[]; -}; - -export type DistributionDetailsCustomizationArgs = { - distro: Distributions; - architecture: string[]; - imageType: ImageTypes[]; -}; - -export type DistributionDetailsCustomizationApi = DistributionDetails; - export type RestrictionStrategy = { shouldHide: boolean; required: boolean; diff --git a/src/store/api/index.ts b/src/store/api/index.ts index a17b48961e..9c2d9159ae 100644 --- a/src/store/api/index.ts +++ b/src/store/api/index.ts @@ -1,6 +1,5 @@ export { composerApi, imageBuilderApi } from './backend'; export { complianceApi } from './compliance'; export { contentSourcesApi } from './contentSources'; -export { distroDetailsApi } from './distributions'; export { rhsmApi } from './rhsm'; export { emptyOnPremApi } from './shared';