diff --git a/pkg/harvester/edit/harvesterhci.io.storage/provisioners/driver.longhorn.io.vue b/pkg/harvester/edit/harvesterhci.io.storage/provisioners/driver.longhorn.io.vue
index fcd37195..b37ff8d3 100644
--- a/pkg/harvester/edit/harvesterhci.io.storage/provisioners/driver.longhorn.io.vue
+++ b/pkg/harvester/edit/harvesterhci.io.storage/provisioners/driver.longhorn.io.vue
@@ -3,9 +3,9 @@ import KeyValue from '@shell/components/form/KeyValue';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import RadioGroup from '@components/Form/Radio/RadioGroup';
-
+import { SECRET, NAMESPACE, LONGHORN } from '@shell/config/types';
import { _CREATE, _VIEW } from '@shell/config/query-params';
-import { LONGHORN } from '@shell/config/types';
+import { CSI_SECRETS } from '@pkg/harvester/config/harvester-map';
import { clone } from '@shell/utils/object';
import { uniq } from '@shell/utils/array';
@@ -15,8 +15,18 @@ const DEFAULT_PARAMETERS = [
'diskSelector',
'nodeSelector',
'migratable',
+ 'encrypted',
];
+const {
+ CSI_PROVISIONER_SECRET_NAME,
+ CSI_PROVISIONER_SECRET_NAMESPACE,
+ CSI_NODE_PUBLISH_SECRET_NAME,
+ CSI_NODE_PUBLISH_SECRET_NAMESPACE,
+ CSI_NODE_STAGE_SECRET_NAME,
+ CSI_NODE_STAGE_SECRET_NAMESPACE
+} = CSI_SECRETS;
+
export default {
components: {
KeyValue,
@@ -40,6 +50,14 @@ export default {
},
},
+ async fetch() {
+ const inStore = this.$store.getters['currentProduct'].inStore;
+ const allNamespaces = await this.$store.dispatch(`${ inStore }/findAll`, { type: NAMESPACE });
+
+ this.secrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
+ this.namespaces = allNamespaces.filter(ns => ns.isSystem === false).map(ns => ns.id); // only show non-system namespaces to user to select
+ },
+
data() {
if (this.realMode === _CREATE) {
this.value['parameters'] = {
@@ -47,11 +65,15 @@ export default {
staleReplicaTimeout: '30',
diskSelector: null,
nodeSelector: null,
- migratable: 'true'
+ encrypted: 'false',
+ migratable: 'true',
};
}
- return {};
+ return {
+ secrets: [],
+ namespaces: [],
+ };
},
computed: {
@@ -97,6 +119,26 @@ export default {
}];
},
+ secretOptions() {
+ const selectedNS = this.secretNamespace;
+
+ return this.secrets.filter(secret => secret.namespace === selectedNS).map(secret => secret.name);
+ },
+
+ secretNameOptions() {
+ return this.namespaces;
+ },
+
+ volumeEncryptionOptions() {
+ return [{
+ label: this.t('generic.yes'),
+ value: 'true'
+ }, {
+ label: this.t('generic.no'),
+ value: 'false'
+ }];
+ },
+
parameters: {
get() {
const parameters = clone(this.value?.parameters) || {};
@@ -113,6 +155,49 @@ export default {
}
},
+ volumeEncryption: {
+ set(neu) {
+ this.$set(this.value, 'parameters', {
+ ...this.value.parameters,
+ encrypted: neu
+ });
+ },
+
+ get() {
+ return this.value?.parameters?.encrypted || 'false';
+ }
+ },
+
+ secretName: {
+ get() {
+ return this.value.parameters[CSI_PROVISIONER_SECRET_NAME];
+ },
+
+ set(neu) {
+ this.$set(this.value, 'parameters', {
+ ...this.value.parameters,
+ [CSI_PROVISIONER_SECRET_NAME]: neu,
+ [CSI_NODE_PUBLISH_SECRET_NAME]: neu,
+ [CSI_NODE_STAGE_SECRET_NAME]: neu
+ });
+ }
+ },
+
+ secretNamespace: {
+ get() {
+ return this.value.parameters[CSI_PROVISIONER_SECRET_NAMESPACE];
+ },
+
+ set(neu) {
+ this.$set(this.value, 'parameters', {
+ ...this.value.parameters,
+ [CSI_PROVISIONER_SECRET_NAMESPACE]: neu,
+ [CSI_NODE_PUBLISH_SECRET_NAMESPACE]: neu,
+ [CSI_NODE_STAGE_SECRET_NAMESPACE]: neu
+ });
+ }
+ },
+
nodeSelector: {
get() {
const nodeSelector = this.value?.parameters?.nodeSelector;
@@ -221,14 +306,39 @@ export default {
+
+
+
+
+
+
+
diff --git a/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue b/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue
index 795b5fd2..4419f102 100644
--- a/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue
+++ b/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue
@@ -8,7 +8,6 @@ import NameNsDescription from '@shell/components/form/NameNsDescription';
import { RadioGroup } from '@components/Form/Radio';
import Select from '@shell/components/form/Select';
import LabeledSelect from '@shell/components/form/LabeledSelect';
-
import CreateEditView from '@shell/mixins/create-edit-view';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { exceptionToErrorsArray } from '@shell/utils/error';
@@ -18,6 +17,9 @@ import { VM_IMAGE_FILE_FORMAT } from '../validators/vm-image';
import { OS } from '../mixins/harvester-vm';
import { HCI } from '../types';
+const ENCRYPT = 'encrypt';
+const DECRYPT = 'decrypt';
+const CLONE = 'clone';
const DOWNLOAD = 'download';
const UPLOAD = 'upload';
const rawORqcow2 = 'raw_qcow2';
@@ -59,6 +61,8 @@ export default {
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find(s => s.isDefault);
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;
},
data() {
@@ -71,12 +75,14 @@ export default {
}
return {
- url: this.value.spec.url,
- files: [],
- resource: '',
- headers: {},
- fileUrl: '',
- file: '',
+ selectedImage: null,
+ images: [],
+ url: this.value.spec.url,
+ files: [],
+ resource: '',
+ headers: {},
+ fileUrl: '',
+ file: '',
};
},
@@ -94,9 +100,16 @@ export default {
},
showEditAsYaml() {
- return this.value.spec.sourceType === DOWNLOAD;
+ return this.value.spec.sourceType === DOWNLOAD || this.value.spec.sourceType === CLONE;
+ },
+ radioGroupOptions() {
+ return [
+ DOWNLOAD,
+ UPLOAD,
+ ENCRYPT,
+ DECRYPT
+ ];
},
-
storageClassOptions() {
const inStore = this.$store.getters['currentProduct'].inStore;
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
@@ -122,9 +135,66 @@ export default {
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
}
},
+ sourceImageOptions() {
+ let options = [];
+
+ if (this.value.spec.sourceType !== CLONE) {
+ return options;
+ }
+ if (this.value.spec.securityParameters.cryptoOperation === ENCRYPT) {
+ options = this.images.filter(image => !image.isEncrypted);
+ } else {
+ options = this.images.filter(image => image.isEncrypted);
+ }
+
+ return options.map(image => image.spec.displayName);
+ },
+ sourceImageName: {
+ get() {
+ return this.selectedImage?.spec.displayName;
+ },
+ set(imageDisplayName) {
+ this.selectedImage = this.images.find(i => i.spec.displayName === imageDisplayName);
+ // sourceImageName should bring the name of the image
+ this.value.spec.securityParameters.sourceImageName = this.selectedImage?.metadata.name || '';
+ }
+ },
+ sourceType: {
+ get() {
+ if (this.value.spec.sourceType === CLONE) {
+ return this.value.spec.securityParameters.cryptoOperation;
+ } else {
+ return this.value.spec.sourceType;
+ }
+ },
+
+ set(neu) {
+ if (neu === DECRYPT || neu === ENCRYPT) {
+ this.value.spec.sourceType = CLONE;
+ this.$set(this.value.spec, 'securityParameters', {
+ cryptoOperation: neu,
+ sourceImageName: '',
+ sourceImageNamespace: this.value.metadata.namespace
+ });
+ this.selectedImage = null;
+ } else {
+ this.$delete(this.value.spec, 'securityParameters');
+ this.value.spec.sourceType = neu;
+ }
+ }
+ }
},
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();
@@ -300,15 +370,14 @@ export default {
>
@@ -334,7 +403,7 @@ export default {
:tooltip="t('harvester.image.urlTip', {}, true)"
/>
-
diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml
index b964f77a..e1e7c633 100644
--- a/pkg/harvester/l10n/en-us.yaml
+++ b/pkg/harvester/l10n/en-us.yaml
@@ -207,6 +207,7 @@ harvester:
=1 {core}
other {cores}}
tableHeaders:
+ imageEncryption: Encryption
size: Size
virtualSize: Virtual Size
progress: Progress
@@ -767,6 +768,8 @@ harvester:
basics: Basics
url: URL
size: Size
+ isEncryption: Encryption
+ encryptionSecret: Encryption Secret
virtualSize: Virtual Size
urlTip: 'Supports the
raw and
qcow2 image formats which are supported by
qemu. Bootable ISO images can also be used and are treated like
raw images.'
fileName: File Name
@@ -775,6 +778,11 @@ harvester:
sourceType:
download: URL
upload: File
+ clone: Clone
+ encrypt: Encrypt
+ decrypt: Decrypt
+ sourceImage: Source Image
+ cryptoOperation: Crypto Operation
warning:
uploading: |-
{count, plural,
@@ -1100,6 +1108,9 @@ harvester:
storage:
label: Storage
useDefault: Use the default storage
+ volumeEncryption: Volume Encryption
+ secretName: Secret Name
+ secretNamespace: Secret Namespace
migratable:
label: Migratable
numberOfReplicas:
diff --git a/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue b/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue
index cc21d3b4..ac180274 100644
--- a/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue
+++ b/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue
@@ -75,6 +75,22 @@ export default {
+
+
+
+
+ {{ row.nameDisplay }}
+
+
+
+ {{ row.nameDisplay }}
+
+
+ |
+
diff --git a/pkg/harvester/list/harvesterhci.io.volume.vue b/pkg/harvester/list/harvesterhci.io.volume.vue
index 26f0df57..45ac6fc2 100644
--- a/pkg/harvester/list/harvesterhci.io.volume.vue
+++ b/pkg/harvester/list/harvesterhci.io.volume.vue
@@ -170,6 +170,22 @@ v-if="getVMName(scope.row)"