Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add volume and image encryption feature #1151

Merged
merged 8 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pkg/harvester/config/harvester-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,12 @@ export const ADD_ONS = {
RANCHER_MONITORING: 'rancher-monitoring',
VM_IMPORT_CONTROLLER: 'vm-import-controller',
};

export const CSI_SECRETS = {
CSI_PROVISIONER_SECRET_NAME: 'csi.storage.k8s.io/provisioner-secret-name',
CSI_PROVISIONER_SECRET_NAMESPACE: 'csi.storage.k8s.io/provisioner-secret-namespace',
CSI_NODE_PUBLISH_SECRET_NAME: 'csi.storage.k8s.io/node-publish-secret-name',
CSI_NODE_PUBLISH_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-publish-secret-namespace',
CSI_NODE_STAGE_SECRET_NAME: 'csi.storage.k8s.io/node-stage-secret-name',
CSI_NODE_STAGE_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-stage-secret-namespace',
};
2 changes: 0 additions & 2 deletions pkg/harvester/config/table-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ export const IMAGE_DOWNLOAD_SIZE = {
labelKey: 'tableHeaders.size',
value: 'downSize',
sort: 'status.size',
width: 120
};

export const IMAGE_VIRTUAL_SIZE = {
name: 'virtualSize',
labelKey: 'harvester.tableHeaders.virtualSize',
value: 'virtualSize',
sort: 'status.virtualSize',
width: 120
};

