add encrypt image and decrypt image actions

Signed-off-by: andy.lee <andy.lee@suse.com>
This commit is contained in:
andy.lee 2024-09-24 12:39:22 +08:00 committed by Francesco Torchia
parent 8117e99b54
commit 228369ad88
No known key found for this signature in database
GPG Key ID: E6D011B7415D4393
4 changed files with 141 additions and 25 deletions

View File

@ -2,7 +2,8 @@
import CopyToClipboardText from '@shell/components/CopyToClipboardText';
import LabelValue from '@shell/components/LabelValue';
import { DESCRIPTION } from '@shell/config/labels-annotations';
import { HCI } from '@pkg/harvester/config/labels-annotations';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { HCI } from '../../types';
import Tabbed from '@shell/components/Tabbed';
import Tab from '@shell/components/Tabbed/Tab';
import { findBy } from '@shell/utils/array';
@ -31,10 +32,14 @@ export default {
const inStore = this.$store.getters['currentProduct'].inStore;
this.secrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
this.images = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE });
},
data() {
return { secrets: [] };
return {
secrets: [],
images: []
};
},
computed: {
@ -64,6 +69,34 @@ export default {
return this.value?.spec?.sourceType === 'upload';
},
sourceImage() {
const { sourceImageName, sourceImageNamespace } = this.value?.spec?.securityParameters || {};
if (sourceImageNamespace && sourceImageName) {
const imageId = `${ sourceImageNamespace }/${ sourceImageName }`;
return this.images.find(image => image.id === imageId);
}
return null;
},
sourceImageLink() {
return this.sourceImage?.detailLocation;
},
sourceImageId() {
if (this.sourceImage) {
return this.sourceImage.displayNameWithNamespace;
}
return '';
},
isEncryptedOrDecrypted() {
return ['encrypt', 'decrypt'].includes(this.value?.spec?.securityParameters?.cryptoOperation);
},
encryptionSecret() {
if (!this.value.isEncrypted) {
return '-';
@ -80,7 +113,7 @@ export default {
},
imageName() {
return this.value?.metadata?.annotations?.[HCI.IMAGE_NAME] || '-';
return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-';
},
}
};
@ -144,7 +177,7 @@ export default {
</div>
</div>
<div v-if="value.isEncrypted" class="row">
<div v-if="value.isEncrypted" class="row mb-20">
<div class="col span-12">
<div class="text-label">
{{ t('harvester.image.encryptionSecret') }}
@ -161,6 +194,23 @@ export default {
</div>
</div>
<div v-if="isEncryptedOrDecrypted" class="row mb-20">
<div class="col span-12">
<div class="text-label">
{{ t('harvester.image.sourceImage') }}
</div>
<n-link v-if="sourceImageId && sourceImageLink" :to="sourceImageLink">
{{ sourceImageId }}
</n-link>
<span v-else-if="sourceImageId">
{{ sourceImageId }}
</span>
<span v-else class="text-muted">
&mdash;
</span>
</div>
</div>
<div v-if="errorMessage !== '-'" class="row">
<div class="col span-12">
<div>

View File

@ -62,12 +62,32 @@ export default {
this['storageClassName'] = this.storageClassName || defaultStorage?.metadata?.name || 'longhorn';
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
this.selectedImage = this.images.find(i => i.name === this.value.name) || null;
const { securityParameters } = this.value.spec;
// edit and view mode should show the source image
if (securityParameters) {
// image ns/name = image.id
const sourceImage = `${ securityParameters.sourceImageNamespace }/${ securityParameters.sourceImageName }`;
this.selectedImage = this.images.find(image => image.id === sourceImage);
}
},
data() {
// pass from Encrypt Image / Decrypt Image actions
const { image, sourceType, cryptoOperation } = this.$route.query || {};
if ( !this.value.spec ) {
this.value['spec'] = { sourceType: DOWNLOAD };
this.value['spec'] = { sourceType: sourceType || DOWNLOAD };
}
if (image && cryptoOperation) {
this.value.spec.securityParameters = {
cryptoOperation,
sourceImageName: image.metadata.name,
sourceImageNamespace: image.metadata.namespace
}
}
if (!this.value.metadata.name) {
@ -75,7 +95,7 @@ export default {
}
return {
selectedImage: null,
selectedImage: image || null,
images: [],
url: this.value.spec.url,
files: [],
@ -147,22 +167,27 @@ export default {
options = this.images.filter(image => image.isEncrypted);
}
return options.map(image => image.spec.displayName);
return options.map(image => image.displayNameWithNamespace);
},
sourceImageName: {
sourceImage: {
get() {
return this.selectedImage?.spec.displayName;
if (this.selectedImage) {
return this.selectedImage.displayNameWithNamespace;
}
return '';
},
set(imageDisplayName) {
this.selectedImage = this.images.find(i => i.spec.displayName === imageDisplayName);
set(neu) {
this.selectedImage = this.images.find(i => i.displayNameWithNamespace === neu);
// sourceImageName should bring the name of the image
this.value.spec.securityParameters.sourceImageName = this.selectedImage?.metadata.name || '';
this.value.spec.securityParameters.sourceImageNamespace = this.selectedImage?.metadata.namespace || '';
}
},
sourceType: {
get() {
if (this.value.spec.sourceType === CLONE) {
return this.value.spec.securityParameters.cryptoOperation;
return this.value.spec?.securityParameters?.cryptoOperation;
} else {
return this.value.spec.sourceType;
}
@ -186,15 +211,6 @@ export default {
},
watch: {
'value.metadata.namespace'(neu) {
if (this.value.spec.sourceType === CLONE) {
this.$set(this.value.spec, 'securityParameters', {
cryptoOperation: this.value.spec.securityParameters.cryptoOperation,
sourceImageName: '',
sourceImageNamespace: neu
});
}
},
'value.spec.url'(neu) {
const url = neu.trim();
@ -451,7 +467,7 @@ export default {
<LabeledSelect
v-if="value.spec.sourceType === 'clone'"
v-model="sourceImageName"
v-model="sourceImage"
:options="sourceImageOptions"
:label="t('harvester.image.sourceImage')"
:mode="mode"

View File

@ -153,7 +153,7 @@ harvester:
warning: Warning
error: Error
action:
createVM: Create a Virtual Machine
createVM: Create Virtual Machine
start: Start
restart: Restart
softreboot: Soft Reboot
@ -162,6 +162,8 @@ harvester:
deepClone: Clone
shallowClone: Clone Template
unpause: Unpause
encryptImage: Encrypt Image
decryptImage: Decrypt Image
ejectCDROM: Eject CD-ROM
editVMQuota: Edit VM Quota
launchFormTemplate: Launch instance from template
@ -778,7 +780,7 @@ harvester:
urlTip: 'Supports the <code>raw</code> and <code>qcow2</code> image formats which are supported by <a href="https://www.qemu.org/docs/master/system/images.html#disk-image-file-formats" target="_blank">qemu</a>. Bootable ISO images can also be used and are treated like <code>raw</code> images.'
fileName: File Name
uploadFile: Upload File
source: Source
source: Source Type
sourceType:
download: URL
upload: File

View File

@ -57,6 +57,20 @@ export default class HciVmImage extends HarvesterResource {
label: this.t('harvester.action.createVM'),
disabled: !this.isReady,
},
{
action: 'encryptImage',
enabled: !this.isEncrypted,
icon: 'icon icon-lock',
label: this.t('harvester.action.encryptImage'),
disabled: !this.isReady,
},
{
action: 'decryptImage',
enabled: this.isEncrypted,
icon: 'icon icon-unlock',
label: this.t('harvester.action.decryptImage'),
disabled: !this.isReady,
},
{
action: 'download',
enabled: this.links?.download,
@ -67,6 +81,36 @@ export default class HciVmImage extends HarvesterResource {
];
}
encryptImage() {
const router = this.currentRouter();
router.push({
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
params: { resource: HCI.IMAGE },
query: {
image: this,
fromPage: HCI.IMAGE,
sourceType: 'clone',
cryptoOperation: 'encrypt'
}
});
}
decryptImage() {
const router = this.currentRouter();
router.push({
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
params: { resource: HCI.IMAGE },
query: {
image: this,
fromPage: HCI.IMAGE,
sourceType: 'clone',
cryptoOperation: 'decrypt'
}
});
}
applyDefaults(resources = this, realMode) {
if (realMode !== _CLONE) {
this.metadata['labels'] = { [HCI_ANNOTATIONS.OS_TYPE]: '', [HCI_ANNOTATIONS.IMAGE_SUFFIX]: '' };
@ -151,6 +195,10 @@ export default class HciVmImage extends HarvesterResource {
!!this.spec.securityParameters?.sourceImageNamespace;
}
get displayNameWithNamespace() {
return `${ this.metadata.namespace }/${ this.spec.displayName }`;
}
get imageMessage() {
if (this.uploadError) {
return ucFirst(this.uploadError);