debian-image-builder-frontend/src/Components/CreateImageWizard/utilities/requestMapper.ts
Ondřej Budai 1bfc830147 remove as much edge mgmt code as possible
We no longer include the edge mgmt federated module so let's remove
all remaining traces of the integration.
2025-08-01 09:00:02 +00:00

895 lines
26 KiB
TypeScript

import { Store } from 'redux';
import { v4 as uuidv4 } from 'uuid';
import { parseSizeUnit } from './parseSizeUnit';
import {
CENTOS_9,
FIRST_BOOT_SERVICE_DATA,
FIRSTBOOT_PATH,
FIRSTBOOT_SERVICE_PATH,
RHEL_10,
RHEL_8,
RHEL_9,
SATELLITE_PATH,
SATELLITE_SERVICE_DATA,
SATELLITE_SERVICE_PATH,
} from '../../../constants';
import { RootState } from '../../../store';
import {
CockpitAwsUploadRequestOptions,
CockpitCreateBlueprintRequest,
CockpitImageRequest,
CockpitUploadTypes,
} from '../../../store/cockpit/types';
import {
AwsUploadRequestOptions,
AzureUploadRequestOptions,
BlueprintExportResponse,
BlueprintResponse,
CreateBlueprintRequest,
Customizations,
CustomRepository,
DistributionProfileItem,
Distributions,
File,
Filesystem,
GcpUploadRequestOptions,
ImageRequest,
ImageTypes,
OpenScap,
OpenScapCompliance,
OpenScapProfile,
Services,
Subscription,
UploadTypes,
User,
} from '../../../store/imageBuilderApi';
import { ApiRepositoryImportResponseRead } from '../../../store/service/contentSourcesApi';
import {
ComplianceType,
initialState,
selectActivationKey,
selectArchitecture,
selectAwsAccountId,
selectAwsRegion,
selectAwsShareMethod,
selectAwsSourceId,
selectAzureHyperVGeneration,
selectAzureResourceGroup,
selectAzureShareMethod,
selectAzureSource,
selectAzureSubscriptionId,
selectAzureTenantId,
selectBaseUrl,
selectBlueprintDescription,
selectBlueprintName,
selectCompliancePolicyID,
selectComplianceProfileID,
selectComplianceType,
selectCustomRepositories,
selectDistribution,
selectFileSystemConfigurationType,
selectFirewall,
selectFirstBootScript,
selectGcpAccountType,
selectGcpEmail,
selectGcpShareMethod,
selectGroups,
selectHostname,
selectImageTypes,
selectKernel,
selectKeyboard,
selectLanguages,
selectMetadata,
selectModules,
selectNtpServers,
selectPackages,
selectPartitions,
selectPayloadRepositories,
selectRecommendedRepositories,
selectRegistrationType,
selectSatelliteCaCertificate,
selectSatelliteRegistrationCommand,
selectServerUrl,
selectServices,
selectSnapshotDate,
selectTemplate,
selectTemplateName,
selectTimezone,
selectUseLatest,
selectUsers,
wizardState,
} from '../../../store/wizardSlice';
import isRhel from '../../../Utilities/isRhel';
import { FileSystemConfigurationType } from '../steps/FileSystem';
import {
getConversionFactor,
Partition,
Units,
} from '../steps/FileSystem/components/FileSystemTable';
import { PackageRepository } from '../steps/Packages/Packages';
import {
convertSchemaToIBCustomRepo,
convertSchemaToIBPayloadRepo,
} from '../steps/Repositories/components/Utilities';
import { AwsShareMethod } from '../steps/TargetEnvironment/Aws';
import { AzureShareMethod } from '../steps/TargetEnvironment/Azure';
import { GcpAccountType, GcpShareMethod } from '../steps/TargetEnvironment/Gcp';
/**
* This function maps the wizard state to a valid CreateBlueprint request object
* @param {Store} store redux store
* @param {string} orgID organization ID
*
* @returns {CreateBlueprintRequest} blueprint creation request payload
*/
export const mapRequestFromState = (
store: Store,
orgID: string
): CreateBlueprintRequest | CockpitCreateBlueprintRequest => {
const state = store.getState();
const imageRequests = getImageRequests(state);
const customizations = getCustomizations(state, orgID);
return {
name: selectBlueprintName(state),
metadata: selectMetadata(state),
description: selectBlueprintDescription(state),
distribution: selectDistribution(state),
image_requests: imageRequests,
customizations,
};
};
const convertFilesystemToPartition = (filesystem: Filesystem): Partition => {
const id = uuidv4();
const [size, unit] = parseSizeUnit(filesystem.min_size);
const partition = {
mountpoint: filesystem.mountpoint,
min_size: size,
id: id,
unit: unit as Units,
};
return partition;
};
/**
* This function overwrites distribution of the blueprints with the major release
* and deprecated CentOS 8 with CentOS 9
* Minor releases were previously used and are still present in older blueprints
* @param distribution blueprint distribution
*/
const getLatestRelease = (distribution: Distributions) => {
return distribution.startsWith('rhel-10')
? RHEL_10
: distribution.startsWith('rhel-9')
? RHEL_9
: distribution.startsWith('rhel-8')
? RHEL_8
: distribution === ('centos-8' as Distributions)
? CENTOS_9
: distribution;
};
function commonRequestToState(
request: BlueprintResponse | CreateBlueprintRequest
) {
const gcp = request.image_requests.find(
(image) => image.image_type === 'gcp'
);
const aws = request.image_requests.find(
(image) => image.image_type === 'aws'
);
const azure = request.image_requests.find(
(image) => image.image_type === 'azure'
);
const snapshotDateFromRequest =
request.image_requests.find((image) => !!image.snapshot_date)
?.snapshot_date || '';
let snapshot_date = '';
// Previously DateOnly format of the snapshot date was used (YYYY-MM-DD),
// meaning this is a format that can be present in already existing blueprints.
// Currently used format is RFC3339 (YYYY-MM-DDTHH:MM:SSZ), this condition
// checks which format is getting parsed and converts DateOnly to RFC3339
// when necessary.
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(snapshotDateFromRequest)) {
snapshot_date = snapshotDateFromRequest;
} else if (/^\d{4}-\d{2}-\d{2}$/.test(snapshotDateFromRequest)) {
snapshot_date = snapshotDateFromRequest + 'T00:00:00Z';
} else {
snapshot_date = '';
}
const awsUploadOptions = aws?.upload_request
.options as AwsUploadRequestOptions;
const gcpUploadOptions = gcp?.upload_request
.options as GcpUploadRequestOptions;
const azureUploadOptions = azure?.upload_request
.options as AzureUploadRequestOptions;
const arch =
request.image_requests[0]?.architecture || initialState.architecture;
if (arch !== 'x86_64' && arch !== 'aarch64') {
throw new Error(`image type: ${arch} has no implementation yet`);
}
let oscapProfile = undefined;
let compliancePolicyID = undefined;
if (request.customizations?.openscap) {
const oscapAsProfile = request.customizations?.openscap as OpenScapProfile;
if (oscapAsProfile.profile_id !== '') {
oscapProfile = oscapAsProfile.profile_id as DistributionProfileItem;
}
const oscapAsCompliance = request.customizations
?.openscap as OpenScapCompliance;
if (oscapAsCompliance.policy_id !== '') {
compliancePolicyID = oscapAsCompliance.policy_id;
}
}
return {
details: {
blueprintName: request.name || '',
isCustomName: true,
blueprintDescription: request.description || '',
},
users:
request.customizations.users?.map((user) => ({
name: user.name,
password: '', // The image-builder API does not return the password.
ssh_key: user.ssh_key || '',
groups: user.groups || [],
isAdministrator: user.groups?.includes('wheel') || false,
hasPassword: user.hasPassword || false,
})) || [],
compliance:
compliancePolicyID !== undefined
? {
complianceType: 'compliance' as ComplianceType,
policyID: compliancePolicyID,
profileID: undefined,
policyTitle: undefined,
}
: oscapProfile !== undefined
? {
complianceType: 'openscap' as ComplianceType,
profileID: oscapProfile,
policyID: undefined,
policyTitle: undefined,
}
: initialState.compliance,
firstBoot: request.customizations
? {
script: getFirstBootScript(request.customizations.files),
}
: initialState.firstBoot,
fileSystem: request.customizations?.filesystem
? {
mode: 'manual' as FileSystemConfigurationType,
partitions: request.customizations?.filesystem.map((fs) =>
convertFilesystemToPartition(fs)
),
}
: {
mode: 'automatic' as FileSystemConfigurationType,
partitions: [],
},
architecture: arch,
distribution:
getLatestRelease(request.distribution) || initialState.distribution,
imageTypes: request.image_requests.map((image) => image.image_type),
azure: {
shareMethod: (azureUploadOptions?.source_id
? 'sources'
: 'manual') as AzureShareMethod,
source: azureUploadOptions?.source_id || '',
tenantId: azureUploadOptions?.tenant_id || '',
subscriptionId: azureUploadOptions?.subscription_id || '',
resourceGroup: azureUploadOptions?.resource_group,
hyperVGeneration: azureUploadOptions?.hyper_v_generation || 'V1',
},
gcp: {
shareMethod: (gcpUploadOptions?.share_with_accounts
? 'withGoogle'
: 'withInsights') as GcpShareMethod,
accountType: gcpUploadOptions?.share_with_accounts?.[0].split(
':'
)[0] as GcpAccountType,
email: gcpUploadOptions?.share_with_accounts?.[0].split(':')[1] || '',
},
aws: {
accountId: awsUploadOptions?.share_with_accounts?.[0] || '',
shareMethod: (awsUploadOptions?.share_with_sources
? 'sources'
: 'manual') as AwsShareMethod,
source: { id: awsUploadOptions?.share_with_sources?.[0] },
sourceId: awsUploadOptions?.share_with_sources?.[0],
},
snapshotting: {
useLatest: !snapshot_date && !request.image_requests[0]?.content_template,
snapshotDate: snapshot_date,
template: request.image_requests[0]?.content_template || '',
templateName: request.image_requests[0]?.content_template_name || '',
},
repositories: {
customRepositories: request.customizations?.custom_repositories || [],
payloadRepositories: request.customizations?.payload_repositories || [],
recommendedRepositories: [],
redHatRepositories: [],
},
packages:
request.customizations?.packages
?.filter((pkg) => !pkg.startsWith('@'))
.map((pkg) => ({
name: pkg,
summary: '',
repository: '' as PackageRepository,
})) || [],
groups:
request.customizations?.packages
?.filter((grp) => grp.startsWith('@'))
.map((grp) => ({
name: grp.substr(1),
description: '',
repository: '' as PackageRepository,
package_list: [],
})) || [],
enabled_modules: request.customizations.enabled_modules || [],
locale: {
languages: request.customizations.locale?.languages || [],
keyboard: request.customizations.locale?.keyboard || '',
},
services: {
enabled: request.customizations?.services?.enabled || [],
masked: request.customizations?.services?.masked || [],
disabled: request.customizations?.services?.disabled || [],
},
kernel: {
name: request.customizations.kernel?.name || '',
append: request.customizations?.kernel?.append?.split(' ') || [],
},
timezone: {
timezone: request.customizations.timezone?.timezone || '',
ntpservers: request.customizations.timezone?.ntpservers || [],
},
hostname: request.customizations.hostname || '',
firewall: {
ports: request.customizations.firewall?.ports || [],
services: {
enabled: request.customizations.firewall?.services?.enabled || [],
disabled: request.customizations.firewall?.services?.disabled || [],
},
},
};
}
/**
* This function maps the blueprint response to the wizard state, used to populate the wizard with the blueprint details
* @param request BlueprintResponse
* @param source V1ListSourceResponseItem
* @returns wizardState
*/
export const mapRequestToState = (request: BlueprintResponse): wizardState => {
const wizardMode = 'edit';
return {
wizardMode,
blueprintId: request.id,
env: {
serverUrl: request.customizations.subscription?.['server-url'] || '',
baseUrl: request.customizations.subscription?.['base-url'] || '',
},
registration: {
registrationType:
request.customizations?.subscription && isRhel(request.distribution)
? request.customizations.subscription.rhc
? 'register-now-rhc'
: 'register-now-insights'
: getSatelliteCommand(request.customizations.files)
? 'register-satellite'
: 'register-later',
activationKey: isRhel(request.distribution)
? request.customizations.subscription?.['activation-key']
: undefined,
satelliteRegistration: {
command: getSatelliteCommand(request.customizations.files),
caCert: request.customizations.cacerts?.pem_certs[0],
},
},
...commonRequestToState(request),
};
};
export function mapToCustomRepositories(
repo: ApiRepositoryImportResponseRead
): CustomRepository[] {
if (!repo.uuid) return [];
return [
{
id: repo.uuid,
name: repo.name,
baseurl: repo.url ? [repo.url] : undefined,
gpgkey: repo.gpg_key ? [repo.gpg_key] : undefined,
check_gpg: repo.metadata_verification ?? undefined,
check_repo_gpg: repo.metadata_verification ?? undefined,
module_hotfixes: repo.module_hotfixes ?? undefined,
enabled: true,
},
];
}
/**
* This function maps the blueprint response to the wizard state, used to populate the wizard with the blueprint details
* @param request BlueprintExportResponse
* @returns wizardState
*/
export const mapExportRequestToState = (
request: BlueprintExportResponse,
image_requests: ImageRequest[]
): wizardState => {
const wizardMode = 'create';
const blueprintResponse: CreateBlueprintRequest = {
name: request.name,
description: request.description,
distribution: request.distribution,
customizations: request.customizations,
image_requests: image_requests,
};
return {
wizardMode,
metadata: {
parent_id: request.metadata?.parent_id || null,
exported_at: request.metadata?.exported_at || '',
is_on_prem: request.metadata?.is_on_prem || false,
},
env: initialState.env,
registration: initialState.registration,
...commonRequestToState(blueprintResponse),
};
};
const getFirstBootScript = (files?: File[]): string => {
const firstBootFile = files?.find((file) => file.path === FIRSTBOOT_PATH);
return firstBootFile?.data ? atob(firstBootFile.data) : '';
};
const getImageRequests = (
state: RootState
): ImageRequest[] | CockpitImageRequest[] => {
const imageTypes = selectImageTypes(state);
const snapshotDate = selectSnapshotDate(state);
const useLatest = selectUseLatest(state);
const template = selectTemplate(state);
const templateName = selectTemplateName(state);
return imageTypes.map((type) => ({
architecture: selectArchitecture(state),
image_type: type,
upload_request: {
type: uploadTypeByTargetEnv(type),
options: getImageOptions(type, state),
},
snapshot_date: !useLatest && !template ? snapshotDate : undefined,
content_template: template || undefined,
content_template_name: templateName || undefined,
}));
};
const getSatelliteCommand = (files?: File[]): string => {
const satelliteCommandFile = files?.find(
(file) => file.path === SATELLITE_PATH
);
return satelliteCommandFile?.data ? atob(satelliteCommandFile.data) : '';
};
const uploadTypeByTargetEnv = (
imageType: ImageTypes
): UploadTypes | CockpitUploadTypes => {
switch (imageType) {
case 'aws':
return 'aws';
case 'gcp':
return 'gcp';
case 'azure':
return 'azure';
case 'oci':
return 'oci.objectstorage';
case 'wsl':
return 'aws.s3';
case 'guest-image':
return 'aws.s3';
case 'image-installer':
return 'aws.s3';
case 'vsphere':
return 'aws.s3';
case 'vsphere-ova':
return 'aws.s3';
case 'ami':
return 'aws';
default: {
throw new Error(`image type: ${imageType} has no implementation yet`);
}
}
};
const getImageOptions = (
imageType: ImageTypes,
state: RootState
):
| AwsUploadRequestOptions
| AzureUploadRequestOptions
| GcpUploadRequestOptions
| CockpitAwsUploadRequestOptions => {
switch (imageType) {
case 'aws':
if (selectAwsShareMethod(state) === 'sources')
return { share_with_sources: [selectAwsSourceId(state) || ''] };
if (!process.env.IS_ON_PREMISE)
return { share_with_accounts: [selectAwsAccountId(state)] };
// TODO: we might want to update the image-builder-crc api
// to accept a region instead (with default us-east-1)
return {
share_with_accounts: [selectAwsAccountId(state)],
region: selectAwsRegion(state),
};
case 'azure':
if (selectAzureShareMethod(state) === 'sources')
return {
source_id: selectAzureSource(state),
resource_group: selectAzureResourceGroup(state),
hyper_v_generation: selectAzureHyperVGeneration(state),
};
return {
tenant_id: selectAzureTenantId(state),
subscription_id: selectAzureSubscriptionId(state),
resource_group: selectAzureResourceGroup(state),
hyper_v_generation: selectAzureHyperVGeneration(state),
};
case 'gcp': {
let googleAccount: string = '';
if (selectGcpShareMethod(state) === 'withGoogle') {
const gcpEmail = selectGcpEmail(state);
switch (selectGcpAccountType(state)) {
case 'user':
googleAccount = `user:${gcpEmail}`;
break;
case 'serviceAccount':
googleAccount = `serviceAccount:${gcpEmail}`;
break;
case 'group':
googleAccount = `group:${gcpEmail}`;
break;
case 'domain':
googleAccount = `domain:${gcpEmail}`;
}
return { share_with_accounts: [googleAccount] };
}
// TODO: GCP withInsights is not implemented yet
return {};
}
}
return {};
};
const getCustomizations = (state: RootState, orgID: string): Customizations => {
const satCert = selectSatelliteCaCertificate(state);
const files: File[] = [];
if (selectFirstBootScript(state)) {
files.push({
path: FIRSTBOOT_SERVICE_PATH,
data: FIRST_BOOT_SERVICE_DATA,
data_encoding: 'base64',
ensure_parents: true,
});
files.push({
path: FIRSTBOOT_PATH,
data: btoa(selectFirstBootScript(state)),
data_encoding: 'base64',
mode: '0774',
ensure_parents: true,
});
}
const satCmd = selectSatelliteRegistrationCommand(state);
if (satCmd && selectRegistrationType(state) === 'register-satellite') {
files.push({
path: SATELLITE_SERVICE_PATH,
data: SATELLITE_SERVICE_DATA,
data_encoding: 'base64',
ensure_parents: true,
});
files.push({
path: SATELLITE_PATH,
data: btoa(satCmd),
mode: '0774',
data_encoding: 'base64',
ensure_parents: true,
});
}
return {
containers: undefined,
directories: undefined,
files: files.length > 0 ? files : undefined,
subscription: getSubscription(state, orgID),
packages: getPackages(state),
enabled_modules: getModules(state),
payload_repositories: getPayloadRepositories(state),
custom_repositories: getCustomRepositories(state),
openscap: getOpenscap(state),
filesystem: getFileSystem(state),
users: getUsers(state),
services: getServices(state),
hostname: selectHostname(state) || undefined,
kernel: getKernel(state),
groups: undefined,
timezone: getTimezone(state),
locale: getLocale(state),
firewall: getFirewall(state),
installation_device: undefined,
fdo: undefined,
ignition: undefined,
partitioning_mode: undefined,
fips: undefined,
cacerts:
satCert && selectRegistrationType(state) === 'register-satellite'
? {
pem_certs: [satCert],
}
: undefined,
};
};
const getServices = (state: RootState): Services | undefined => {
const services = selectServices(state);
const enabledSvcs = services.enabled || [];
if (
enabledSvcs.length === 0 &&
services.masked.length === 0 &&
services.disabled.length === 0
) {
return undefined;
}
return {
enabled: enabledSvcs.length ? enabledSvcs : undefined,
masked: services.masked.length ? services.masked : undefined,
disabled: services.disabled.length ? services.disabled : undefined,
};
};
const getOpenscap = (state: RootState): OpenScap | undefined => {
const complianceType = selectComplianceType(state);
const profile = selectComplianceProfileID(state);
const policy = selectCompliancePolicyID(state);
if (complianceType === 'openscap' && profile) {
return { profile_id: profile };
}
if (complianceType === 'compliance' && policy) {
return { policy_id: policy };
}
return undefined;
};
const getUsers = (state: RootState): User[] | undefined => {
const users = selectUsers(state);
if (users.length === 0) {
return undefined;
}
return users.map((user) => {
const result: User = {
name: user.name,
};
if (user.password !== '') {
result.password = user.password;
}
if (user.ssh_key !== '') {
result.ssh_key = user.ssh_key;
}
if (user.groups.length > 0) {
result.groups = user.groups;
}
result.hasPassword = user.hasPassword || user.password !== '';
return result as User;
});
};
const getFileSystem = (state: RootState): Filesystem[] | undefined => {
const mode = selectFileSystemConfigurationType(state);
const convertToBytes = (minSize: string, conversionFactor: number) => {
return minSize.length > 0 ? parseInt(minSize) * conversionFactor : 0;
};
if (mode === 'manual') {
const partitions = selectPartitions(state);
const fileSystem = partitions.map((partition) => {
return {
min_size: convertToBytes(
partition.min_size,
getConversionFactor(partition.unit)
),
mountpoint: partition.mountpoint,
};
});
return fileSystem;
}
return undefined;
};
const getPackages = (state: RootState) => {
const packages = selectPackages(state);
const groups = selectGroups(state);
if (packages.length > 0 || groups.length > 0) {
return packages
.map((pkg) => pkg.name)
.concat(groups.map((grp) => '@' + grp.name));
}
return undefined;
};
const getModules = (state: RootState) => {
const modules = selectModules(state);
if (modules.length > 0) {
return modules;
}
return undefined;
};
const getTimezone = (state: RootState) => {
const timezone = selectTimezone(state);
const ntpservers = selectNtpServers(state);
if (!timezone && ntpservers?.length === 0) {
return undefined;
}
return {
timezone: timezone ? timezone : undefined,
ntpservers: ntpservers && ntpservers.length > 0 ? ntpservers : undefined,
};
};
const getSubscription = (
state: RootState,
orgID: string
): Subscription | undefined => {
const registrationType = selectRegistrationType(state);
const activationKey = selectActivationKey(state);
if (
registrationType === 'register-later' ||
registrationType === 'register-satellite'
) {
return undefined;
}
if (activationKey === undefined) {
throw new Error(
'Activation key unexpectedly undefined while generating subscription customization'
);
}
const initialSubscription = {
'activation-key': activationKey,
organization: Number(orgID),
'server-url': selectServerUrl(state),
'base-url': selectBaseUrl(state),
};
switch (registrationType) {
case 'register-now-rhc':
return { ...initialSubscription, insights: true, rhc: true };
case 'register-now-insights':
return { ...initialSubscription, insights: true, rhc: false };
case 'register-now':
return { ...initialSubscription, insights: false, rhc: false };
}
};
const getLocale = (state: RootState) => {
const languages = selectLanguages(state);
const keyboard = selectKeyboard(state);
if (languages?.length === 0 && !keyboard) {
return undefined;
}
return {
languages: languages && languages.length > 0 ? languages : undefined,
keyboard: keyboard ? keyboard : undefined,
};
};
const getFirewall = (state: RootState) => {
const ports = selectFirewall(state).ports;
const services = selectFirewall(state).services;
const firewall = {};
if (ports.length > 0) {
Object.assign(firewall, { ports: ports });
}
if (services.enabled.length > 0 || services.disabled.length > 0) {
Object.assign(firewall, {
services: {
enabled: services.enabled.length > 0 ? services.enabled : undefined,
disabled: services.disabled.length > 0 ? services.disabled : undefined,
},
});
}
return Object.keys(firewall).length > 0 ? firewall : undefined;
};
const getCustomRepositories = (state: RootState) => {
const customRepositories = selectCustomRepositories(state).map((cr) => {
return {
...cr,
baseurl: cr.baseurl && cr.baseurl.length !== 0 ? cr.baseurl : undefined,
} as CustomRepository;
});
const recommendedRepositories = selectRecommendedRepositories(state);
const customAndRecommendedRepositories = [...customRepositories];
for (const repo in recommendedRepositories) {
customAndRecommendedRepositories.push(
convertSchemaToIBCustomRepo(recommendedRepositories[repo])
);
}
if (customAndRecommendedRepositories.length === 0) {
return undefined;
}
return customAndRecommendedRepositories;
};
const getPayloadRepositories = (state: RootState) => {
const payloadRepositories = selectPayloadRepositories(state);
const recommendedRepositories = selectRecommendedRepositories(state);
const payloadAndRecommendedRepositories = [...payloadRepositories];
for (const repo in recommendedRepositories) {
payloadAndRecommendedRepositories.push(
convertSchemaToIBPayloadRepo(recommendedRepositories[repo])
);
}
if (payloadAndRecommendedRepositories.length === 0) {
return undefined;
}
return payloadAndRecommendedRepositories;
};
const getKernel = (state: RootState) => {
const kernel = selectKernel(state);
const kernelAppendString = selectKernel(state).append.join(' ');
const kernelRequest = {};
if (!kernel.name && kernel.append.length === 0) {
return undefined;
}
if (kernel.name) {
Object.assign(kernelRequest, {
name: kernel.name,
});
}
if (kernelAppendString !== '') {
Object.assign(kernelRequest, {
append: kernelAppendString,
});
}
return Object.keys(kernelRequest).length > 0 ? kernelRequest : undefined;
};