@rancher/shell 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/assets/translations/en-us.yaml +27 -769
  2. package/assets/translations/zh-hans.yaml +8 -769
  3. package/components/ActionMenu.vue +3 -3
  4. package/components/CodeMirror.vue +6 -8
  5. package/components/CommunityLinks.vue +1 -1
  6. package/components/ContainerResourceLimit.vue +14 -0
  7. package/components/ExplorerMembers.vue +123 -0
  8. package/components/ExplorerProjectsNamespaces.vue +405 -0
  9. package/components/GrafanaDashboard.vue +17 -2
  10. package/components/LocaleSelector.vue +81 -0
  11. package/components/PromptModal.vue +2 -3
  12. package/components/ResourceList/index.vue +1 -1
  13. package/components/ResourceTable.vue +3 -6
  14. package/components/SingleClusterInfo.vue +1 -1
  15. package/components/SortableTable/index.vue +23 -20
  16. package/components/SortableTable/selection.js +1 -0
  17. package/components/auth/AzureWarning.vue +5 -1
  18. package/components/auth/Principal.vue +1 -1
  19. package/components/auth/RoleDetailEdit.vue +18 -11
  20. package/components/fleet/FleetRepos.vue +0 -2
  21. package/components/form/NameNsDescription.vue +4 -6
  22. package/components/form/NodeScheduling.vue +1 -1
  23. package/components/form/WorkloadPorts.vue +1 -1
  24. package/components/formatter/WorkloadHealthScale.vue +1 -1
  25. package/components/nav/Header.vue +9 -9
  26. package/components/nav/NamespaceFilter.vue +7 -4
  27. package/components/nav/TopLevelMenu.vue +6 -43
  28. package/components/nav/WindowManager/ContainerLogs.vue +1 -1
  29. package/config/product/harvester-manager.js +64 -2
  30. package/config/product/manager.js +9 -0
  31. package/config/settings.js +17 -71
  32. package/config/table-headers.js +0 -1
  33. package/config/types.js +5 -25
  34. package/core/plugin-routes.ts +34 -22
  35. package/core/plugin.ts +15 -3
  36. package/core/plugins-loader.js +2 -0
  37. package/core/plugins.js +79 -36
  38. package/core/types.ts +7 -1
  39. package/detail/provisioning.cattle.io.cluster.vue +13 -0
  40. package/detail/workload/index.vue +11 -5
  41. package/{components/dialog → dialog}/AddClusterMemberDialog.vue +0 -0
  42. package/{components/dialog → dialog}/AddCustomBadgeDialog.vue +0 -0
  43. package/{components/dialog → dialog}/AddProjectMemberDialog.vue +0 -0
  44. package/{components/dialog → dialog}/AddonConfigConfirmationDialog.vue +0 -0
  45. package/{components/dialog → dialog}/DrainNode.vue +0 -0
  46. package/{components/dialog → dialog}/ForceMachineRemoveDialog.vue +0 -0
  47. package/{components/dialog → dialog}/GenericPrompt.vue +0 -0
  48. package/{components/dialog → dialog}/RollbackWorkloadDialog.vue +0 -0
  49. package/{components/dialog → dialog}/RotateCertificatesDialog.vue +0 -0
  50. package/{components/dialog → dialog}/RotateEncryptionKeyDialog.vue +0 -0
  51. package/{components/dialog → dialog}/SaveAsRKETemplateDialog.vue +0 -0
  52. package/{components/dialog → dialog}/ScaleMachineDownDialog.vue +0 -0
  53. package/edit/auth/azuread.vue +20 -1
  54. package/edit/management.cattle.io.project.vue +2 -2
  55. package/edit/namespace.vue +17 -10
  56. package/edit/persistentvolumeclaim.vue +1 -0
  57. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
  58. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +33 -5
  59. package/edit/service.vue +1 -1
  60. package/edit/workload/index.vue +363 -15
  61. package/edit/workload/mixins/workload.js +62 -7
  62. package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +1 -0
  63. package/layouts/default.vue +52 -27
  64. package/layouts/error.vue +5 -1
  65. package/layouts/home.vue +6 -2
  66. package/list/harvesterhci.io.management.cluster.vue +74 -33
  67. package/list/namespace.vue +3 -5
  68. package/machine-config/amazonec2.vue +2 -0
  69. package/machine-config/harvester.vue +96 -49
  70. package/middleware/authenticated.js +56 -52
  71. package/mixins/form-validation.js +1 -1
  72. package/mixins/resource-fetch.js +3 -1
  73. package/models/fleet.cattle.io.bundle.js +26 -19
  74. package/models/harvesterhci.io.management.cluster.js +194 -5
  75. package/models/management.cattle.io.cluster.js +1 -1
  76. package/models/management.cattle.io.clusterroletemplatebinding.js +9 -0
  77. package/models/management.cattle.io.project.js +23 -2
  78. package/models/namespace.js +19 -3
  79. package/models/pod.js +19 -2
  80. package/models/provisioning.cattle.io.cluster.js +4 -0
  81. package/models/workload.js +4 -243
  82. package/models/workload.service.js +314 -0
  83. package/nuxt.config.js +11 -9
  84. package/package.json +3 -3
  85. package/pages/auth/login.vue +11 -2
  86. package/pages/auth/setup.vue +1 -1
  87. package/pages/c/_cluster/_product/members/index.vue +3 -93
  88. package/pages/c/_cluster/_product/projectsnamespaces.vue +6 -403
  89. package/pages/c/_cluster/settings/performance.vue +19 -16
  90. package/pages/fail-whale.vue +1 -10
  91. package/pages/index.vue +18 -4
  92. package/pages/plugins.vue +2 -2
  93. package/pages/prefs.vue +8 -6
  94. package/pkg/auto-import.js +44 -7
  95. package/pkg/dynamic-plugin-loader.js +28 -0
  96. package/pkg/import.js +2 -2
  97. package/pkg/model-loader-require.lib.js +3 -0
  98. package/pkg/vue.config.js +9 -6
  99. package/plugins/dashboard-store/model-loader-require.js +12 -0
  100. package/plugins/dashboard-store/model-loader.js +4 -1
  101. package/plugins/dashboard-store/resource-class.js +10 -3
  102. package/plugins/steve/actions.js +1 -1
  103. package/plugins/steve/index.js +6 -4
  104. package/plugins/steve/subscribe.js +34 -23
  105. package/rancher-components/Form/Checkbox/Checkbox.test.ts +77 -0
  106. package/rancher-components/Form/Checkbox/Checkbox.vue +12 -2
  107. package/scripts/build-pkg.sh +48 -2
  108. package/scripts/drone-build-pkg.sh +31 -0
  109. package/scripts/publish-shell.sh +10 -11
  110. package/scripts/serve-pkgs +17 -10
  111. package/store/catalog.js +3 -1
  112. package/store/i18n.js +16 -11
  113. package/store/index.js +4 -181
  114. package/store/prefs.js +30 -2
  115. package/store/type-map.js +16 -29
  116. package/utils/cluster.js +1 -1
  117. package/utils/custom-validators.js +1 -12
  118. package/utils/dynamic-importer.js +1 -1
  119. package/utils/validators/setting.js +0 -35
  120. package/components/FilterLabel.vue +0 -254
  121. package/components/HarvesterUpgradeProgressBarList.vue +0 -109
  122. package/components/VMConsoleBar.vue +0 -87
  123. package/components/dialog/harvester/AddHotplugModal.vue +0 -159
  124. package/components/dialog/harvester/BackupModal.vue +0 -117
  125. package/components/dialog/harvester/CloneTemplate.vue +0 -125
  126. package/components/dialog/harvester/EjectCDROMDialog.vue +0 -157
  127. package/components/dialog/harvester/ExportImageDialog.vue +0 -152
  128. package/components/dialog/harvester/MaintenanceDialog.vue +0 -94
  129. package/components/dialog/harvester/MigrationDialog.vue +0 -154
  130. package/components/dialog/harvester/RestoreDialog.vue +0 -153
  131. package/components/dialog/harvester/SupportBundle.vue +0 -217
  132. package/components/dialog/harvester/UnplugVolume.vue +0 -108
  133. package/components/form/SerialConsole/index.vue +0 -267
  134. package/components/formatter/AttachVMWithName.vue +0 -46
  135. package/components/formatter/CloudInitType.vue +0 -27
  136. package/components/formatter/HarvesterBackupTargetValidation.vue +0 -43
  137. package/components/formatter/HarvesterCPUUsed.vue +0 -122
  138. package/components/formatter/HarvesterDiskState.vue +0 -66
  139. package/components/formatter/HarvesterHostName.vue +0 -66
  140. package/components/formatter/HarvesterIpAddress.vue +0 -90
  141. package/components/formatter/HarvesterMemoryUsed.vue +0 -140
  142. package/components/formatter/HarvesterMigrationState.vue +0 -85
  143. package/components/formatter/HarvesterNodeName.vue +0 -49
  144. package/components/formatter/HarvesterStorageUsed.vue +0 -194
  145. package/components/formatter/HarvesterVmState.vue +0 -123
  146. package/components/nav/HarvesterUpgrade.vue +0 -232
  147. package/components/novnc/NovncConsole.vue +0 -93
  148. package/components/novnc/NovncConsoleItem.vue +0 -89
  149. package/components/novnc/NovncConsoleWrapper.vue +0 -243
  150. package/config/harvester-map.js +0 -44
  151. package/config/harvester-table-headers.js +0 -27
  152. package/config/product/harvester.js +0 -305
  153. package/detail/harvesterhci.io.host/HarvesterHostBasic.vue +0 -364
  154. package/detail/harvesterhci.io.host/HarvesterHostDisk.vue +0 -200
  155. package/detail/harvesterhci.io.host/HarvesterHostNetwork.vue +0 -89
  156. package/detail/harvesterhci.io.host/VirtualMachineInstance.vue +0 -134
  157. package/detail/harvesterhci.io.host/index.vue +0 -243
  158. package/detail/harvesterhci.io.virtualmachinebackup/index.vue +0 -221
  159. package/detail/harvesterhci.io.virtualmachineimage.vue +0 -118
  160. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineBasics.vue +0 -279
  161. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineEvents.vue +0 -75
  162. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineKeypairs.vue +0 -114
  163. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineMigration.vue +0 -79
  164. package/detail/kubevirt.io.virtualmachine/index.vue +0 -213
  165. package/edit/harvesterhci.io.cloudtemplate.vue +0 -123
  166. package/edit/harvesterhci.io.host/HarvesterDisk.vue +0 -262
  167. package/edit/harvesterhci.io.host/index.vue +0 -533
  168. package/edit/harvesterhci.io.keypair.vue +0 -112
  169. package/edit/harvesterhci.io.managedchart/index.vue +0 -25
  170. package/edit/harvesterhci.io.managedchart/rancher-monitoring.vue +0 -172
  171. package/edit/harvesterhci.io.networkattachmentdefinition.vue +0 -210
  172. package/edit/harvesterhci.io.setting/additional-ca.vue +0 -36
  173. package/edit/harvesterhci.io.setting/backup-target.vue +0 -182
  174. package/edit/harvesterhci.io.setting/http-proxy.vue +0 -79
  175. package/edit/harvesterhci.io.setting/index.vue +0 -201
  176. package/edit/harvesterhci.io.setting/overcommit-config.vue +0 -94
  177. package/edit/harvesterhci.io.setting/ssl-certificates.vue +0 -117
  178. package/edit/harvesterhci.io.setting/ssl-parameters.vue +0 -161
  179. package/edit/harvesterhci.io.setting/support-bundle-image.vue +0 -134
  180. package/edit/harvesterhci.io.setting/support-bundle-namespaces.vue +0 -73
  181. package/edit/harvesterhci.io.setting/vip-pools.vue +0 -244
  182. package/edit/harvesterhci.io.setting/vm-force-reset-policy.vue +0 -81
  183. package/edit/harvesterhci.io.virtualmachinebackup.vue +0 -256
  184. package/edit/harvesterhci.io.virtualmachineimage.vue +0 -364
  185. package/edit/harvesterhci.io.virtualmachinetemplateversion.vue +0 -340
  186. package/edit/harvesterhci.io.volume.vue +0 -195
  187. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/AccessCredentialsUsers.vue +0 -190
  188. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/index.vue +0 -212
  189. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/type/basicAuth.vue +0 -94
  190. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/type/sshkey.vue +0 -85
  191. package/edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig/DataTemplate.vue +0 -153
  192. package/edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig/index.vue +0 -279
  193. package/edit/kubevirt.io.virtualmachine/VirtualMachineCpuMemory.vue +0 -113
  194. package/edit/kubevirt.io.virtualmachine/VirtualMachineNetwork/__tests__/HarvesterEditNetwork.test.ts +0 -41
  195. package/edit/kubevirt.io.virtualmachine/VirtualMachineNetwork/base.vue +0 -281
  196. package/edit/kubevirt.io.virtualmachine/VirtualMachineNetwork/index.vue +0 -142
  197. package/edit/kubevirt.io.virtualmachine/VirtualMachineReserved.vue +0 -54
  198. package/edit/kubevirt.io.virtualmachine/VirtualMachineSSHKey.vue +0 -256
  199. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/index.vue +0 -391
  200. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditContainer.test.ts +0 -40
  201. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditExisting.test.ts +0 -102
  202. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVMImage.test.ts +0 -117
  203. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVolume.test.ts +0 -74
  204. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/container.vue +0 -132
  205. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/existing.vue +0 -303
  206. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/vmImage.vue +0 -285
  207. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/volume.vue +0 -188
  208. package/edit/kubevirt.io.virtualmachine/index.vue +0 -642
  209. package/edit/network.harvesterhci.io.clusternetwork/index.vue +0 -19
  210. package/edit/network.harvesterhci.io.clusternetwork/vlan.vue +0 -134
  211. package/edit/workload/types/Deployment.vue +0 -377
  212. package/edit/workload/types/Generic.vue +0 -295
  213. package/list/harvesterhci.io.cloudtemplate.vue +0 -78
  214. package/list/harvesterhci.io.dashboard/HarvesterUpgrade.vue +0 -211
  215. package/list/harvesterhci.io.dashboard/UpgradeInfo.vue +0 -40
  216. package/list/harvesterhci.io.dashboard/index.vue +0 -752
  217. package/list/harvesterhci.io.host/index.vue +0 -186
  218. package/list/harvesterhci.io.networkattachmentdefinition.vue +0 -167
  219. package/list/harvesterhci.io.setting.vue +0 -241
  220. package/list/harvesterhci.io.virtualmachinebackup.vue +0 -172
  221. package/list/harvesterhci.io.virtualmachineimage.vue +0 -80
  222. package/list/harvesterhci.io.virtualmachinetemplateversion.vue +0 -173
  223. package/list/harvesterhci.io.volume.vue +0 -122
  224. package/list/kubevirt.io.virtualmachine.vue +0 -193
  225. package/mixins/harvester-vm/impl.js +0 -267
  226. package/mixins/harvester-vm/index.js +0 -1357
  227. package/models/harvester/configmap.js +0 -32
  228. package/models/harvester/harvesterhci.io.blockdevice.js +0 -55
  229. package/models/harvester/harvesterhci.io.keypair.js +0 -12
  230. package/models/harvester/harvesterhci.io.setting.js +0 -127
  231. package/models/harvester/harvesterhci.io.supportbundle.js +0 -35
  232. package/models/harvester/harvesterhci.io.upgrade.js +0 -226
  233. package/models/harvester/harvesterhci.io.virtualmachinebackup.js +0 -116
  234. package/models/harvester/harvesterhci.io.virtualmachineimage.js +0 -255
  235. package/models/harvester/harvesterhci.io.virtualmachinerestore.js +0 -43
  236. package/models/harvester/harvesterhci.io.virtualmachinetemplate.js +0 -69
  237. package/models/harvester/harvesterhci.io.virtualmachinetemplateversion.js +0 -227
  238. package/models/harvester/k8s.cni.cncf.io.networkattachmentdefinition.js +0 -32
  239. package/models/harvester/kubevirt.io.virtualmachine.js +0 -850
  240. package/models/harvester/kubevirt.io.virtualmachineinstance.js +0 -142
  241. package/models/harvester/management.cattle.io.managedchart.js +0 -191
  242. package/models/harvester/management.cattle.io.setting.js +0 -40
  243. package/models/harvester/network.harvesterhci.io.clusternetwork.js +0 -100
  244. package/models/harvester/network.harvesterhci.io.nodenetwork.js +0 -34
  245. package/models/harvester/node.js +0 -255
  246. package/models/harvester/persistentvolumeclaim.js +0 -166
  247. package/models/harvester/pod.js +0 -185
  248. package/pages/c/_cluster/harvester/airgapupgrade/index.vue +0 -309
  249. package/pages/c/_cluster/harvester/console/_uid/serial.vue +0 -51
  250. package/pages/c/_cluster/harvester/console/_uid/vnc.vue +0 -52
  251. package/pages/c/_cluster/harvester/index.vue +0 -24
  252. package/pages/c/_cluster/harvester/support/index.vue +0 -154
  253. package/pkg/model-loader.lib.js +0 -3
  254. package/promptRemove/kubevirt.io.virtualmachine.vue +0 -164
  255. package/store/harvester-common.js +0 -126
  256. package/utils/validators/vm-datavolumes.js +0 -38
  257. package/utils/validators/vm-image.js +0 -32
  258. package/utils/validators/vm.js +0 -221