export const IMAGE_PROGRESS = {
Expand Down
49 changes: 47 additions & 2 deletions pkg/harvester/detail/harvesterhci.io.virtualmachineimage/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import Tabbed from '@shell/components/Tabbed';
import Tab from '@shell/components/Tabbed/Tab';
import { findBy } from '@shell/utils/array';
import { get } from '@shell/utils/object';

import { ucFirst } from '@shell/utils/string';
import Storage from './Storage';
import { SECRET } from '@shell/config/types';

export default {
components: {
Expand All @@ -26,8 +27,14 @@ export default {
},
},

async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;

this.secrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
},

data() {
return {};
return { secrets: [] };
},

computed: {
Expand Down Expand Up @@ -57,6 +64,21 @@ export default {
return this.value?.spec?.sourceType === 'upload';
},

encryptionSecret() {
if (!this.value.isEncrypted) {
return '-';
a110605 marked this conversation as resolved.
Show resolved Hide resolved
}

return this.value.encryptionSecret;
},
secretLink() {
return this.secrets.find(sc => sc.id === this.value.encryptionSecret)?.detailLocation;
},

isEncryptedString() {
return ucFirst(String(this.value.isEncrypted));
},

imageName() {
return this.value?.metadata?.annotations?.[HCI.IMAGE_NAME] || '-';
},
Expand Down Expand Up @@ -116,6 +138,29 @@ export default {
</div>
</div>

<div class="row">
<div class="col span-12">
<LabelValue :name="t('harvester.image.isEncryption')" :value="isEncryptedString" class="mb-20" />
</div>
</div>

<div v-if="value.isEncrypted" class="row">
<div class="col span-12">
<div class="text-label">
{{ t('harvester.image.encryptionSecret') }}
</div>
<n-link v-if="encryptionSecret && secretLink" :to="secretLink">
{{ encryptionSecret }}
</n-link>
<span v-else-if="encryptionSecret">
{{ encryptionSecret }}
</span>
<span v-else class="text-muted">
&mdash;
</span>
</div>
</div>

<div v-if="errorMessage !== '-'" class="row">
<div class="col span-12">
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ export default {

imageName() {
const imageList = this.$store.getters['harvester/all'](HCI.IMAGE) || [];

const image = imageList.find( (I) => {
return this.value.rootImageId === I.id;
});
const image = imageList.find( I => this.value.rootImageId === I.id);

return image?.spec?.displayName || 'N/A';
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,34 @@ 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';

// UI components for Longhorn storage class parameters
const DEFAULT_PARAMETERS = [
'numberOfReplicas',
'staleReplicaTimeout',
'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 {
name: 'DriverLonghornIO',

components: {
KeyValue,
LabeledSelect,
Expand All @@ -40,20 +53,30 @@ export default {
},
},

async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;

await this.$store.dispatch(`${ inStore }/findAll`, { type: NAMESPACE });

const allSecrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });

// only show non-system secret to user to select
this.secrets = allSecrets.filter(secret => secret.isSystem === false);
a110605 marked this conversation as resolved.
Show resolved Hide resolved
},
data() {
if (this.realMode === _CREATE) {
this.$set(this.value, 'parameters', {
numberOfReplicas: '3',
staleReplicaTimeout: '30',
diskSelector: null,
nodeSelector: null,
encrypted: 'false',
migratable: 'true',
});
}

return {};
return { secrets: [] };
},

computed: {
longhornNodes() {
const inStore = this.$store.getters['currentProduct'].inStore;
Expand Down Expand Up @@ -97,11 +120,29 @@ export default {
}];
},

secretOptions() {
return this.secrets.map(secret => secret.id);
},

volumeEncryptionOptions() {
return [{
label: this.t('generic.yes'),
value: 'true'
}, {
label: this.t('generic.no'),
value: 'false'
}];
},

parameters: {
get() {
const parameters = clone(this.value?.parameters) || {};

DEFAULT_PARAMETERS.map((key) => {
DEFAULT_PARAMETERS.forEach((key) => {
delete parameters[key];
});

Object.values(CSI_SECRETS).forEach((key) => {
delete parameters[key];
});

Expand All @@ -113,6 +154,46 @@ export default {
}
},

volumeEncryption: {
set(neu) {
this.$set(this.value, 'parameters', {
...this.value.parameters,
encrypted: neu
});
},

get() {
return this.value?.parameters?.encrypted || 'false';
}
},

secret: {
get() {
const selectedNs = this.value.parameters[CSI_PROVISIONER_SECRET_NAMESPACE];
const selectedName = this.value.parameters[CSI_PROVISIONER_SECRET_NAME];

if (selectedNs && selectedName) {
return `${ selectedNs }/${ selectedName }`;
}

return '';
},

set(selectedSecret) {
const [namespace, name] = selectedSecret.split('/');

this.$set(this.value, 'parameters', {
...this.value.parameters,
[CSI_PROVISIONER_SECRET_NAME]: name,
[CSI_NODE_PUBLISH_SECRET_NAME]: name,
[CSI_NODE_STAGE_SECRET_NAME]: name,
[CSI_PROVISIONER_SECRET_NAMESPACE]: namespace,
[CSI_NODE_PUBLISH_SECRET_NAMESPACE]: namespace,
[CSI_NODE_STAGE_SECRET_NAMESPACE]: namespace
});
}
},

nodeSelector: {
get() {
const nodeSelector = this.value?.parameters?.nodeSelector;
Expand Down Expand Up @@ -221,14 +302,31 @@ export default {
</LabeledSelect>
</div>
</div>
<div class="row mt-10">
<div class="row mt-20">
<RadioGroup
v-model="value.parameters.migratable"
name="layer3NetworkMode"
:label="t('harvester.storage.parameters.migratable.label')"
:mode="mode"
:options="migratableOptions"
/>
</div>
<div class="row mt-20">
<RadioGroup
v-model="volumeEncryption"
name="volumeEncryption"
:label="t('harvester.storage.volumeEncryption')"
:mode="mode"
:options="volumeEncryptionOptions"
/>
</div>
<div v-if="value.parameters.encrypted === 'true'" class="row mt-20">
<div class="col span-6">
<RadioGroup
v-model="value.parameters.migratable"
name="layer3NetworkMode"
:label="t('harvester.storage.parameters.migratable.label')"
<LabeledSelect
v-model="secret"
:label="t('harvester.storage.secret')"
:options="secretOptions"
:mode="mode"
:options="migratableOptions"
/>
</div>
</div>
Expand Down
Loading
Loading