feat: support manually configuring VM IP (#315)

* feat: add annotations

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

* feat: map annotation

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

* feat: add annotaion to template

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

---------

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
This commit is contained in:
Yiya Chen 2025-06-06 10:03:37 +08:00 committed by GitHub
parent 01528f7429
commit 29b1ab2fb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 10 deletions

View File

@ -68,5 +68,6 @@ export const HCI = {
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
K8S_ARCH: 'kubernetes.io/arch',
IMAGE_DISPLAY_NAME: 'harvesterhci.io/imageDisplayName'
IMAGE_DISPLAY_NAME: 'harvesterhci.io/imageDisplayName',
CUSTOM_IP: 'harvesterhci.io/custom-ip'
};

View File

@ -189,10 +189,16 @@ export default {
}
this.value.spec['templateId'] = `${ namespace }/${ name }`;
// inherit labels and annotations so the VM gets them when created from the template
this.value.spec.vm.metadata.labels = {
...this.value.spec.vm.metadata.labels,
...this.value.metadata.labels
};
this.value.spec.vm.metadata.annotations = {
...this.value.spec.vm.metadata.annotations,
...this.value.metadata.annotations
};
const res = await this.value.save();
await this.saveSecret(res);
@ -363,6 +369,26 @@ export default {
@update:value="value.setInstanceLabels($event)"
/>
</Tab>
<Tab
name="annotations"
:label="t('harvester.tab.annotations')"
:weight="-11"
>
<Banner color="info">
<t k="harvester.virtualMachine.annotations.banner" />
</Banner>
<KeyValue
key="annotations"
:value="value.annotations"
:protected-keys="value.systemAnnotations || []"
:toggle-filter="true"
:add-label="t('labels.addAnnotation')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setAnnotations($event)"
/>
</Tab>
<Tab
name="advanced"
:label="t('harvester.tab.advanced')"

View File

@ -255,9 +255,10 @@ export default {
cloneVersionVM.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
// Update labels and instance labels value
// Update labels, instance labels and annotations
this.value.metadata.labels = cloneVersionVM.metadata.labels;
this.value.spec.template.metadata.labels = cloneVersionVM.spec.template.metadata.labels;
this.value.metadata.annotations = cloneVersionVM.metadata.annotations;
this.getInitConfig({
value: cloneVersionVM, existUserData: true, fromTemplate: true
@ -764,10 +765,31 @@ export default {
/>
</Tab>
<Tab
name="annotations"
:label="t('harvester.tab.annotations')"
:weight="-11"
>
<Banner color="info">
<t k="harvester.virtualMachine.annotations.banner" />
</Banner>
<KeyValue
key="annotations"
:value="value.annotations"
:protected-keys="value.systemAnnotations || []"
:toggle-filter="toggler"
:add-label="t('labels.addAnnotation')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setAnnotations($event)"
/>
</Tab>
<Tab
name="advanced"
:label="t('harvester.tab.advanced')"
:weight="-11"
:weight="-12"
>
<div class="row mb-20">
<div class="col span-6">

View File

@ -35,11 +35,26 @@ export default {
},
computed: {
// Return VM instance IP and VM annotation IP
ips() {
return [...this.vmiIp, ...this.networkAnnotationIP]
.filter(Boolean)
.sort((a, b) => a.ip < b.ip ? -1 : 1);
if (this.vmiIp.length) {
return [...this.vmiIp, ...this.networkAnnotationIP]
.filter(Boolean)
.sort((a, b) => a.ip < b.ip ? -1 : 1);
}
return this.customAnnotationIP;
},
customAnnotationIP() {
const annotationIp = get(this.row, `metadata.annotations."${ HCI_ANNOTATIONS.CUSTOM_IP }"`);
if (annotationIp && isIpv4(annotationIp)) {
return [{
name: 'custom-ip', ip: annotationIp, isCustom: true
}];
}
return [];
},
networkAnnotationIP() {
@ -97,12 +112,13 @@ export default {
<template>
<div v-if="showIP">
<span
v-for="{ip, name} in ips"
:key="ip"
v-for="{ ip, name, isCustom } in ips"
:key="`${ip}-${name}`"
>
<CopyToClipboardText
v-clean-tooltip="name"
v-clean-tooltip="isCustom ? t('harvester.formatters.harvesterIpAddress.customIpTooltip') : name"
:text="ip"
:plain="isCustom"
/>
</span>
</div>

View File

@ -229,6 +229,8 @@ harvester:
{count, plural,
=1 {core}
other {cores}}
harvesterIpAddress:
customIpTooltip: "Custom IP (set via annotation)"
tableHeaders:
imageEncryption: Encryption
size: Size
@ -276,6 +278,7 @@ harvester:
quotas: Quotas
snapshots: Snapshots
instanceLabel: Instance Labels
annotations: Annotations
fields:
version: Version
name: Name
@ -792,6 +795,8 @@ harvester:
banner: These labels are automatically synchronized to the virtual machine instance.
labels:
banner: These key values are added as labels to the virtual machine.
annotations:
banner: These key values are added as annotations to the virtual machine.
volume:
label: Volumes

View File

@ -284,4 +284,10 @@ export default class HciVmTemplateVersion extends HarvesterResource {
get efiPersistentStateFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
}
get systemAnnotations() {
const annotations = this.annotations || {};
return Object.keys(annotations).filter((key) => key.includes(HCI_ANNOTATIONS.TEMPLATE_VERSION_CUSTOM_NAME));
}
}