Skip to content

Commit

Permalink
CNV-37857: Enable SSH secret selection on quick create flow
Browse files Browse the repository at this point in the history
Signed-off-by: Aviv Turgeman <aturgema@redhat.com>
  • Loading branch information
avivtur committed Feb 5, 2024
1 parent 8753b79 commit 3d09d6c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 47 deletions.
9 changes: 7 additions & 2 deletions src/views/catalog/templatescatalog/TemplatesCatalog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { FC, useMemo, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';

import { clearSessionStorageVM } from '@catalog/utils/WizardVMContext';
import { clearSessionStorageVM, useWizardVMContext } from '@catalog/utils/WizardVMContext';
import { V1Template } from '@kubevirt-ui/kubevirt-api/console';
import { DEFAULT_NAMESPACE } from '@kubevirt-utils/constants/constants';
import { Stack, Toolbar, ToolbarContent } from '@patternfly/react-core';
Expand All @@ -24,6 +24,7 @@ const TemplatesCatalog: FC<RouteComponentProps<{ ns: string }>> = ({
},
}) => {
const [selectedTemplate, setSelectedTemplate] = useState<undefined | V1Template>(undefined);
const { updateTabsData } = useWizardVMContext();

const [filters, onFilterChange, clearAll] = useTemplatesFilters();
const { availableDatasources, availableTemplatesUID, bootSourcesLoaded, loaded, templates } =
Expand Down Expand Up @@ -78,9 +79,13 @@ const TemplatesCatalog: FC<RouteComponentProps<{ ns: string }>> = ({
skeletonCatalog
)}
<TemplatesCatalogDrawer
onClose={() => {
setSelectedTemplate(undefined);
clearSessionStorageVM();
updateTabsData({});
}}
isOpen={!!selectedTemplate}
namespace={namespace ?? DEFAULT_NAMESPACE}
onClose={() => setSelectedTemplate(undefined)}
template={selectedTemplate}
/>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
import React, { FC } from 'react';
import React, { FC, useCallback, useMemo, useState } from 'react';

import { SecretModel, V1Template } from '@kubevirt-ui/kubevirt-api/console';
import { useWizardVMContext } from '@catalog/utils/WizardVMContext';
import {
removeSSHKeyObject,
updateSSHKeyObject,
} from '@catalog/wizard/tabs/scripts/components/sshkey-utils';
import { SecretModel } from '@kubevirt-ui/kubevirt-api/console';
import { IoK8sApiCoreV1Secret } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { useModal } from '@kubevirt-utils/components/ModalProvider/ModalProvider';
import { isEqualObject } from '@kubevirt-utils/components/NodeSelectorModal/utils/helpers';
import SSHSecretModal from '@kubevirt-utils/components/SSHSecretSection/SSHSecretModal';
import {
SecretSelectionOption,
SSHSecretDetails,
} from '@kubevirt-utils/components/SSHSecretSection/utils/types';
import {
addSecretToVM,
removeSecretToVM,
} from '@kubevirt-utils/components/SSHSecretSection/utils/utils';
import EditButtonWithTooltip from '@kubevirt-utils/components/VirtualMachineDescriptionItem/EditButtonWithTooltip';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { getInitialSSHDetails } from '@kubevirt-utils/resources/secret/utils';
import { getVMSSHSecretName } from '@kubevirt-utils/resources/vm';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import {
DescriptionList,
Expand All @@ -14,35 +33,113 @@ import {
SplitItem,
} from '@patternfly/react-core';

import { useDrawerContext } from './hooks/useDrawerContext';

type AuthorizedSSHKeyProps = {
authorizedSSHKey: string;
template: V1Template;
namespace: string;
};
const AuthorizedSSHKey: FC<AuthorizedSSHKeyProps> = ({ authorizedSSHKey, template }) => {
const AuthorizedSSHKey: FC<AuthorizedSSHKeyProps> = ({ authorizedSSHKey, namespace }) => {
const { t } = useKubevirtTranslation();
const { createModal } = useModal();
const { setVM, template, vm } = useDrawerContext();
const { updateTabsData } = useWizardVMContext();

const vmAttachedSecretName = useMemo(() => getVMSSHSecretName(vm), [vm]);
const secretName = authorizedSSHKey || vmAttachedSecretName;

const additionalSecretResource: IoK8sApiCoreV1Secret = template?.objects?.find(
(object) => object?.kind === SecretModel.kind,
);

const [sshDetails, setSSHDetails] = useState<SSHSecretDetails>(
getInitialSSHDetails({
applyKeyToProject: !isEmpty(authorizedSSHKey),
secretToCreate:
isEmpty(authorizedSSHKey) && !isEmpty(additionalSecretResource)
? additionalSecretResource
: null,
sshSecretName: secretName,
}),
);

const onSubmit = useCallback(
async (details: SSHSecretDetails) => {
const { applyKeyToProject, secretOption, sshPubKey, sshSecretName } = details;

if (isEqualObject(details, sshDetails)) {
return;
}

removeSSHKeyObject(updateTabsData, sshDetails.sshSecretName);
updateTabsData((currentTabsData) => {
currentTabsData.authorizedSSHKey = sshSecretName;
currentTabsData.applySSHToSettings = applyKeyToProject;
});

if (
secretOption === SecretSelectionOption.none &&
sshDetails.secretOption !== SecretSelectionOption.none
) {
await setVM(removeSecretToVM(vm));
}

if (
secretOption === SecretSelectionOption.useExisting &&
sshDetails.sshSecretName !== sshSecretName &&
!isEmpty(sshSecretName)
) {
await setVM(addSecretToVM(vm, sshSecretName));
}

if (
secretOption === SecretSelectionOption.addNew &&
!isEmpty(sshPubKey) &&
!isEmpty(sshSecretName)
) {
updateSSHKeyObject(vm, updateTabsData, sshPubKey, sshSecretName);
await setVM(addSecretToVM(vm, sshSecretName));
}

setSSHDetails(details);
return Promise.resolve();
},
[sshDetails, updateTabsData, setVM, vm],
);

return (
!isEmpty(authorizedSSHKey) && (
<SplitItem>
<DescriptionList>
<DescriptionListGroup>
<DescriptionListTerm>{t('Public SSH key')}</DescriptionListTerm>
<DescriptionListDescription>{authorizedSSHKey}</DescriptionListDescription>
{!isEmpty(additionalSecretResource) && (
<HelperText>
<HelperTextItem hasIcon variant="warning">
{t('This key will override the SSH key secret set on the template')}
</HelperTextItem>
</HelperText>
)}
</DescriptionListGroup>
</DescriptionList>
</SplitItem>
)
<SplitItem>
<DescriptionList>
<DescriptionListGroup>
<DescriptionListTerm>{t('Public SSH key')}</DescriptionListTerm>
<DescriptionListDescription>
<EditButtonWithTooltip
onEditClick={() =>
createModal((modalProps) => (
<SSHSecretModal
{...modalProps}
initialSSHSecretDetails={sshDetails}
namespace={namespace}
onSubmit={onSubmit}
/>
))
}
isEditable
testId="ssh-edit-btn"
>
{sshDetails?.sshSecretName || t('Not configured')}
</EditButtonWithTooltip>
</DescriptionListDescription>
{!isEmpty(additionalSecretResource) && (
<HelperText>
<HelperTextItem hasIcon variant="warning">
{t('This key will override the SSH key secret set on the template')}
</HelperTextItem>
</HelperText>
)}
</DescriptionListGroup>
</DescriptionList>
</SplitItem>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const TemplatesCatalogDrawerCreateForm: FC<TemplatesCatalogDrawerCreateFo
({ authorizedSSHKey, namespace, onCancel, subscriptionData }) => {
const { t } = useKubevirtTranslation();

const { isBootSourceAvailable, template, templateLoadingError } = useDrawerContext();
const { isBootSourceAvailable, templateLoadingError } = useDrawerContext();

const {
createError,
Expand Down Expand Up @@ -82,7 +82,7 @@ export const TemplatesCatalogDrawerCreateForm: FC<TemplatesCatalogDrawerCreateFo
</DescriptionListGroup>
</DescriptionList>
</SplitItem>
<AuthorizedSSHKey authorizedSSHKey={authorizedSSHKey} template={template} />
<AuthorizedSSHKey authorizedSSHKey={authorizedSSHKey} namespace={namespace} />
</Split>
</StackItem>
<StackItem />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AUTOMATIC_UPDATE_FEATURE_NAME } from 'src/views/clusteroverview/Setting

import { quickCreateVM } from '@catalog/utils/quick-create-vm';
import { isRHELTemplate } from '@catalog/utils/utils';
import { useWizardVMContext } from '@catalog/utils/WizardVMContext';
import { clearSessionStorageVM, useWizardVMContext } from '@catalog/utils/WizardVMContext';
import {
ProcessedTemplatesModel,
SecretModel,
Expand All @@ -19,6 +19,7 @@ import {
} from '@kubevirt-utils/components/SSHSecretSection/utils/utils';
import { DISABLED_GUEST_SYSTEM_LOGS_ACCESS } from '@kubevirt-utils/hooks/useFeatures/constants';
import { useFeatures } from '@kubevirt-utils/hooks/useFeatures/useFeatures';
import useKubevirtUserSettings from '@kubevirt-utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings';
import { RHELAutomaticSubscriptionData } from '@kubevirt-utils/hooks/useRHELAutomaticSubscription/utils/types';
import { getAnnotation, getResourceUrl } from '@kubevirt-utils/resources/shared';
import {
Expand All @@ -43,7 +44,8 @@ const useCreateDrawerForm = (
subscriptionData: RHELAutomaticSubscriptionData,
authorizedSSHKey: string,
) => {
const { updateTabsData, updateVM } = useWizardVMContext();
const { tabsData, updateTabsData, updateVM } = useWizardVMContext();
const [authorizedSSHKeys, updateAuthorizedSSHKeys] = useKubevirtUserSettings('ssh');
const { featureEnabled: autoUpdateEnabled } = useFeatures(AUTOMATIC_UPDATE_FEATURE_NAME);
const { featureEnabled: isDisabledGuestSystemLogs } = useFeatures(
DISABLED_GUEST_SYSTEM_LOGS_ACCESS,
Expand Down Expand Up @@ -86,10 +88,6 @@ const useCreateDrawerForm = (
const templateToProcess = produce(template, (draftTemplate) => {
const vmObject = getTemplateVirtualMachineObject(draftTemplate);

if (!isEmpty(authorizedSSHKey)) {
draftTemplate.objects = template.objects.filter((obj) => obj?.kind !== SecretModel.kind);
}

if (vm?.spec?.template) {
ensurePath(vmObject, [
'spec.template.spec.domain.cpu',
Expand All @@ -100,19 +98,41 @@ const useCreateDrawerForm = (
vmObject.spec.template.spec.domain.cpu.cores = cpu?.cores;
vmObject.spec.template.spec.domain.memory.guest = memory;

const modifiedTemplateObjects = template?.objects?.map((obj) =>
obj.kind === VirtualMachineModel.kind ? vmObject : obj,
);
const modifiedTemplateObjects = draftTemplate?.objects?.map((obj) => {
if (obj.kind === VirtualMachineModel.kind) {
if (isEmpty(tabsData) && !isEmpty(authorizedSSHKey)) {
return addSecretToVM(vmObject, authorizedSSHKey);
}

return vmObject;
}

return obj;
});

draftTemplate.objects = isEmpty(tabsData?.additionalObjects)
? modifiedTemplateObjects
: [...modifiedTemplateObjects, ...tabsData?.additionalObjects];

if (isEmpty(tabsData) && !isEmpty(authorizedSSHKey)) {
draftTemplate.objects = draftTemplate?.objects?.filter(
(obj) => obj?.kind !== SecretModel.kind,
);
}

draftTemplate.objects = modifiedTemplateObjects;
if (tabsData?.authorizedSSHKey && tabsData?.applySSHToSettings) {
updateAuthorizedSSHKeys({
...authorizedSSHKeys,
[namespace]: tabsData.authorizedSSHKey,
});
}
}
});

try {
const quickCreatedVM = await quickCreateVM({
models,
overrides: {
authorizedSSHKey,
autoUpdateEnabled,
isDisabledGuestSystemLogs,
name: nameField,
Expand All @@ -137,6 +157,8 @@ const useCreateDrawerForm = (
setCreateError(error);
} finally {
setIsQuickCreating(false);
clearSessionStorageVM();
updateTabsData({});
}
};

Expand Down
14 changes: 3 additions & 11 deletions src/views/catalog/utils/quick-create-vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,20 @@ import VirtualMachineModel from '@kubevirt-ui/kubevirt-api/console/models/Virtua
import { V1Devices, V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import { updateCloudInitRHELSubscription } from '@kubevirt-utils/components/CloudinitModal/utils/cloudinit-utils';
import { applyCloudDriveCloudInitVolume } from '@kubevirt-utils/components/SSHSecretSection/utils/utils';
import { addSecretToVM } from '@kubevirt-utils/components/SSHSecretSection/utils/utils';
import { DEFAULT_NAMESPACE } from '@kubevirt-utils/constants/constants';
import { RHELAutomaticSubscriptionData } from '@kubevirt-utils/hooks/useRHELAutomaticSubscription/utils/types';
import {
LABEL_USED_TEMPLATE_NAME,
LABEL_USED_TEMPLATE_NAMESPACE,
replaceTemplateVM,
} from '@kubevirt-utils/resources/template';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { k8sCreate, K8sModel } from '@openshift-console/dynamic-plugin-sdk';

import { createMultipleResources, isRHELTemplate } from './utils';

type QuickCreateVMType = (inputs: {
models: { [key: string]: K8sModel };
overrides: {
authorizedSSHKey: string;
autoUpdateEnabled: boolean;
isDisabledGuestSystemLogs: boolean;
name: string;
Expand All @@ -36,7 +33,6 @@ type QuickCreateVMType = (inputs: {
export const quickCreateVM: QuickCreateVMType = async ({
models,
overrides: {
authorizedSSHKey,
autoUpdateEnabled,
isDisabledGuestSystemLogs,
name,
Expand Down Expand Up @@ -82,17 +78,13 @@ export const quickCreateVM: QuickCreateVMType = async ({
: updatedVolumes;
});

const vmToCreate = !isEmpty(authorizedSSHKey)
? addSecretToVM(overridedVM, authorizedSSHKey)
: overridedVM;

const { objects } = replaceTemplateVM(processedTemplate, vmToCreate);
const { objects } = replaceTemplateVM(processedTemplate, overridedVM);

const createdObjects = await createMultipleResources(objects, models, namespace);

const createdVM = createdObjects.find(
const vmToCreate = createdObjects.find(
(object) => object.kind === VirtualMachineModel.kind,
) as V1VirtualMachine;

return createdVM;
return vmToCreate;
};

0 comments on commit 3d09d6c

Please sign in to comment.