diff --git a/src/Components/Blueprints/helpers/onPremToHostedBlueprintMapper.tsx b/src/Components/Blueprints/helpers/onPremToHostedBlueprintMapper.tsx index 993c0a50..aa3ab462 100644 --- a/src/Components/Blueprints/helpers/onPremToHostedBlueprintMapper.tsx +++ b/src/Components/Blueprints/helpers/onPremToHostedBlueprintMapper.tsx @@ -1,6 +1,7 @@ import { BlueprintExportResponse, Container, + CreateBlueprintRequest, Directory, Distributions, Fdo, @@ -20,7 +21,7 @@ export type BlueprintOnPrem = { description?: string; packages?: PackagesOnPrem[]; groups?: GroupsPackagesOnPrem[]; - distro: Distributions; + distro?: Distributions; customizations?: CustomizationsOnPrem; containers?: Container[]; }; @@ -85,7 +86,7 @@ export type UserOnPrem = { export type GroupOnPrem = { name: string; - gid: number; + gid: number | undefined; }; export type SshKeyOnPrem = { @@ -115,7 +116,7 @@ export const mapOnPremToHosted = ( return { name: blueprint.name, description: blueprint.description || '', - distribution: blueprint.distro, + distribution: blueprint.distro!, customizations: { ...blueprint.customizations, containers: blueprint.containers, @@ -176,3 +177,119 @@ export const mapOnPremToHosted = ( }, }; }; + +export const mapHostedToOnPrem = ( + blueprint: CreateBlueprintRequest +): BlueprintOnPrem => { + const result: BlueprintOnPrem = { + name: blueprint.name, + customizations: {}, + }; + + if (blueprint.customizations?.packages) { + result.packages = blueprint.customizations.packages.map((pkg) => { + return { + name: pkg, + version: '*', + }; + }); + } + + if (blueprint.customizations?.containers) { + result.containers = blueprint.customizations.containers; + } + + if (blueprint.customizations?.directories) { + result.customizations!.directories = blueprint.customizations.directories; + } + + if (blueprint.customizations?.files) { + result.customizations!.files = blueprint.customizations.files; + } + + if (blueprint.customizations?.openscap) { + result.customizations!.openscap = blueprint.customizations.openscap; + } + + if (blueprint.customizations?.filesystem) { + result.customizations!.filesystem = blueprint.customizations.filesystem.map( + (fs) => { + return { + mountpoint: fs.mountpoint, + minsize: fs.min_size, + }; + } + ); + } + + if (blueprint.customizations?.users) { + result.customizations!.user = blueprint.customizations.users.map((u) => { + return { + name: u.name, + key: u.ssh_key || '', + }; + }); + } + + if (blueprint.customizations?.services) { + result.customizations!.services = blueprint.customizations.services; + } + + if (blueprint.customizations?.hostname) { + result.customizations!.hostname = blueprint.customizations.hostname; + } + + if (blueprint.customizations?.kernel) { + result.customizations!.kernel = blueprint.customizations.kernel; + } + + if (blueprint.customizations?.groups) { + result.customizations!.groups = blueprint.customizations.groups.map((g) => { + return { + name: g.name, + gid: g.gid, + }; + }); + } + + if (blueprint.customizations?.timezone) { + result.customizations!.timezone = blueprint.customizations.timezone; + } + + if (blueprint.customizations?.locale) { + result.customizations!.locale = blueprint.customizations.locale; + } + + if (blueprint.customizations?.firewall) { + result.customizations!.firewall = blueprint.customizations.firewall; + } + + if (blueprint.customizations?.installation_device) { + result.customizations!.installation_device = + blueprint.customizations.installation_device; + } + + if (blueprint.customizations?.fdo) { + result.customizations!.fdo = blueprint.customizations.fdo; + } + + if (blueprint.customizations?.ignition) { + result.customizations!.ignition = blueprint.customizations.ignition; + } + + if (blueprint.customizations?.partitioning_mode) { + result.customizations!.partitioning_mode = + blueprint.customizations.partitioning_mode; + } + + if (blueprint.customizations?.fips) { + result.customizations!.fips = + blueprint.customizations.fips?.enabled || false; + } + + if (blueprint.customizations?.installer) { + result.customizations!.installer = blueprint.customizations.installer; + } + + return result; +}; diff --git a/src/store/backendApi.ts b/src/store/backendApi.ts index aae3f9f8..e2103080 100644 --- a/src/store/backendApi.ts +++ b/src/store/backendApi.ts @@ -36,6 +36,10 @@ export const useListSnapshotsByDateMutation = process.env.IS_ON_PREMISE ? cockpitQueries.useListSnapshotsByDateMutation : useContentSourcesListSnapshotsByDateMutation; +export const useComposeBlueprintMutation = process.env.IS_ON_PREMISE + ? cockpitQueries.useComposeBlueprintMutation + : imageBuilderQueries.useComposeBlueprintMutation; + export const useBackendPrefetch = process.env.IS_ON_PREMISE ? cockpitApi.usePrefetch : imageBuilderApi.usePrefetch; diff --git a/src/store/cockpitApi.ts b/src/store/cockpitApi.ts index ede0ba04..35c7db61 100644 --- a/src/store/cockpitApi.ts +++ b/src/store/cockpitApi.ts @@ -8,7 +8,6 @@ import path from 'path'; // We also needed to create an alias in vitest to make this work. import cockpit from 'cockpit'; import { fsinfo } from 'cockpit/fsinfo'; -import toml from 'toml'; import { ListSnapshotsByDateApiArg, @@ -16,6 +15,9 @@ import { } from './contentSourcesApi'; import { emptyCockpitApi } from './emptyCockpitApi'; import { + ComposeBlueprintApiResponse, + ComposeBlueprintApiArg, + CreateBlueprintRequest, GetArchitecturesApiResponse, GetArchitecturesApiArg, GetBlueprintsApiArg, @@ -29,9 +31,10 @@ import { GetBlueprintApiArg, CreateBlueprintApiResponse, CreateBlueprintApiArg, + ComposeResponse, } from './imageBuilderApi'; -import { mapOnPremToHosted } from '../Components/Blueprints/helpers/onPremToHostedBlueprintMapper'; +import { mapHostedToOnPrem } from '../Components/Blueprints/helpers/onPremToHostedBlueprintMapper'; import { BLUEPRINTS_DIR } from '../constants'; const getBlueprintsPath = async () => { @@ -125,16 +128,17 @@ export const cockpitApi = emptyCockpitApi.injectEndpoints({ const entries = Object.entries(info?.entries || {}); let blueprints: BlueprintItem[] = await Promise.all( entries.map(async ([filename]) => { - const file = cockpit.file(path.join(blueprintsDir, filename)); + const file = cockpit.file( + path.join(blueprintsDir, filename, `${filename}.json`) + ); const contents = await file.read(); - const parsed = toml.parse(contents); + const parsed = JSON.parse(contents); file.close(); - const blueprint = mapOnPremToHosted(parsed); const version = (parsed.version as number) ?? 1; return { - ...blueprint, + ...parsed, id: filename as string, version: version, last_modified_at: Date.now().toString(), @@ -249,6 +253,80 @@ export const cockpitApi = emptyCockpitApi.injectEndpoints({ }, }), }), + composeBlueprint: builder.mutation< + ComposeBlueprintApiResponse, + ComposeBlueprintApiArg + >({ + queryFn: async ({ id: filename }) => { + try { + const blueprintsDir = await getBlueprintsPath(); + const file = cockpit.file( + path.join(blueprintsDir, filename, `${filename}.json`) + ); + const contents = await file.read(); + const parsed = JSON.parse(contents); + + const cloudapi = cockpit.http('/run/cloudapi/api.socket', { + superuser: 'try', + }); + + const createBPReq = parsed as CreateBlueprintRequest; + const blueprint = mapHostedToOnPrem(createBPReq); + const composes: ComposeResponse[] = []; + for (const ir of parsed.image_requests) { + const composeReq = { + distribution: createBPReq.distribution, + blueprint: blueprint, + image_requests: [ + { + architecture: ir.architecture, + image_type: ir.image_type, + repositories: [], + upload_targets: [ + { + type: 'local', + upload_options: {}, + }, + ], + }, + ], + }; + const saveReq = { + distribution: createBPReq.distribution, + blueprint: parsed, + image_requests: [ + { + architecture: ir.architecture, + image_type: ir.image_type, + repositories: [], + upload_request: { + type: 'local', + options: {}, + }, + }, + ], + }; + const resp = await cloudapi.post( + '/api/image-builder-composer/v2/compose', + composeReq, + { + 'content-type': 'application/json', + } + ); + const composeResp = JSON.parse(resp); + await cockpit + .file(path.join(blueprintsDir, filename, composeResp.id)) + .replace(JSON.stringify(saveReq)); + composes.push({ id: composeResp.id }); + } + return { + data: composes, + }; + } catch (error) { + return { error }; + } + }, + }), }; }, }); @@ -262,4 +340,5 @@ export const { useDeleteBlueprintMutation, useGetOscapProfilesQuery, useListSnapshotsByDateMutation, + useComposeBlueprintMutation, } = cockpitApi;