@@ -1,1357 +0,0 @@
1
- import YAML from 'yaml';
2
- import jsyaml from 'js-yaml';
3
- import isEqual from 'lodash/isEqual';
4
- import isEmpty from 'lodash/isEmpty';
5
- import difference from 'lodash/difference';
6
-
7
- import { sortBy } from '@shell/utils/sort';
8
- import { set } from '@shell/utils/object';
9
-
10
- import { allHash } from '@shell/utils/promise';
11
- import { randomStr } from '@shell/utils/string';
12
- import { base64Decode } from '@shell/utils/crypto';
13
- import { formatSi, parseSi } from '@shell/utils/units';
14
- import { SOURCE_TYPE, ACCESS_CREDENTIALS } from '@shell/config/harvester-map';
15
- import { _CLONE } from '@shell/config/query-params';
16
- import {
17
- PVC, HCI, STORAGE_CLASS, NODE, SECRET, CONFIG_MAP, NETWORK_ATTACHMENT
18
- } from '@shell/config/types';
19
- import { HCI_SETTING } from '@shell/config/settings';
20
- import { HCI as HCI_ANNOTATIONS, HOSTNAME } from '@shell/config/labels-annotations';
21
- import impl, { QGA_JSON, USB_TABLET } from '@shell/mixins/harvester-vm/impl';
22
-
23
- export const MANAGEMENT_NETWORK = 'management Network';
24
-
25
- export const OS = [{
26
- label: 'Windows',
27
- value: 'windows'
28
- }, {
29
- label: 'Linux',
30
- value: 'linux'
31
- }, {
32
- label: 'Debian',
33
- value: 'debian'
34
- }, {
35
- label: 'Fedora',
36
- value: 'fedora'
37
- }, {
38
- label: 'Gentoo',
39
- value: 'gentoo'
40
- }, {
41
- label: 'Mandriva',
42
- value: 'mandriva'
43
- }, {
44
- label: 'Oracle',
45
- value: 'oracle'
46
- }, {
47
- label: 'Red Hat',
48
- value: 'redhat'
49
- }, {
50
- label: 'openSUSE',
51
- value: 'openSUSE',
52
- }, {
53
- label: 'Turbolinux',
54
- value: 'turbolinux'
55
- }, {
56
- label: 'Ubuntu',
57
- value: 'ubuntu'
58
- }, {
59
- label: 'Xandros',
60
- value: 'xandros'
61
- }, {
62
- label: 'Other Linux',
63
- match: ['centos'],
64
- value: 'otherLinux'
65
- }];
66
-
67
- export const CD_ROM = 'cd-rom';
68
- export const HARD_DISK = 'disk';
69
-
70
- export default {
71
- mixins: [impl],
72
-
73
- props: {
74
- value: {
75
- type: Object,
76
- required: true,
77
- },
78
-
79
- resource: {
80
- type: String,
81
- default: ''
82
- }
83
- },
84
-
85
- async fetch() {
86
- const hash = {
87
- pvcs: this.$store.dispatch('harvester/findAll', { type: PVC }),
88
- storageClasses: this.$store.dispatch('harvester/findAll', { type: STORAGE_CLASS }),
89
- sshs: this.$store.dispatch('harvester/findAll', { type: HCI.SSH }),
90
- settings: this.$store.dispatch('harvester/findAll', { type: HCI.SETTING }),
91
- images: this.$store.dispatch('harvester/findAll', { type: HCI.IMAGE }),
92
- versions: this.$store.dispatch('harvester/findAll', { type: HCI.VM_VERSION }),
93
- templates: this.$store.dispatch('harvester/findAll', { type: HCI.VM_TEMPLATE }),
94
- networkAttachment: this.$store.dispatch('harvester/findAll', { type: NETWORK_ATTACHMENT }),
95
- vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
96
- vmims: this.$store.dispatch('harvester/findAll', { type: HCI.VMIM }),
97
- vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
98
- secrets: this.$store.dispatch('harvester/findAll', { type: SECRET }),
99
- };
100
-
101
- if (this.$store.getters['harvester/schemaFor'](NODE)) {
102
- hash.nodes = this.$store.dispatch('harvester/findAll', { type: NODE });
103
- }
104
- await allHash(hash);
105
- },
106
-
107
- data() {
108
- const isClone = this.realMode === _CLONE;
109
-
110
- return {
111
- OS,
112
- isClone,
113
- spec: null,
114
- osType: 'linux',
115
- sshKey: [],
116
- runStrategy: 'RerunOnFailure',
117
- installAgent: true,
118
- hasCreateVolumes: [],
119
- installUSBTablet: true,
120
- networkScript: '',
121
- userScript: '',
122
- imageId: '',
123
- diskRows: [],
124
- networkRows: [],
125
- machineType: '',
126
- secretName: '',
127
- secretRef: null,
128
- showAdvanced: false,
129
- deleteAgent: true,
130
- memory: null,
131
- cpu: '',
132
- reservedMemory: null,
133
- accessCredentials: [],
134
- efiEnabled: false,
135
- secureBoot: false,
136
- userDataTemplateId: '',
137
- };
138
- },
139
-
140
- computed: {
141
- images() {
142
- return this.$store.getters['harvester/all'](HCI.IMAGE);
143
- },
144
-
145
- versions() {
146
- return this.$store.getters['harvester/all'](HCI.VM_VERSION);
147
- },
148
-
149
- templates() {
150
- return this.$store.getters['harvester/all'](HCI.VM_TEMPLATE);
151
- },
152
-
153
- pvcs() {
154
- return this.$store.getters['harvester/all'](PVC);
155
- },
156
-
157
- secrets() {
158
- return this.$store.getters['harvester/all'](SECRET);
159
- },
160
-
161
- nodesIdOptions() {
162
- const nodes = this.$store.getters['harvester/all'](NODE);
163
-
164
- return nodes.filter(N => !N.isUnSchedulable).map((node) => {
165
- return {
166
- label: node.nameDisplay,
167
- value: node.id
168
- };
169
- });
170
- },
171
-
172
- defaultStorageClass() {
173
- const defaultStorage = this.$store.getters['harvester/all'](STORAGE_CLASS).find( O => O.isDefault);
174
-
175
- return defaultStorage?.metadata?.name || 'longhorn';
176
- },
177
-
178
- storageClassSetting() {
179
- try {
180
- const storageClassValue = this.$store.getters['harvester/all'](HCI.SETTING).find( O => O.id === HCI_SETTING.DEFAULT_STORAGE_CLASS)?.value;
181
-
182
- return JSON.parse(storageClassValue);
183
- } catch (e) {
184
- return {};
185
- }
186
- },
187
-
188
- customDefaultStorageClass() {
189
- return this.storageClassSetting.storageClass;
190
- },
191
-
192
- customVolumeMode() {
193
- return this.storageClassSetting.volumeMode || 'Block';
194
- },
195
-
196
- customAccessMode() {
197
- return this.storageClassSetting.accessModes || 'ReadWriteMany';
198
- },
199
-
200
- isWindows() {
201
- return this.osType === 'windows';
202
- },
203
-
204
- needNewSecret() {
205
- // When creating a template it is always necessary to create a new secret.
206
- return this.resource === HCI.VM_VERSION || this.isCreate;
207
- },
208
- },
209
-
210
- async created() {
211
- await this.$store.dispatch('harvester/findAll', { type: SECRET });
212
-
213
- this.getInitConfig({ value: this.value });
214
- },
215
-
216
- methods: {
217
- getInitConfig(config) {
218
- const { value } = config;
219
-
220
- const vm = this.resource === HCI.VM ? value : this.resource === HCI.BACKUP ? this.value.status?.source : value.spec.vm;
221
-
222
- const spec = vm?.spec;
223
-
224
- if (!spec) {
225
- return;
226
- }
227
- const resources = spec.template.spec.domain.resources;
228
-
229
- // If the user is created via yaml, there may be no "resources.limits": kubectl apply -f https://kubevirt.io/labs/manifests/vm.yaml
230
- if (!resources?.limits || (resources?.limits && !resources?.limits?.memory && resources?.limits?.memory !== null)) {
231
- spec.template.spec.domain.resources = {
232
- ...spec.template.spec.domain.resources,
233
- limits: {
234
- ...spec.template.spec.domain.resources.limits,
235
- memory: spec.template.spec.domain.resources.requests.memory
236
- }
237
- };
238
- }
239
-
240
- const runStrategy = spec.runStrategy || 'RerunOnFailure';
241
- const machineType = value.machineType;
242
- const cpu = spec.template.spec.domain?.cpu?.cores;
243
- const memory = spec.template.spec.domain.resources.limits.memory;
244
- const reservedMemory = vm.metadata?.annotations?.[HCI_ANNOTATIONS.VM_RESERVED_MEMORY];
245
-
246
- const sshKey = this.getSSHFromAnnotation(spec) || [];
247
-
248
- const imageId = this.getRootImageId(vm) || '';
249
- const diskRows = this.getDiskRows(vm);
250
- const networkRows = this.getNetworkRows(vm);
251
- const hasCreateVolumes = this.getHasCreatedVolumes(spec) || [];
252
-
253
- let { userData = undefined, networkData = undefined } = this.getSecretCloudData(spec);
254
-
255
- if (this.resource === HCI.BACKUP) {
256
- const secretBackups = this.value.status?.secretBackups;
257
-
258
- if (secretBackups) {
259
- const secretNetworkData = secretBackups[0]?.data?.networkdata || '';
260
- const secretUserData = secretBackups[0]?.data?.userdata || '';
261
-
262
- userData = base64Decode(secretUserData);
263
- networkData = base64Decode(secretNetworkData);
264
- }
265
- }
266
- const osType = this.getOsType(vm) || 'linux';
267
-
268
- userData = this.isCreate ? this.getInitUserData({ osType }) : userData;
269
- const installUSBTablet = this.isInstallUSBTablet(spec);
270
- const installAgent = this.isCreate ? true : this.hasInstallAgent(userData, osType, true);
271
- const efiEnabled = this.isEfiEnabled(spec);
272
- const secureBoot = this.isSecureBoot(spec);
273
-
274
- const secretRef = this.getSecret(spec);
275
- const accessCredentials = this.getAccessCredentials(spec);
276
-
277
- if (Object.prototype.hasOwnProperty.call(spec, 'running')) {
278
- delete spec.running;
279
- spec.runStrategy = 'RerunOnFailure';
280
- }
281
-
282
- this.$set(this, 'spec', spec);
283
- this.$set(this, 'runStrategy', runStrategy);
284
- this.$set(this, 'secretRef', secretRef);
285
- this.$set(this, 'accessCredentials', accessCredentials);
286
- this.$set(this, 'userScript', userData);
287
- this.$set(this, 'networkScript', networkData);
288
-
289
- this.$set(this, 'sshKey', sshKey);
290
- this.$set(this, 'osType', osType);
291
- this.$set(this, 'installAgent', installAgent);
292
-
293
- this.$set(this, 'cpu', cpu);
294
- this.$set(this, 'memory', memory);
295
- this.$set(this, 'reservedMemory', reservedMemory);
296
- this.$set(this, 'machineType', machineType);
297
-
298
- this.$set(this, 'installUSBTablet', installUSBTablet);
299
- this.$set(this, 'efiEnabled', efiEnabled);
300
- this.$set(this, 'secureBoot', secureBoot);
301
-
302
- this.$set(this, 'hasCreateVolumes', hasCreateVolumes);
303
- this.$set(this, 'networkRows', networkRows);
304
- this.$set(this, 'imageId', imageId);
305
-
306
- this.$set(this, 'diskRows', diskRows);
307
- },
308
-
309
- getDiskRows(vm) {
310
- const namespace = vm.metadata.namespace;
311
- const _volumes = vm.spec.template.spec.volumes || [];
312
- const _disks = vm.spec.template.spec.domain.devices.disks || [];
313
- const _volumeClaimTemplates = this.getVolumeClaimTemplates(vm);
314
-
315
- let out = [];
316
-
317
- if (_disks.length === 0) {
318
- out.push({
319
- id: randomStr(5),
320
- source: SOURCE_TYPE.IMAGE,
321
- name: 'disk-0',
322
- accessMode: 'ReadWriteMany',
323
- bus: 'virtio',
324
- volumeName: '',
325
- size: '10Gi',
326
- type: HARD_DISK,
327
- storageClassName: '',
328
- image: this.imageId,
329
- volumeMode: 'Block',
330
- });
331
- } else {
332
- out = _disks.map( (DISK, index) => {
333
- const volume = _volumes.find( V => V.name === DISK.name );
334
-
335
- let size = '';
336
- let image = '';
337
- let source = '';
338
- let realName = '';
339
- let container = '';
340
- let volumeName = '';
341
- let accessMode = '';
342
- let volumeMode = '';
343
- let storageClassName = '';
344
- let hotpluggable = false;
345
-
346
- const type = DISK?.cdrom ? CD_ROM : HARD_DISK;
347
-
348
- if (volume?.containerDisk) { // SOURCE_TYPE.CONTAINER
349
- source = SOURCE_TYPE.CONTAINER;
350
- container = volume.containerDisk.image;
351
- }
352
-
353
- if (volume.persistentVolumeClaim && volume.persistentVolumeClaim?.claimName) {
354
- volumeName = volume.persistentVolumeClaim.claimName;
355
- const DVT = _volumeClaimTemplates.find( T => T.metadata.name === volumeName);
356
-
357
- realName = volumeName;
358
- // If the DVT can be found, it cannot be an existing volume
359
- if (DVT) {
360
- // has annotation (HCI_ANNOTATIONS.IMAGE_ID) => SOURCE_TYPE.IMAGE
361
- if (DVT.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID] !== undefined) {
362
- image = DVT.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID];
363
- source = SOURCE_TYPE.IMAGE;
364
- } else {
365
- source = SOURCE_TYPE.NEW;
366
- }
367
-
368
- const dataVolumeSpecPVC = DVT?.spec || {};
369
-
370
- volumeMode = dataVolumeSpecPVC?.volumeMode;
371
- accessMode = dataVolumeSpecPVC?.accessModes?.[0];
372
- size = dataVolumeSpecPVC?.resources?.requests?.storage || '10Gi';
373
- storageClassName = dataVolumeSpecPVC?.storageClassName;
374
- } else { // SOURCE_TYPE.ATTACH_VOLUME
375
- const allPVCs = this.$store.getters['harvester/all'](PVC);
376
- const pvcResource = allPVCs.find( O => O.id === `${ namespace }/${ volume?.persistentVolumeClaim?.claimName }`);
377
-
378
- source = SOURCE_TYPE.ATTACH_VOLUME;
379
- accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany';
380
- size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
381
- storageClassName = pvcResource?.spec?.storageClassName;
382
- volumeMode = pvcResource?.spec?.volumeMode || 'Block';
383
- volumeName = pvcResource?.metadata?.name || '';
384
- }
385
-
386
- hotpluggable = volume.persistentVolumeClaim.hotpluggable || false;
387
- }
388
-
389
- const bus = DISK?.disk?.bus || DISK?.cdrom?.bus;
390
-
391
- const bootOrder = DISK?.bootOrder ? DISK?.bootOrder : index;
392
-
393
- const parseValue = parseSi(size);
394
-
395
- const formatSize = formatSi(parseValue, {
396
- increment: 1024,
397
- addSuffix: false,
398
- maxExponent: 3,
399
- minExponent: 3,
400
- });
401
-
402
- const allVolumeStatus = JSON.parse(vm.metadata?.annotations?.[HCI_ANNOTATIONS.VM_VOLUME_STATUS] || '[]');
403
- const volumeStatus = allVolumeStatus.find(volume => realName === volume.name);
404
-
405
- return {
406
- id: randomStr(5),
407
- bootOrder,
408
- source,
409
- name: DISK.name,
410
- realName,
411
- bus,
412
- volumeName,
413
- container,
414
- accessMode,
415
- size: `${ formatSize }Gi`,
416
- volumeMode: volumeMode || this.customVolumeMode,
417
- image,
418
- type,
419
- storageClassName,
420
- hotpluggable,
421
- volumeStatus,
422
- };
423
- });
424
- }
425
-
426
- out = sortBy(out, 'bootOrder');
427
-
428
- return out.filter( O => O.name !== 'cloudinitdisk');
429
- },
430
-
431
- getNetworkRows(vm) {
432
- const networks = vm.spec.template.spec.networks || [];
433
- const interfaces = vm.spec.template.spec.domain.devices.interfaces || [];
434
-
435
- const out = interfaces.map( (I, index) => {
436
- const network = networks.find( N => I.name === N.name);
437
-
438
- const type = I.sriov ? 'sriov' : I.bridge ? 'bridge' : 'masquerade';
439
-
440
- const isPod = !!network.pod;
441
-
442
- return {
443
- ...I,
444
- index,
445
- type,
446
- isPod,
447
- model: I.model,
448
- networkName: isPod ? MANAGEMENT_NETWORK : network?.multus?.networkName,
449
- };
450
- });
451
-
452
- return out;
453
- },
454
-
455
- parseVM() {
456
- this.parseOther();
457
- this.parseAccessCredentials();
458
- this.parseNetworkRows(this.networkRows);
459
- this.parseDiskRows(this.diskRows);
460
- },
461
-
462
- parseOther() {
463
- if (!this.spec.template.spec.domain.machine) {
464
- this.$set(this.spec.template.spec.domain, 'machine', { type: this.machineType });
465
- } else {
466
- this.$set(this.spec.template.spec.domain.machine, 'type', this.machineType);
467
- }
468
-
469
- this.spec.template.spec.domain.cpu.cores = this.cpu;
470
- this.spec.template.spec.domain.resources.limits.cpu = this.cpu;
471
- this.spec.template.spec.domain.resources.limits.memory = this.memory;
472
-
473
- // parse reserved memory
474
- const vm = this.resource === HCI.VM ? this.value : this.value.spec.vm;
475
-
476
- if (!this.reservedMemory) {
477
- delete vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY];
478
- } else {
479
- vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY] = this.reservedMemory;
480
- }
481
- },
482
-
483
- parseDiskRows(disk) {
484
- const disks = [];
485
- const volumes = [];
486
- const diskNameLables = [];
487
- const volumeClaimTemplates = [];
488
-
489
- disk.forEach( (R, index) => {
490
- const prefixName = this.value.metadata?.name || '';
491
-
492
- let dataVolumeName = '';
493
-
494
- if (R.source === SOURCE_TYPE.ATTACH_VOLUME) {
495
- dataVolumeName = R.volumeName;
496
- } else if (this.isClone || !this.hasCreateVolumes.includes(R.realName)) {
497
- dataVolumeName = `${ prefixName }-${ R.name }-${ randomStr(5).toLowerCase() }`;
498
- } else {
499
- dataVolumeName = R.realName;
500
- }
501
-
502
- const _disk = this.parseDisk(R, index);
503
- const _volume = this.parseVolume(R, dataVolumeName);
504
- const _dataVolumeTemplate = this.parseVolumeClaimTemplate(R, dataVolumeName);
505
-
506
- disks.push(_disk);
507
- volumes.push(_volume);
508
- diskNameLables.push(dataVolumeName);
509
-
510
- if (R.source !== SOURCE_TYPE.CONTAINER && R.source !== SOURCE_TYPE.ATTACH_VOLUME) {
511
- volumeClaimTemplates.push(_dataVolumeTemplate);
512
- }
513
- });
514
-
515
- if (!this.secretName || this.needNewSecret) {
516
- this.secretName = this.generateSecretName(this.secretNamePrefix);
517
- }
518
-
519
- if (!disks.find( D => D.name === 'cloudinitdisk')) {
520
- if (!this.isWindows) {
521
- disks.push({
522
- name: 'cloudinitdisk',
523
- disk: { bus: 'virtio' }
524
- });
525
-
526
- volumes.push({
527
- name: 'cloudinitdisk',
528
- cloudInitNoCloud: {
529
- secretRef: { name: this.secretName },
530
- networkDataSecretRef: { name: this.secretName }
531
- }
532
- });
533
- }
534
- }
535
-
536
- let spec = {
537
- ...this.spec,
538
- runStrategy: this.runStrategy,
539
- template: {
540
- ...this.spec.template,
541
- metadata: {
542
- ...this.spec?.template?.metadata,
543
- annotations: {
544
- ...this.spec?.template?.metadata?.annotations,
545
- [HCI_ANNOTATIONS.SSH_NAMES]: JSON.stringify(this.sshKey)
546
- },
547
- labels: {
548
- ...this.spec?.template?.metadata?.labels,
549
- [HCI_ANNOTATIONS.VM_NAME]: this.value?.metadata?.name,
550
- }
551
- },
552
- spec: {
553
- ...this.spec.template?.spec,
554
- domain: {
555
- ...this.spec.template?.spec?.domain,
556
- devices: {
557
- ...this.spec.template?.spec?.domain?.devices,
558
- disks,
559
- },
560
- },
561
- volumes,
562
- }
563
- }
564
- };
565
-
566
- if (volumes.length === 0) {
567
- delete spec.template.spec.volumes;
568
- }
569
-
570
- if (this.resource === HCI.VM) {
571
- if (!this.isSingle) {
572
- spec = this.multiVMScheduler(spec);
573
- }
574
-
575
- this.$set(this.value.metadata, 'annotations', {
576
- ...this.value.metadata.annotations,
577
- [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates),
578
- [HCI_ANNOTATIONS.NETWORK_IPS]: JSON.stringify(this.value.networkIps)
579
- });
580
-
581
- this.$set(this.value.metadata, 'labels', {
582
- ...this.value.metadata.labels,
583
- [HCI_ANNOTATIONS.CREATOR]: 'harvester',
584
- [HCI_ANNOTATIONS.OS]: this.osType
585
- });
586
-
587
- this.$set(this.value, 'spec', spec);
588
- this.$set(this, 'spec', spec);
589
- } else if (this.resource === HCI.VM_VERSION) {
590
- this.$set(this.value.spec.vm, 'spec', spec);
591
- this.$set(this.value.spec.vm.metadata, 'annotations', { ...this.value.spec.vm.metadata.annotations, [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates) });
592
- this.$set(this.value.spec.vm.metadata, 'labels', { [HCI_ANNOTATIONS.OS]: this.osType });
593
- this.$set(this, 'spec', spec);
594
- }
595
- },
596
-
597
- multiVMScheduler(spec) {
598
- spec.template.metadata.labels[HCI_ANNOTATIONS.VM_NAME_PREFIX] = this.namePrefix;
599
-
600
- const rule = {
601
- weight: 1,
602
- podAffinityTerm: {
603
- topologyKey: HOSTNAME,
604
- labelSelector: { matchLabels: { [HCI_ANNOTATIONS.VM_NAME_PREFIX]: this.namePrefix } }
605
- }
606
- };
607
-
608
- return {
609
- ...spec,
610
- template: {
611
- ...spec.template,
612
- spec: {
613
- ...spec.template.spec,
614
- affinity: {
615
- ...spec.template.spec.affinity,
616
- podAntiAffinity: {
617
- ...spec.template.spec?.affinity?.podAntiAffinity,
618
- preferredDuringSchedulingIgnoredDuringExecution: [
619
- ...(spec.template.spec?.affinity?.podAntiAffinity?.preferredDuringSchedulingIgnoredDuringExecution || []),
620
- rule
621
- ]
622
- }
623
- }
624
- }
625
- }
626
- };
627
- },
628
-
629
- parseNetworkRows(networkRow) {
630
- const networks = [];
631
- const interfaces = [];
632
-
633
- networkRow.forEach( (R) => {
634
- const _network = this.parseNetwork(R);
635
- const _interface = this.parseInterface(R);
636
-
637
- networks.push(_network);
638
- interfaces.push(_interface);
639
- });
640
-
641
- const spec = {
642
- ...this.spec.template.spec,
643
- domain: {
644
- ...this.spec.template.spec.domain,
645
- devices: {
646
- ...this.spec.template.spec.domain.devices,
647
- interfaces,
648
- },
649
- },
650
- networks
651
- };
652
-
653
- this.$set(this.spec.template, 'spec', spec);
654
- },
655
-
656
- parseAccessCredentials() {
657
- const out = [];
658
- const annotations = {};
659
- const users = JSON.parse(this.spec?.template?.metadata?.annotations?.[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_USERS] || '[]');
660
-
661
- for (const row of this.accessCredentials) {
662
- if (this.needNewSecret) {
663
- row.secretName = this.generateSecretName(this.secretNamePrefix);
664
- }
665
-
666
- if (row.source === ACCESS_CREDENTIALS.RESET_PWD) {
667
- users.push(row.username);
668
- out.push({
669
- userPassword: {
670
- source: { secret: { secretName: row.secretName } },
671
- propagationMethod: { qemuGuestAgent: { } }
672
- }
673
- });
674
- }
675
-
676
- if (row.source === ACCESS_CREDENTIALS.INJECT_SSH) {
677
- users.push(...row.users);
678
- annotations[row.secretName] = row.sshkeys;
679
- out.push({
680
- sshPublicKey: {
681
- source: { secret: { secretName: row.secretName } },
682
- propagationMethod: { qemuGuestAgent: { users: row.users } }
683
- }
684
- });
685
- }
686
- }
687
-
688
- if (out.length === 0 && !!this.spec.template.spec.accessCredentials) {
689
- delete this.spec.template.spec.accessCredentials;
690
- } else {
691
- this.spec.template.spec.accessCredentials = out;
692
- }
693
-
694
- if (users.length !== 0) {
695
- this.spec.template.metadata.annotations[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_USERS] = JSON.stringify(Array.from(new Set(users)));
696
- this.spec.template.metadata.annotations[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_NAMES] = JSON.stringify(annotations);
697
- }
698
- },
699
-
700
- getInitUserData(config) {
701
- const _QGA_JSON = this.getMatchQGA(config.osType);
702
-
703
- const out = jsyaml.dump(_QGA_JSON);
704
-
705
- return `#cloud-config\n${ out }`;
706
- },
707
-
708
- /**
709
- * Generate user data yaml which is decide by the "Install guest agent",
710
- * "OS type", "SSH Keys" and user input.
711
- * @param config
712
- */
713
- getUserData(config) {
714
- try {
715
- // https://github.com/eemeli/yaml/issues/136
716
- let userDataDoc = this.userScript ? YAML.parseDocument(this.userScript) : YAML.parseDocument({});
717
-
718
- const allSSHAuthorizedKeys = this.mergeSSHAuthorizedKeys(this.userScript);
719
-
720
- if (allSSHAuthorizedKeys.length > 0) {
721
- userDataDoc.setIn(['ssh_authorized_keys'], allSSHAuthorizedKeys);
722
- } else if (YAML.isCollection(userDataDoc.getIn('ssh_authorized_keys'))) {
723
- userDataDoc.deleteIn(['ssh_authorized_keys']);
724
- }
725
-
726
- userDataDoc = config.installAgent ? this.mergeQGA({ userDataDoc, ...config }) : this.deleteQGA({ userDataDoc, ...config });
727
- const userDataYaml = userDataDoc.toString();
728
-
729
- if (userDataYaml === '{}\n') {
730
- // When the YAML parsed value is '{}\n', it means that the userData is empty, then undefined is returned.
731
- return undefined;
732
- }
733
-
734
- return userDataYaml;
735
- } catch (e) {
736
- console.error('Error: Unable to parse yaml document', e); // eslint-disable-line no-console
737
-
738
- return this.userScript;
739
- }
740
- },
741
-
742
- updateSSHKey(neu) {
743
- this.$set(this, 'sshKey', neu);
744
- },
745
-
746
- updateCpuMemory(cpu, memory) {
747
- this.$set(this, 'cpu', cpu);
748
- this.$set(this, 'memory', memory);
749
- },
750
-
751
- parseDisk(R, index) {
752
- const out = { name: R.name };
753
-
754
- if (R.type === HARD_DISK) {
755
- out.disk = { bus: R.bus };
756
- } else if (R.type === CD_ROM) {
757
- out.cdrom = { bus: R.bus };
758
- }
759
-
760
- out.bootOrder = index + 1;
761
-
762
- return out;
763
- },
764
-
765
- parseVolume(R, dataVolumeName) {
766
- const out = { name: R.name };
767
-
768
- if (R.source === SOURCE_TYPE.CONTAINER) {
769
- out.containerDisk = { image: R.container };
770
- } else if (R.source === SOURCE_TYPE.IMAGE || R.source === SOURCE_TYPE.NEW || R.source === SOURCE_TYPE.ATTACH_VOLUME) {
771
- out.persistentVolumeClaim = { claimName: dataVolumeName };
772
- if (R.hotpluggable) {
773
- out.persistentVolumeClaim.hotpluggable = true;
774
- }
775
- }
776
-
777
- return out;
778
- },
779
-
780
- parseVolumeClaimTemplate(R, dataVolumeName) {
781
- if (!String(R.size).includes('Gi') && R.size) {
782
- R.size = `${ R.size }Gi`;
783
- }
784
-
785
- const out = {
786
- metadata: { name: dataVolumeName },
787
- spec: {
788
- accessModes: [R.accessMode],
789
- resources: { requests: { storage: R.size } },
790
- volumeMode: R.volumeMode
791
- }
792
- };
793
-
794
- switch (R.source) {
795
- case SOURCE_TYPE.NEW:
796
- out.spec.storageClassName = R.storageClassName || this.customDefaultStorageClass || this.defaultStorageClass;
797
- break;
798
- case SOURCE_TYPE.IMAGE: {
799
- const image = this.images.find( I => R.image === I.id);
800
-
801
- if (image) {
802
- out.spec.storageClassName = `longhorn-${ image.metadata.name }`;
803
- out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: image.id };
804
- } else if (this.resource === HCI.VM_VERSION) {
805
- out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: '' };
806
- }
807
-
808
- break;
809
- }
810
- }
811
-
812
- return out;
813
- },
814
-
815
- getSSHListValue(arr) {
816
- return arr.map( id => this.getSSHValue(id)).filter( O => O !== undefined);
817
- },
818
-
819
- parseInterface(R) {
820
- const _interface = {};
821
- const type = R.type;
822
-
823
- _interface[type] = {};
824
-
825
- if (R.macAddress) {
826
- _interface.macAddress = R.macAddress;
827
- }
828
-
829
- _interface.model = R.model;
830
- _interface.name = R.name;
831
-
832
- return _interface;
833
- },
834
-
835
- parseNetwork(R) {
836
- const out = { name: R.name };
837
-
838
- if (R.isPod) {
839
- out.pod = {};
840
- } else {
841
- out.multus = { networkName: R.networkName };
842
- }
843
-
844
- return out;
845
- },
846
-
847
- updateUserData(value) {
848
- this.userScript = value;
849
- },
850
-
851
- updateNetworkData(value) {
852
- this.networkScript = value;
853
- },
854
-
855
- mergeSSHAuthorizedKeys(yaml) {
856
- try {
857
- const sshAuthorizedKeys = YAML.parseDocument(yaml)
858
- .get('ssh_authorized_keys')
859
- ?.toJSON() || [];
860
-
861
- const sshList = this.getSSHListValue(this.sshKey);
862
-
863
- return sshAuthorizedKeys.length ? [...new Set([...sshList, ...sshAuthorizedKeys])] : sshList;
864
- } catch (e) {
865
- return [];
866
- }
867
- },
868
-
869
- /**
870
- * @param paths A Object path, e.g. 'a.b.c' => ['a', 'b', 'c']. Refer to https://eemeli.org/yaml/#scalar-values
871
- * @returns
872
- */
873
- deleteYamlDocProp(doc, paths) {
874
- try {
875
- const item = doc.getIn([])?.items[0];
876
- const key = item?.key;
877
- const hasCloudConfigComment = !!key?.commentBefore?.includes('cloud-config');
878
- const isMatchProp = key.source === paths[paths.length - 1];
879
-
880
- if (key && hasCloudConfigComment && isMatchProp) {
881
- // Comments are mounted on the next node and we should not delete the node containing cloud-config
882
- } else {
883
- doc.deleteIn(paths);
884
- }
885
- } catch (e) {}
886
- },
887
-
888
- mergeQGA(config) {
889
- const { osType, userDataDoc } = config;
890
- const _QGA_JSON = this.getMatchQGA(osType);
891
- const userDataYAML = userDataDoc.toString();
892
- const userDataJSON = YAML.parse(userDataYAML);
893
- let packages = userDataJSON?.packages || [];
894
- let runcmd = userDataJSON?.runcmd || [];
895
-
896
- userDataDoc.setIn(['package_update'], true);
897
-
898
- if (Array.isArray(packages)) {
899
- if (!packages.includes('qemu-guest-agent')) {
900
- packages.push('qemu-guest-agent');
901
- }
902
- } else {
903
- packages = QGA_JSON.packages;
904
- }
905
-
906
- if (Array.isArray(runcmd)) {
907
- let findIndex = -1;
908
- const hasSameRuncmd = runcmd.find( S => Array.isArray(S) && S.join('-') === _QGA_JSON.runcmd[0].join('-'));
909
-
910
- const hasSimilarRuncmd = runcmd.find( (S, index) => {
911
- if (Array.isArray(S) && S.join('-') === this.getSimilarRuncmd(osType).join('-')) {
912
- findIndex = index;
913
-
914
- return true;
915
- }
916
-
917
- return false;
918
- });
919
-
920
- if (hasSimilarRuncmd) {
921
- runcmd[findIndex] = _QGA_JSON.runcmd[0];
922
- } else if (!hasSameRuncmd) {
923
- runcmd.push(_QGA_JSON.runcmd[0]);
924
- }
925
- } else {
926
- runcmd = _QGA_JSON.runcmd;
927
- }
928
-
929
- if (packages.length > 0) {
930
- userDataDoc.setIn(['packages'], packages);
931
- } else {
932
- userDataDoc.setIn(['packages'], []); // It needs to be set empty first, as it is possible that cloud-init comments are mounted on this node
933
- this.deleteYamlDocProp(userDataDoc, ['packages']);
934
- this.deleteYamlDocProp(userDataDoc, ['package_update']);
935
- }
936
-
937
- if (runcmd.length > 0) {
938
- userDataDoc.setIn(['runcmd'], runcmd);
939
- } else {
940
- this.deleteYamlDocProp(userDataDoc, ['runcmd']);
941
- }
942
-
943
- return userDataDoc;
944
- },
945
-
946
- deleteQGA(config) {
947
- const { osType, userDataDoc, deletePackage = false } = config;
948
-
949
- const userDataTemplateValue = this.$store.getters['harvester/byId'](CONFIG_MAP, this.userDataTemplateId)?.data?.cloudInit || '';
950
-
951
- const userDataYAML = userDataDoc.toString();
952
- const userDataJSON = YAML.parse(userDataYAML);
953
- const packages = userDataJSON?.packages || [];
954
- const runcmd = userDataJSON?.runcmd || [];
955
-
956
- if (Array.isArray(packages) && deletePackage) {
957
- const templateHasQGAPackage = this.convertToJson(userDataTemplateValue);
958
-
959
- for (let i = 0; i < packages.length; i++) {
960
- if (packages[i] === 'qemu-guest-agent') {
961
- if (!(Array.isArray(templateHasQGAPackage?.packages) && templateHasQGAPackage.packages.includes('qemu-guest-agent'))) {
962
- packages.splice(i, 1);
963
- }
964
- }
965
- }
966
- }
967
-
968
- if (Array.isArray(runcmd)) {
969
- const _QGA_JSON = this.getMatchQGA(osType);
970
-
971
- for (let i = 0; i < runcmd.length; i++) {
972
- if (Array.isArray(runcmd[i]) && runcmd[i].join('-') === _QGA_JSON.runcmd[0].join('-')) {
973
- runcmd.splice(i, 1);
974
- }
975
- }
976
- }
977
-
978
- if (packages.length > 0) {
979
- userDataDoc.setIn(['packages'], packages);
980
- } else {
981
- userDataDoc.setIn(['packages'], []);
982
- this.deleteYamlDocProp(userDataDoc, ['packages']);
983
- this.deleteYamlDocProp(userDataDoc, ['package_update']);
984
- }
985
-
986
- if (runcmd.length > 0) {
987
- userDataDoc.setIn(['runcmd'], runcmd);
988
- } else {
989
- this.deleteYamlDocProp(userDataDoc, ['runcmd']);
990
- }
991
-
992
- return userDataDoc;
993
- },
994
-
995
- generateSecretName(name) {
996
- return name ? `${ name }-${ randomStr(5).toLowerCase() }` : undefined;
997
- },
998
-
999
- getOwnerReferencesFromVM(resource) {
1000
- const name = resource.metadata.name;
1001
- const kind = resource.kind;
1002
- const apiVersion = this.resource === HCI.VM ? 'kubevirt.io/v1' : 'harvesterhci.io/v1beta1';
1003
- const uid = resource?.metadata?.uid;
1004
-
1005
- return [{
1006
- name,
1007
- kind,
1008
- uid,
1009
- apiVersion,
1010
- }];
1011
- },
1012
-
1013
- async saveSecret(vm) {
1014
- if (!vm?.spec || !this.secretName || this.isWindows) {
1015
- return true;
1016
- }
1017
-
1018
- let secret = this.getSecret(vm.spec);
1019
-
1020
- const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
1021
-
1022
- if (!secret || this.needNewSecret) {
1023
- secret = await this.$store.dispatch('harvester/create', {
1024
- metadata: {
1025
- name: this.secretName,
1026
- namespace: this.value.metadata.namespace,
1027
- labels: { [HCI_ANNOTATIONS.CLOUD_INIT]: 'harvester' },
1028
- ownerReferences: this.getOwnerReferencesFromVM(vm)
1029
- },
1030
- type: SECRET
1031
- });
1032
- }
1033
-
1034
- try {
1035
- if (secret) {
1036
- secret.setData('userdata', userData);
1037
- secret.setData('networkdata', this.networkScript);
1038
-
1039
- await secret.save();
1040
- }
1041
- } catch (e) {
1042
- return Promise.reject(e);
1043
- }
1044
- },
1045
-
1046
- async saveAccessCredentials(vm) {
1047
- if (!vm?.spec) {
1048
- return true;
1049
- }
1050
-
1051
- // save
1052
- const toSave = [];
1053
-
1054
- for (const row of this.accessCredentials) {
1055
- let secretRef = row.secretRef;
1056
-
1057
- if (!secretRef || this.needNewSecret) {
1058
- secretRef = await this.$store.dispatch('harvester/create', {
1059
- metadata: {
1060
- name: row.secretName,
1061
- namespace: vm.metadata.namespace,
1062
- labels: { [HCI_ANNOTATIONS.CLOUD_INIT]: 'harvester' },
1063
- ownerReferences: this.getOwnerReferencesFromVM(vm)
1064
- },
1065
- type: SECRET
1066
- });
1067
- }
1068
-
1069
- if (row.source === ACCESS_CREDENTIALS.RESET_PWD) {
1070
- secretRef.setData(row.username, row.newPassword);
1071
- }
1072
-
1073
- if (row.source === ACCESS_CREDENTIALS.INJECT_SSH) {
1074
- for (const secretId of row.sshkeys) {
1075
- const keypair = (this.$store.getters['harvester/all'](HCI.SSH) || []).find(s => s.id === secretId);
1076
-
1077
- secretRef.setData(`${ keypair.metadata.namespace }-${ keypair.metadata.name }`, keypair.spec.publicKey);
1078
- }
1079
- }
1080
-
1081
- toSave.push(secretRef);
1082
- }
1083
-
1084
- try {
1085
- for (const resource of toSave) {
1086
- await resource.save();
1087
- }
1088
- } catch (e) {
1089
- return Promise.reject(e);
1090
- }
1091
- },
1092
-
1093
- getAccessCredentialsValidation() {
1094
- const errors = [];
1095
-
1096
- for (let i = 0; i < this.accessCredentials.length; i++) {
1097
- const row = this.accessCredentials[i];
1098
- const source = row.source;
1099
-
1100
- if (source === ACCESS_CREDENTIALS.RESET_PWD) {
1101
- if (!row.username) {
1102
- const fieldName = this.t('harvester.virtualMachine.input.username');
1103
- const message = this.t('validation.required', { key: fieldName });
1104
-
1105
- errors.push(message);
1106
- }
1107
-
1108
- if (!row.newPassword) {
1109
- const fieldName = this.t('harvester.virtualMachine.input.password');
1110
- const message = this.t('validation.required', { key: fieldName });
1111
-
1112
- errors.push(message);
1113
- }
1114
-
1115
- if (row.newPassword && row.newPassword.length < 6) {
1116
- const fieldName = this.t('harvester.virtualMachine.input.password');
1117
- const message = this.t('validation.number.min', { key: fieldName, val: '6' });
1118
-
1119
- errors.push(message);
1120
- }
1121
- } else {
1122
- if (!row.users || row.users.length === 0) {
1123
- const fieldName = this.t('harvester.virtualMachine.input.username');
1124
- const message = this.t('validation.required', { key: fieldName });
1125
-
1126
- errors.push(message);
1127
- }
1128
-
1129
- if (!row.sshkeys || row.sshkeys.length === 0) {
1130
- const fieldName = this.t('harvester.virtualMachine.input.sshKeyValue');
1131
- const message = this.t('validation.required', { key: fieldName });
1132
-
1133
- errors.push(message);
1134
- }
1135
- }
1136
-
1137
- if (errors.length > 0) {
1138
- break;
1139
- }
1140
- }
1141
-
1142
- return errors;
1143
- },
1144
-
1145
- getHasCreatedVolumes(spec) {
1146
- const out = [];
1147
-
1148
- if (spec.template.spec.volumes) {
1149
- spec.template.spec.volumes.forEach((V) => {
1150
- if (V?.persistentVolumeClaim?.claimName) {
1151
- out.push(V.persistentVolumeClaim.claimName);
1152
- }
1153
- });
1154
- }
1155
-
1156
- return out;
1157
- },
1158
-
1159
- handlerUSBTablet(val) {
1160
- const hasExist = this.isInstallUSBTablet(this.spec);
1161
- const inputs = this.spec.template.spec.domain.devices?.inputs || [];
1162
-
1163
- if (val && !hasExist) {
1164
- if (inputs.length > 0) {
1165
- inputs.push(USB_TABLET[0]);
1166
- } else {
1167
- Object.assign(this.spec.template.spec.domain.devices, {
1168
- inputs: [
1169
- USB_TABLET[0]
1170
- ]
1171
- });
1172
- }
1173
- } else if (!val) {
1174
- const index = inputs.findIndex(O => isEqual(O, USB_TABLET[0]));
1175
-
1176
- if (hasExist && inputs.length === 1) {
1177
- this.$delete(this.spec.template.spec.domain.devices, 'inputs');
1178
- } else if (hasExist) {
1179
- inputs.splice(index, 1);
1180
- this.$set(this.spec.template.spec.domain.devices, 'inputs', inputs);
1181
- }
1182
- }
1183
- },
1184
-
1185
- setBootMethod(boot = { efi: false, secureBoot: false }) {
1186
- if (boot.efi && boot.secureBoot) {
1187
- set(this.spec.template.spec.domain, 'features.smm.enabled', true);
1188
- set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', true);
1189
- } else if (boot.efi && !boot.secureBoot) {
1190
- set(this.spec.template.spec.domain, 'features.smm.enabled', false);
1191
- set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', false);
1192
- } else {
1193
- this.$delete(this.spec.template.spec.domain, 'firmware');
1194
- this.$delete(this.spec.template.spec.domain.features, 'smm');
1195
- }
1196
- },
1197
-
1198
- deleteSSHFromUserData(ssh = []) {
1199
- const sshAuthorizedKeys = this.getSSHFromUserData(this.userScript);
1200
-
1201
- ssh.map((id) => {
1202
- const index = sshAuthorizedKeys.findIndex(value => value === this.getSSHValue(id));
1203
-
1204
- if (index >= 0) {
1205
- sshAuthorizedKeys.splice(index, 1);
1206
- }
1207
- });
1208
-
1209
- const userDataJson = this.convertToJson(this.userScript);
1210
-
1211
- userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
1212
-
1213
- if (sshAuthorizedKeys.length === 0) {
1214
- delete userDataJson.ssh_authorized_keys;
1215
- }
1216
-
1217
- if (isEmpty(userDataJson)) {
1218
- this.$set(this, 'userScript', undefined);
1219
- } else {
1220
- this.$set(this, 'userScript', jsyaml.dump(userDataJson));
1221
- }
1222
-
1223
- this.refreshYamlEditor();
1224
- },
1225
-
1226
- refreshYamlEditor() {
1227
- this.$nextTick(() => {
1228
- this.$refs.yamlEditor?.updateValue();
1229
- });
1230
- },
1231
-
1232
- toggleAdvanced() {
1233
- this.showAdvanced = !this.showAdvanced;
1234
- },
1235
-
1236
- updateAgent(value) {
1237
- if (!value) {
1238
- this.deletePackage = true;
1239
- }
1240
- },
1241
-
1242
- updateDataTemplateId(type, id) {
1243
- if (type === 'user') {
1244
- const oldInstallAgent = this.installAgent;
1245
-
1246
- this.userDataTemplateId = id;
1247
- this.$nextTick(() => {
1248
- if (oldInstallAgent) {
1249
- this.installAgent = oldInstallAgent;
1250
- }
1251
- });
1252
- }
1253
- },
1254
-
1255
- updateReserved(value = {}) {
1256
- const { memory } = value;
1257
-
1258
- this.$set(this, 'reservedMemory', memory);
1259
- },
1260
- },
1261
-
1262
- watch: {
1263
- diskRows: {
1264
- handler(neu, old) {
1265
- if (Array.isArray(neu)) {
1266
- const imageId = neu[0]?.image;
1267
- const image = this.images.find( I => imageId === I.id);
1268
- const osType = image?.imageOSType;
1269
-
1270
- const oldImageId = old[0]?.image;
1271
-
1272
- if (this.isCreate && oldImageId === imageId && imageId) {
1273
- this.osType = osType;
1274
- }
1275
- }
1276
- }
1277
- },
1278
-
1279
- secretRef: {
1280
- handler(secret) {
1281
- if (secret && this.resource !== HCI.BACKUP) {
1282
- this.userScript = secret?.decodedData?.userdata;
1283
- this.networkScript = secret?.decodedData?.networkdata;
1284
- this.secretName = secret?.metadata.name;
1285
- this.refreshYamlEditor();
1286
- }
1287
- },
1288
- immediate: true,
1289
- deep: true
1290
- },
1291
-
1292
- isWindows(val) {
1293
- if (val) {
1294
- this.$set(this, 'sshKey', []);
1295
- this.$set(this, 'userScript', undefined);
1296
- this.$set(this, 'installAgent', false);
1297
- }
1298
- },
1299
-
1300
- installUSBTablet(val) {
1301
- this.handlerUSBTablet(val);
1302
- },
1303
-
1304
- efiEnabled(val) {
1305
- this.setBootMethod({ efi: val, secureBoot: this.secureBoot });
1306
- },
1307
-
1308
- secureBoot(val) {
1309
- this.setBootMethod({ efi: this.efiEnabled, secureBoot: val });
1310
- },
1311
-
1312
- installAgent: {
1313
- /**
1314
- * rules
1315
- * 1. The value in user Data is the first priority
1316
- * 2. After selecting the template, if checkbox is checked, only merge operation will be performed on user data,
1317
- * if checkbox is unchecked, no value will be deleted in user data
1318
- */
1319
- handler(neu) {
1320
- if (this.deleteAgent) {
1321
- const out = this.getUserData({
1322
- installAgent: neu, osType: this.osType, deletePackage: this.deletePackage
1323
- });
1324
-
1325
- this.$set(this, 'userScript', out);
1326
- this.refreshYamlEditor();
1327
- }
1328
- this.deleteAgent = true;
1329
- this.deletePackage = false;
1330
- }
1331
- },
1332
-
1333
- osType(neu) {
1334
- const out = this.getUserData({ installAgent: this.installAgent, osType: neu });
1335
-
1336
- this.$set(this, 'userScript', out);
1337
- this.refreshYamlEditor();
1338
- },
1339
-
1340
- userScript(neu, old) {
1341
- const hasInstallAgent = this.hasInstallAgent(neu, this.osType, this.installAgent);
1342
-
1343
- if (hasInstallAgent !== this.installAgent) {
1344
- this.deleteAgent = false;
1345
- this.installAgent = hasInstallAgent;
1346
- }
1347
- },
1348
-
1349
- sshKey(neu, old) {
1350
- const _diff = difference(old, neu);
1351
-
1352
- if (_diff.length && this.isEdit) {
1353
- this.deleteSSHFromUserData(_diff);
1354
- }
1355
- }
1356
- }
1357
- };