diff --git a/api/config/imageBuilder.ts b/api/config/imageBuilder.ts index b18c29a0..34f82fa8 100644 --- a/api/config/imageBuilder.ts +++ b/api/config/imageBuilder.ts @@ -28,6 +28,7 @@ const config: ConfigFile = { 'deleteBlueprint', 'getBlueprint', 'recommendPackage', + 'fixupBlueprint', ], }; diff --git a/api/schema/imageBuilder.yaml b/api/schema/imageBuilder.yaml index 571159cc..ed4c5e21 100644 --- a/api/schema/imageBuilder.yaml +++ b/api/schema/imageBuilder.yaml @@ -722,6 +722,27 @@ paths: application/json: schema: $ref: "#/components/schemas/RecommendationsResponse" + /experimental/blueprints/{id}/fixup: + parameters: + - in: path + name: id + schema: + type: string + format: uuid + example: '123e4567-e89b-12d3-a456-426655440000' + required: true + description: UUID of a blueprint + post: + summary: Apply linter fixes to blueprint + operationId: fixupBlueprint + description: | + Apply fixes which should fix any lint errors in the blueprint. + responses: + 200: + description: successful update + 404: + description: blueprint was not found + components: schemas: HTTPError: @@ -1028,6 +1049,7 @@ components: - id - name - description + - lint - distribution - image_requests - customizations @@ -1039,6 +1061,8 @@ components: type: string description: type: string + lint: + $ref: "#/components/schemas/BlueprintLint" distribution: $ref: '#/components/schemas/Distributions' image_requests: @@ -1097,6 +1121,29 @@ components: is_on_prem: type: boolean default: false + BlueprintLint: + required: + - errors + description: | + Linting errors in the current blueprint, these might need to be resolved before the + blueprint can be used to build images again. + properties: + errors: + type: array + items: + $ref: '#/components/schemas/BlueprintLintItem' + BlueprintLintItem: + type: object + required: + - name + - description + properties: + name: + type: string + example: Compliance + description: + type: string + example: package a required by policy is not present Distributions: type: string description: | @@ -1116,6 +1163,8 @@ components: - rhel-8.10 - rhel-9 - rhel-9-nightly + - rhel-9.6-nightly + - rhel-9.7-nightly - rhel-9-beta - rhel-90 - rhel-91 @@ -1124,6 +1173,8 @@ components: - rhel-94 - rhel-95 - rhel-10-nightly + - rhel-10.0-nightly + - rhel-10.1-nightly - rhel-10-beta - centos-9 - centos-10 @@ -1588,6 +1639,12 @@ components: example: ['postgresql'] items: type: string + enabled_modules: + type: array + description: | + List of dnf modules to enable, so that packages can be installed from them. + items: + $ref: '#/components/schemas/Module' payload_repositories: type: array items: @@ -2118,3 +2175,20 @@ components: type: boolean module_hotfixes: type: boolean + Module: + type: object + required: + - name + - stream + additionalProperties: false + properties: + name: + type: string + example: 'nodejs' + description: | + Name of the module to enable. + stream: + type: string + example: '22' + description: | + Stream to enable. diff --git a/src/Components/ImagesTable/Release.tsx b/src/Components/ImagesTable/Release.tsx index 1b5c692a..6cbde5e6 100644 --- a/src/Components/ImagesTable/Release.tsx +++ b/src/Components/ImagesTable/Release.tsx @@ -20,6 +20,8 @@ const Release = ({ release }: ReleaseProps) => { 'rhel-9': 'RHEL 9', 'rhel-9-beta': 'RHEL 9 Beta', 'rhel-9-nightly': 'RHEL 9', + 'rhel-9.6-nightly': 'RHEL 9', + 'rhel-9.7-nightly': 'RHEL 9', 'rhel-90': 'RHEL 9.0', 'rhel-91': 'RHEL 9.1', 'rhel-92': 'RHEL 9.2', @@ -27,6 +29,8 @@ const Release = ({ release }: ReleaseProps) => { 'rhel-94': 'RHEL 9.4', 'rhel-95': 'RHEL 9.5', 'rhel-10-nightly': 'RHEL 10', + 'rhel-10.0-nightly': 'RHEL 10', + 'rhel-10.1-nightly': 'RHEL 10', 'rhel-10-beta': 'RHEL 10 Beta', 'centos-8': 'CentOS Stream 8', 'centos-9': 'CentOS Stream 9', diff --git a/src/store/cockpit/cockpitApi.ts b/src/store/cockpit/cockpitApi.ts index b6f62b2a..c49481e7 100644 --- a/src/store/cockpit/cockpitApi.ts +++ b/src/store/cockpit/cockpitApi.ts @@ -171,6 +171,10 @@ export const cockpitApi = contentSourcesApi.injectEndpoints({ id, version: version, last_modified_at: new Date(bpInfo!.mtime * 1000).toString(), + // linting is not supported on prem + lint: { + errors: [], + }, }, }; } catch (error) { diff --git a/src/store/service/enhancedImageBuilderApi.ts b/src/store/service/enhancedImageBuilderApi.ts index 14efa0d6..18b20130 100644 --- a/src/store/service/enhancedImageBuilderApi.ts +++ b/src/store/service/enhancedImageBuilderApi.ts @@ -222,6 +222,31 @@ const enhancedApi = imageBuilderApi.enhanceEndpoints({ }); }, }, + fixupBlueprint: { + invalidatesTags: [{ type: 'Blueprint' }], + onQueryStarted: async (_, { dispatch, queryFulfilled }) => { + queryFulfilled + .then(() => { + dispatch( + addNotification({ + variant: 'success', + title: 'Blueprint was fixed', + }) + ); + }) + .catch((err) => { + dispatch( + addNotification({ + variant: 'danger', + title: 'Blueprint could not be fixed', + description: `Status code ${err.error.status}: ${errorMessage( + err + )}`, + }) + ); + }); + }, + }, }, }); diff --git a/src/store/service/imageBuilderApi.ts b/src/store/service/imageBuilderApi.ts index c33d5e14..fe083c53 100644 --- a/src/store/service/imageBuilderApi.ts +++ b/src/store/service/imageBuilderApi.ts @@ -171,6 +171,15 @@ const injectedRtkApi = api.injectEndpoints({ body: queryArg.recommendPackageRequest, }), }), + fixupBlueprint: build.mutation< + FixupBlueprintApiResponse, + FixupBlueprintApiArg + >({ + query: (queryArg) => ({ + url: `/experimental/blueprints/${queryArg.id}/fixup`, + method: "POST", + }), + }), }), overrideExisting: false, }); @@ -335,6 +344,11 @@ export type RecommendPackageApiResponse = export type RecommendPackageApiArg = { recommendPackageRequest: RecommendPackageRequest; }; +export type FixupBlueprintApiResponse = unknown; +export type FixupBlueprintApiArg = { + /** UUID of a blueprint */ + id: string; +}; export type Repository = { /** An ID referring to a repository defined in content sources can be used instead of 'baseurl', 'mirrorlist' or 'metalink'. @@ -378,6 +392,8 @@ export type Distributions = | "rhel-8.10" | "rhel-9" | "rhel-9-nightly" + | "rhel-9.6-nightly" + | "rhel-9.7-nightly" | "rhel-9-beta" | "rhel-90" | "rhel-91" @@ -386,6 +402,8 @@ export type Distributions = | "rhel-94" | "rhel-95" | "rhel-10-nightly" + | "rhel-10.0-nightly" + | "rhel-10.1-nightly" | "rhel-10-beta" | "centos-9" | "centos-10" @@ -578,6 +596,14 @@ export type Subscription = { */ rhc?: boolean | undefined; }; +export type Module = { + /** Name of the module to enable. + */ + name: string; + /** Stream to enable. + */ + stream: string; +}; export type CustomRepository = { id: string; name?: string | undefined; @@ -711,6 +737,9 @@ export type Customizations = { files?: File[] | undefined; subscription?: Subscription | undefined; packages?: string[] | undefined; + /** List of dnf modules to enable, so that packages can be installed from them. + */ + enabled_modules?: Module[] | undefined; payload_repositories?: Repository[] | undefined; /** List of custom repositories. */ custom_repositories?: CustomRepository[] | undefined; @@ -759,10 +788,18 @@ export type CreateBlueprintRequest = { customizations: Customizations; metadata?: BlueprintMetadata | undefined; }; +export type BlueprintLintItem = { + name: string; + description: string; +}; +export type BlueprintLint = { + errors: BlueprintLintItem[]; +}; export type BlueprintResponse = { id: string; name: string; description: string; + lint: BlueprintLint; distribution: Distributions; /** Array of image requests. Having more image requests in a single blueprint is currently not supported. */ @@ -959,4 +996,5 @@ export const { useGetOscapCustomizationsQuery, useLazyGetOscapCustomizationsQuery, useRecommendPackageMutation, + useFixupBlueprintMutation, } = injectedRtkApi; diff --git a/src/test/fixtures/blueprints.ts b/src/test/fixtures/blueprints.ts index 44773af6..a148a9c0 100644 --- a/src/test/fixtures/blueprints.ts +++ b/src/test/fixtures/blueprints.ts @@ -545,6 +545,9 @@ export const multipleTargetsBlueprintResponse: GetBlueprintApiResponse = { rhc: true, }, }, + lint: { + errors: [], + }, }; export const darkChocolateBlueprintResponse: GetBlueprintApiResponse = { @@ -561,4 +564,7 @@ export const darkChocolateBlueprintResponse: GetBlueprintApiResponse = { rhc: true, }, }, + lint: { + errors: [], + }, }; diff --git a/src/test/fixtures/editMode.ts b/src/test/fixtures/editMode.ts index 18d28fb4..0879ab7a 100644 --- a/src/test/fixtures/editMode.ts +++ b/src/test/fixtures/editMode.ts @@ -186,6 +186,9 @@ export const rhel9BlueprintResponse: BlueprintResponse = { ...rhel9CreateBlueprintRequest, id: mockBlueprintIds['rhel9'], description: mockBlueprintDescriptions['rhel9'], + lint: { + errors: [], + }, }; export const rhel8CreateBlueprintRequest: CreateBlueprintRequest = { @@ -200,6 +203,9 @@ export const rhel8BlueprintResponse: BlueprintResponse = { ...rhel8CreateBlueprintRequest, id: mockBlueprintIds['rhel8'], description: mockBlueprintDescriptions['rhel8'], + lint: { + errors: [], + }, }; export const centos9CreateBlueprintRequest: CreateBlueprintRequest = { @@ -214,6 +220,9 @@ export const centos9BlueprintResponse: BlueprintResponse = { ...centos9CreateBlueprintRequest, id: mockBlueprintIds['centos9'], description: mockBlueprintDescriptions['centos9'], + lint: { + errors: [], + }, }; export const x86_64ImageRequest: ImageRequest = { @@ -232,6 +241,9 @@ export const x86_64BlueprintResponse: BlueprintResponse = { ...x86_64CreateBlueprintRequest, id: mockBlueprintIds['x86_64'], description: mockBlueprintDescriptions['x86_64'], + lint: { + errors: [], + }, }; export const aarch64ImageRequest: ImageRequest = { @@ -250,6 +262,9 @@ export const aarch64BlueprintResponse: BlueprintResponse = { ...aarch64CreateBlueprintRequest, id: mockBlueprintIds['aarch64'], description: mockBlueprintDescriptions['aarch64'], + lint: { + errors: [], + }, }; export const awsImageRequest: ImageRequest = { @@ -274,6 +289,9 @@ export const awsBlueprintResponse: BlueprintResponse = { ...awsCreateBlueprintRequest, id: mockBlueprintIds['aws'], description: mockBlueprintDescriptions['aws'], + lint: { + errors: [], + }, }; export const gcpImageRequest: ImageRequest = { @@ -298,6 +316,9 @@ export const gcpBlueprintResponse: BlueprintResponse = { ...gcpCreateBlueprintRequest, id: mockBlueprintIds['gcp'], description: mockBlueprintDescriptions['gcp'], + lint: { + errors: [], + }, }; export const azureImageRequest: ImageRequest = { @@ -324,6 +345,9 @@ export const azureBlueprintResponse: BlueprintResponse = { ...azureCreateBlueprintRequest, id: mockBlueprintIds['azure'], description: mockBlueprintDescriptions['azure'], + lint: { + errors: [], + }, }; export const registrationCreateBlueprintRequest: CreateBlueprintRequest = { @@ -339,6 +363,9 @@ export const registrationBlueprintResponse: BlueprintResponse = { ...registrationCreateBlueprintRequest, id: mockBlueprintIds['registration'], description: mockBlueprintDescriptions['registration'], + lint: { + errors: [], + }, }; export const oscapCreateBlueprintRequest: CreateBlueprintRequest = { @@ -358,6 +385,9 @@ export const oscapBlueprintResponse: BlueprintResponse = { ...oscapCreateBlueprintRequest, id: mockBlueprintIds['oscap'], description: mockBlueprintDescriptions['oscap'], + lint: { + errors: [], + }, }; export const fscCreateBlueprintRequest: CreateBlueprintRequest = { @@ -373,6 +403,9 @@ export const fscBlueprintResponse: BlueprintResponse = { ...fscCreateBlueprintRequest, id: mockBlueprintIds['fsc'], description: mockBlueprintDescriptions['fsc'], + lint: { + errors: [], + }, }; export const snapshotCreateBlueprintRequest: CreateBlueprintRequest = { @@ -389,6 +422,9 @@ export const snapshotBlueprintResponse: BlueprintResponse = { ...snapshotCreateBlueprintRequest, id: mockBlueprintIds['snapshot'], description: mockBlueprintDescriptions['snapshot'], + lint: { + errors: [], + }, }; export const repositoriesCreateBlueprintRequest: CreateBlueprintRequest = { @@ -405,6 +441,9 @@ export const repositoriesBlueprintResponse: BlueprintResponse = { ...repositoriesCreateBlueprintRequest, id: mockBlueprintIds['repositories'], description: mockBlueprintDescriptions['repositories'], + lint: { + errors: [], + }, }; export const packagesCreateBlueprintRequest: CreateBlueprintRequest = { @@ -420,6 +459,9 @@ export const packagesBlueprintResponse: BlueprintResponse = { ...packagesCreateBlueprintRequest, id: mockBlueprintIds['packages'], description: mockBlueprintDescriptions['packages'], + lint: { + errors: [], + }, }; export const timezoneCreateBlueprintRequest: CreateBlueprintRequest = { @@ -438,6 +480,9 @@ export const timezoneBlueprintResponse: BlueprintResponse = { ...timezoneCreateBlueprintRequest, id: mockBlueprintIds['timezone'], description: mockBlueprintDescriptions['timezone'], + lint: { + errors: [], + }, }; export const usersCreateBlueprintRequest: CreateBlueprintRequest = { @@ -459,6 +504,9 @@ export const usersBlueprintResponse: BlueprintResponse = { ...usersCreateBlueprintRequest, id: mockBlueprintIds['users'], description: mockBlueprintDescriptions['users'], + lint: { + errors: [], + }, }; export const localeCreateBlueprintRequest: CreateBlueprintRequest = { @@ -477,6 +525,9 @@ export const localeBlueprintResponse: BlueprintResponse = { ...localeCreateBlueprintRequest, id: mockBlueprintIds['locale'], description: mockBlueprintDescriptions['locale'], + lint: { + errors: [], + }, }; export const hostnameCreateBlueprintRequest: CreateBlueprintRequest = { @@ -492,6 +543,9 @@ export const hostnameBlueprintResponse: BlueprintResponse = { ...hostnameCreateBlueprintRequest, id: mockBlueprintIds['hostname'], description: mockBlueprintDescriptions['hostname'], + lint: { + errors: [], + }, }; export const kernelCreateBlueprintRequest: CreateBlueprintRequest = { @@ -510,6 +564,9 @@ export const kernelBlueprintResponse: BlueprintResponse = { ...kernelCreateBlueprintRequest, id: mockBlueprintIds['kernel'], description: mockBlueprintDescriptions['kernel'], + lint: { + errors: [], + }, }; export const firewallCreateBlueprintRequest: CreateBlueprintRequest = { @@ -539,6 +596,9 @@ export const firewallBlueprintResponse: BlueprintResponse = { ...firewallCreateBlueprintRequest, id: mockBlueprintIds['firewall'], description: mockBlueprintDescriptions['firewall'], + lint: { + errors: [], + }, }; export const servicesCreateBlueprintRequest: CreateBlueprintRequest = { @@ -554,6 +614,9 @@ export const servicesBlueprintResponse: BlueprintResponse = { ...servicesCreateBlueprintRequest, id: mockBlueprintIds['services'], description: mockBlueprintDescriptions['services'], + lint: { + errors: [], + }, }; export const firstBootCreateBlueprintRequest: CreateBlueprintRequest = { @@ -570,6 +633,9 @@ export const firstBootBlueprintResponse: BlueprintResponse = { ...firstBootCreateBlueprintRequest, id: mockBlueprintIds['firstBoot'], description: mockBlueprintDescriptions['firstBoot'], + lint: { + errors: [], + }, }; export const detailsCreateBlueprintRequest: CreateBlueprintRequest = { @@ -583,6 +649,9 @@ export const detailsBlueprintResponse: BlueprintResponse = { ...detailsCreateBlueprintRequest, id: mockBlueprintIds['details'], description: mockBlueprintDescriptions['details'], + lint: { + errors: [], + }, }; export const complianceCreateBlueprintRequest: CreateBlueprintRequest = { @@ -604,6 +673,9 @@ export const complianceBlueprintResponse: BlueprintResponse = { ...complianceCreateBlueprintRequest, id: mockBlueprintIds['compliance'], description: mockBlueprintDescriptions['compliance'], + lint: { + errors: [], + }, }; export const getMockBlueprintResponse = (id: string) => {