-
-
-
-
-
-
-
expect(typeof fn === 'function').toBeTruthy();
describe('
', () => {
it('checks handlers', () => {
- checkFuntion(valueHandler(jest.fn()));
checkFuntion(checkboxHandler(jest.fn()));
checkFuntion(eventValueHandler(jest.fn()));
diff --git a/src/components/Form/utils.js b/src/components/Form/utils.js
index 0e5df3f1a..a2e2b01b2 100644
--- a/src/components/Form/utils.js
+++ b/src/components/Form/utils.js
@@ -1,5 +1,3 @@
-export const valueHandler = callback => (typeof callback === 'function' ? value => callback(value) : null);
-
export const eventValueHandler = (callback, defaultValue = '') =>
typeof callback === 'function' ? event => callback(event.target.value || defaultValue) : null;
diff --git a/src/components/InlineEdit/InlineEdit.js b/src/components/InlineEdit/InlineEdit.js
new file mode 100644
index 000000000..86a2a22ec
--- /dev/null
+++ b/src/components/InlineEdit/InlineEdit.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { InlineFormFactory } from '../Form/FormFactory';
+import { Loading } from '../Loading/Loading';
+
+export const InlineEdit = ({
+ updating,
+ editing,
+ formFields,
+ LoadingComponent,
+ children,
+ onFormChange,
+ fieldsValues,
+}) => {
+ if (updating) {
+ return
;
+ }
+ if (editing) {
+ return
;
+ }
+ return
{children}
;
+};
+
+InlineEdit.propTypes = {
+ updating: PropTypes.bool,
+ editing: PropTypes.bool,
+ formFields: PropTypes.object,
+ LoadingComponent: PropTypes.func,
+ children: PropTypes.node.isRequired,
+ onFormChange: PropTypes.func,
+ fieldsValues: PropTypes.object,
+};
+
+InlineEdit.defaultProps = {
+ updating: false,
+ editing: false,
+ formFields: {},
+ LoadingComponent: Loading,
+ onFormChange: () => {},
+ fieldsValues: {},
+};
diff --git a/src/components/InlineEdit/fixtures/InlineEdit.fixture.js b/src/components/InlineEdit/fixtures/InlineEdit.fixture.js
new file mode 100644
index 000000000..78e52cc1d
--- /dev/null
+++ b/src/components/InlineEdit/fixtures/InlineEdit.fixture.js
@@ -0,0 +1,8 @@
+import { InlineEdit } from '..';
+
+export default {
+ component: InlineEdit,
+ props: {
+ children: 'inline edit',
+ },
+};
diff --git a/src/components/InlineEdit/index.js b/src/components/InlineEdit/index.js
new file mode 100644
index 000000000..f16a156e7
--- /dev/null
+++ b/src/components/InlineEdit/index.js
@@ -0,0 +1 @@
+export * from './InlineEdit';
diff --git a/src/components/InlineEdit/tests/InlineEdit.test.js b/src/components/InlineEdit/tests/InlineEdit.test.js
new file mode 100644
index 000000000..7bad1e04d
--- /dev/null
+++ b/src/components/InlineEdit/tests/InlineEdit.test.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { InlineEdit } from '..';
+
+import { default as InlineEditFixture } from '../fixtures/InlineEdit.fixture';
+
+const testInlineEdit = () =>
;
+
+describe('
', () => {
+ it('renders correctly', () => {
+ const component = shallow(testInlineEdit());
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/components/InlineEdit/tests/__snapshots__/InlineEdit.test.js.snap b/src/components/InlineEdit/tests/__snapshots__/InlineEdit.test.js.snap
new file mode 100644
index 000000000..2a05bdd24
--- /dev/null
+++ b/src/components/InlineEdit/tests/__snapshots__/InlineEdit.test.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`
renders correctly 1`] = `
+
+ inline edit
+
+`;
diff --git a/src/components/Loading/Loading.js b/src/components/Loading/Loading.js
index 4dae02c44..58efb846f 100644
--- a/src/components/Loading/Loading.js
+++ b/src/components/Loading/Loading.js
@@ -9,5 +9,9 @@ export const Loading = ({ text }) => (
);
Loading.propTypes = {
- text: PropTypes.string.isRequired,
+ text: PropTypes.string,
+};
+
+Loading.defaultProps = {
+ text: 'Loading',
};
diff --git a/src/constants/index.js b/src/constants/index.js
index aa489e0e1..758c95f86 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -64,3 +64,4 @@ export const VM_STATUS_TO_TEXT = {
};
export const DEFAULT_RDP_PORT = 3389;
+export const DASHES = '---';
diff --git a/src/k8s/mock_pod/cloudInitTestPod.pod.js b/src/k8s/mock_pod/cloudInitTestPod.mock.js
similarity index 100%
rename from src/k8s/mock_pod/cloudInitTestPod.pod.js
rename to src/k8s/mock_pod/cloudInitTestPod.mock.js
diff --git a/src/k8s/mock_vm/cloudInitTestVm.vm.js b/src/k8s/mock_vm/cloudInitTestVm.mock.js
similarity index 100%
rename from src/k8s/mock_vm/cloudInitTestVm.vm.js
rename to src/k8s/mock_vm/cloudInitTestVm.mock.js
diff --git a/src/k8s/mock_vmi/blue.vmi.js b/src/k8s/mock_vmi/blue.mock.js
similarity index 100%
rename from src/k8s/mock_vmi/blue.vmi.js
rename to src/k8s/mock_vmi/blue.mock.js
diff --git a/src/k8s/mock_vmi/cloudInitTestVmi.vmi.js b/src/k8s/mock_vmi/cloudInitTestVmi.mock.js
similarity index 100%
rename from src/k8s/mock_vmi/cloudInitTestVmi.vmi.js
rename to src/k8s/mock_vmi/cloudInitTestVmi.mock.js
diff --git a/src/k8s/tests/migrate.test.js b/src/k8s/tests/migrate.test.js
index 028d7e8cf..bb217e1f3 100644
--- a/src/k8s/tests/migrate.test.js
+++ b/src/k8s/tests/migrate.test.js
@@ -1,6 +1,6 @@
import { migrate } from '../migrate';
import { k8sCreate } from './request.test';
-import { blueVmi } from '../mock_vmi/blue.vmi';
+import { blueVmi } from '../mock_vmi/blue.mock';
import { VirtualMachineInstanceMigrationModel } from '../../models';
import { getModelApi } from '../selectors';
diff --git a/src/utils/selectors.js b/src/utils/selectors.js
index c99bebe37..774c5588e 100644
--- a/src/utils/selectors.js
+++ b/src/utils/selectors.js
@@ -34,7 +34,17 @@ export const getCpu = vm => get(vm, 'spec.template.spec.domain.cpu.cores');
export const getOperatingSystem = vm => getLabelValue(vm, TEMPLATE_OS_LABEL);
export const getWorkloadProfile = vm => getLabelValue(vm, TEMPLATE_WORKLOAD_LABEL);
export const getFlavor = vm => getLabelValue(vm, TEMPLATE_FLAVOR_LABEL);
-export const getVmTemplate = vm => getLabelValue(vm, ANNOTATION_USED_TEMPLATE);
+export const getVmTemplate = vm => {
+ const vmTemplate = getLabelValue(vm, ANNOTATION_USED_TEMPLATE);
+ if (vmTemplate) {
+ const templateParts = vmTemplate.split('_');
+ return {
+ name: templateParts[1],
+ namespace: templateParts[0],
+ };
+ }
+ return null;
+};
export const getDescription = vm => get(vm, 'metadata.annotations.description');
export const getCloudInitData = vm => {
const volumes = getVolumes(vm);
diff --git a/src/utils/tests/selectors.test.js b/src/utils/tests/selectors.test.js
index 732a574a3..53a3f5d4f 100644
--- a/src/utils/tests/selectors.test.js
+++ b/src/utils/tests/selectors.test.js
@@ -1,5 +1,5 @@
import { getVmiIpAddresses } from '../selectors';
-import { cloudInitTestVmi } from '../../k8s/mock_vmi/cloudInitTestVmi.vmi';
+import { cloudInitTestVmi } from '../../k8s/mock_vmi/cloudInitTestVmi.mock';
describe('getVmiIpAddresses()', () => {
it('returns multiple IP addresses correctly', () => {
diff --git a/src/utils/tests/utils.test.js b/src/utils/tests/utils.test.js
index b5189d32a..07bab9e09 100644
--- a/src/utils/tests/utils.test.js
+++ b/src/utils/tests/utils.test.js
@@ -1,5 +1,14 @@
-import { ANNOTATION_FIRST_BOOT, BOOT_ORDER_SECOND, BOOT_ORDER_FIRST, PVC_ACCESSMODE_RWO } from '../../constants';
-import { getPxeBootPatch, getAddDiskPatch } from '../utils';
+import { cloneDeep } from 'lodash';
+
+import {
+ ANNOTATION_FIRST_BOOT,
+ BOOT_ORDER_SECOND,
+ BOOT_ORDER_FIRST,
+ PVC_ACCESSMODE_RWO,
+ TEMPLATE_FLAVOR_LABEL,
+} from '../../constants';
+import { getPxeBootPatch, getAddDiskPatch, getUpdateDescriptionPatch, getUpdateFlavorPatch } from '../utils';
+import { cloudInitTestVm } from '../../k8s/mock_vm/cloudInitTestVm.mock';
const getVM = firstBoot => ({
metadata: {
@@ -75,11 +84,11 @@ const dataVolumeTemplate = {
},
};
-const compareAddPatch = (patch, expectedPath, expectedValue) => {
+const comparePatch = (patch, path, value, op = 'add') => {
expect(patch).toEqual({
- op: 'add',
- path: expectedPath,
- value: expectedValue,
+ op,
+ path,
+ value,
});
};
@@ -119,9 +128,9 @@ describe('utils.js tests', () => {
const patch = getAddDiskPatch(vm, storageNoClass);
expect(patch).toHaveLength(3);
- compareAddPatch(patch[0], '/spec/template/spec/domain/devices/disks/0', disk);
- compareAddPatch(patch[1], '/spec/template/spec/volumes', [volume]);
- compareAddPatch(patch[2], '/spec/dataVolumeTemplates', [dataVolumeTemplate]);
+ comparePatch(patch[0], '/spec/template/spec/domain/devices/disks/0', disk);
+ comparePatch(patch[1], '/spec/template/spec/volumes', [volume]);
+ comparePatch(patch[2], '/spec/dataVolumeTemplates', [dataVolumeTemplate]);
const dataVolWithClass = {
...dataVolumeTemplate,
@@ -135,8 +144,171 @@ describe('utils.js tests', () => {
};
const patchWithClass = getAddDiskPatch(vm, storage);
expect(patchWithClass).toHaveLength(3);
- compareAddPatch(patchWithClass[0], '/spec/template/spec/domain/devices/disks/0', disk);
- compareAddPatch(patchWithClass[1], '/spec/template/spec/volumes', [volume]);
- compareAddPatch(patchWithClass[2], '/spec/dataVolumeTemplates', [dataVolWithClass]);
+ comparePatch(patchWithClass[0], '/spec/template/spec/domain/devices/disks/0', disk);
+ comparePatch(patchWithClass[1], '/spec/template/spec/volumes', [volume]);
+ comparePatch(patchWithClass[2], '/spec/dataVolumeTemplates', [dataVolWithClass]);
+ });
+ it('Update description patch', () => {
+ let patch = getUpdateDescriptionPatch(cloudInitTestVm, 'new description');
+ expect(patch).toHaveLength(1);
+ comparePatch(patch[0], '/metadata/annotations/description', 'new description', 'replace');
+
+ patch = getUpdateDescriptionPatch(cloudInitTestVm, cloudInitTestVm.metadata.annotations.description);
+ expect(patch).toHaveLength(0);
+
+ patch = getUpdateDescriptionPatch(cloudInitTestVm, '');
+ expect(patch).toHaveLength(1);
+ comparePatch(patch[0], '/metadata/annotations/description', undefined, 'remove');
+
+ const vmWithNoDescription = cloneDeep(cloudInitTestVm);
+ delete vmWithNoDescription.metadata.annotations.description;
+
+ patch = getUpdateDescriptionPatch(vmWithNoDescription, 'new description');
+ expect(patch).toHaveLength(1);
+ comparePatch(patch[0], '/metadata/annotations/description', 'new description');
+
+ const vmWithNoAnnotations = cloneDeep(cloudInitTestVm);
+ delete vmWithNoAnnotations.metadata.annotations;
+
+ patch = getUpdateDescriptionPatch(vmWithNoAnnotations, 'new description');
+ expect(patch).toHaveLength(1);
+ comparePatch(patch[0], '/metadata/annotations', { description: 'new description' });
+ });
+ it('Update flavor patch', () => {
+ let patch = getUpdateFlavorPatch(cloudInitTestVm, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu/cores`, 1, 'replace');
+ comparePatch(patch[3], `/spec/template/spec/domain/resources/requests/memory`, '3G', 'replace');
+
+ patch = getUpdateFlavorPatch(cloudInitTestVm, 'small', '2', '2G');
+ expect(patch).toHaveLength(0);
+
+ patch = getUpdateFlavorPatch(cloudInitTestVm, 'small', '3', '2G');
+ expect(patch).toHaveLength(1);
+ comparePatch(patch[0], `/spec/template/spec/domain/cpu/cores`, 3, 'replace');
+
+ patch = getUpdateFlavorPatch(cloudInitTestVm, 'small', '2', '1G');
+ expect(patch).toHaveLength(1);
+ comparePatch(patch[0], `/spec/template/spec/domain/resources/requests/memory`, '1G', 'replace');
+
+ patch = getUpdateFlavorPatch(cloudInitTestVm, 'medium', '2', '2G');
+ expect(patch).toHaveLength(2);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1medium`, 'true');
+
+ const vmWithNoFlavorLabel = cloneDeep(cloudInitTestVm);
+ delete vmWithNoFlavorLabel.metadata.labels[`${TEMPLATE_FLAVOR_LABEL}/small`];
+
+ patch = getUpdateFlavorPatch(vmWithNoFlavorLabel, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(3);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[1], `/spec/template/spec/domain/cpu/cores`, 1, 'replace');
+ comparePatch(patch[2], `/spec/template/spec/domain/resources/requests/memory`, '3G', 'replace');
+
+ const vmWithNoLabels = cloneDeep(cloudInitTestVm);
+ delete vmWithNoLabels.metadata.labels;
+
+ patch = getUpdateFlavorPatch(vmWithNoLabels, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels`, {});
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu/cores`, 1, 'replace');
+ comparePatch(patch[3], `/spec/template/spec/domain/resources/requests/memory`, '3G', 'replace');
+
+ const vmWithNoCores = cloneDeep(cloudInitTestVm);
+ delete vmWithNoCores.spec.template.spec.domain.cpu.cores;
+
+ patch = getUpdateFlavorPatch(vmWithNoCores, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu/cores`, 1);
+ comparePatch(patch[3], `/spec/template/spec/domain/resources/requests/memory`, '3G', 'replace');
+
+ const vmWithNoCpuCores = cloneDeep(cloudInitTestVm);
+ delete vmWithNoCpuCores.spec.template.spec.domain.cpu;
+
+ patch = getUpdateFlavorPatch(vmWithNoCpuCores, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu`, { cores: 1 });
+ comparePatch(patch[3], `/spec/template/spec/domain/resources/requests/memory`, '3G', 'replace');
+
+ const vmWithNoMemory = cloneDeep(cloudInitTestVm);
+ delete vmWithNoMemory.spec.template.spec.domain.resources.requests.memory;
+
+ patch = getUpdateFlavorPatch(vmWithNoMemory, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu/cores`, 1, 'replace');
+ comparePatch(patch[3], `/spec/template/spec/domain/resources/requests/memory`, '3G');
+
+ const vmWithNoRequests = cloneDeep(cloudInitTestVm);
+ delete vmWithNoRequests.spec.template.spec.domain.resources.requests;
+
+ patch = getUpdateFlavorPatch(vmWithNoRequests, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu/cores`, 1, 'replace');
+ comparePatch(patch[3], `/spec/template/spec/domain/resources/requests`, { memory: '3G' });
+
+ const vmWithNoResources = cloneDeep(cloudInitTestVm);
+ delete vmWithNoResources.spec.template.spec.domain.resources;
+
+ patch = getUpdateFlavorPatch(vmWithNoResources, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(4);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain/cpu/cores`, 1, 'replace');
+ comparePatch(patch[3], `/spec/template/spec/domain/resources`, { requests: { memory: '3G' } });
+
+ const vmWithNoDomain = cloneDeep(cloudInitTestVm);
+ delete vmWithNoDomain.spec.template.spec.domain;
+
+ patch = getUpdateFlavorPatch(vmWithNoDomain, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(5);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec/domain`, {});
+ comparePatch(patch[3], `/spec/template/spec/domain/cpu`, { cores: 1 });
+ comparePatch(patch[4], `/spec/template/spec/domain/resources`, { requests: { memory: '3G' } });
+
+ const vmWithNoTemplateSpec = cloneDeep(cloudInitTestVm);
+ delete vmWithNoTemplateSpec.spec.template.spec;
+
+ patch = getUpdateFlavorPatch(vmWithNoTemplateSpec, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(5);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template/spec`, { domain: {} });
+ comparePatch(patch[3], `/spec/template/spec/domain/cpu`, { cores: 1 });
+ comparePatch(patch[4], `/spec/template/spec/domain/resources`, { requests: { memory: '3G' } });
+
+ const vmWithNoTemplate = cloneDeep(cloudInitTestVm);
+ delete vmWithNoTemplate.spec.template;
+
+ patch = getUpdateFlavorPatch(vmWithNoTemplate, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(5);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec/template`, { spec: { domain: {} } });
+ comparePatch(patch[3], `/spec/template/spec/domain/cpu`, { cores: 1 });
+ comparePatch(patch[4], `/spec/template/spec/domain/resources`, { requests: { memory: '3G' } });
+
+ const vmWithNoSpec = cloneDeep(cloudInitTestVm);
+ delete vmWithNoSpec.spec;
+
+ patch = getUpdateFlavorPatch(vmWithNoSpec, 'Custom', '1', '3G');
+ expect(patch).toHaveLength(5);
+ comparePatch(patch[0], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1small`, undefined, 'remove');
+ comparePatch(patch[1], `/metadata/labels/${TEMPLATE_FLAVOR_LABEL}~1Custom`, 'true');
+ comparePatch(patch[2], `/spec`, { template: { spec: { domain: {} } } });
+ comparePatch(patch[3], `/spec/template/spec/domain/cpu`, { cores: 1 });
+ comparePatch(patch[4], `/spec/template/spec/domain/resources`, { requests: { memory: '3G' } });
});
});
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 3dd8d1ee1..c92bee2ef 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -1,7 +1,13 @@
-import { get } from 'lodash';
+import { get, has } from 'lodash';
-import { getDisks, getInterfaces, getName } from './selectors';
-import { ANNOTATION_FIRST_BOOT, BOOT_ORDER_FIRST, BOOT_ORDER_SECOND, PVC_ACCESSMODE_RWO } from '../constants';
+import { getDisks, getInterfaces, getName, getDescription, getFlavor, getCpu, getMemory } from './selectors';
+import {
+ ANNOTATION_FIRST_BOOT,
+ BOOT_ORDER_FIRST,
+ BOOT_ORDER_SECOND,
+ PVC_ACCESSMODE_RWO,
+ TEMPLATE_FLAVOR_LABEL,
+} from '../constants';
export function prefixedId(idPrefix, id) {
return idPrefix && id ? `${idPrefix}-${id}` : null;
@@ -143,3 +149,174 @@ export const getAddDiskPatch = (vm, storage) => {
return patch;
};
+
+export const getUpdateDescriptionPatch = (vm, description) => {
+ const patch = [];
+ if (description !== getDescription(vm)) {
+ if (!description && has(vm.metadata, 'annotations.description')) {
+ patch.push({
+ op: 'remove',
+ path: '/metadata/annotations/description',
+ });
+ } else if (!has(vm.metadata, 'annotations')) {
+ patch.push({
+ op: 'add',
+ path: '/metadata/annotations',
+ value: {
+ description,
+ },
+ });
+ } else {
+ patch.push({
+ op: has(vm.metadata, 'annotations.description') ? 'replace' : 'add',
+ path: '/metadata/annotations/description',
+ value: description,
+ });
+ }
+ }
+ return patch;
+};
+
+const getDomainPatch = vm => {
+ let patch;
+ if (!has(vm, 'spec')) {
+ patch = {
+ op: 'add',
+ path: '/spec',
+ value: {
+ template: {
+ spec: {
+ domain: {},
+ },
+ },
+ },
+ };
+ } else if (!has(vm.spec, 'template')) {
+ patch = {
+ op: 'add',
+ path: '/spec/template',
+ value: {
+ spec: {
+ domain: {},
+ },
+ },
+ };
+ } else if (!has(vm.spec.template, 'spec')) {
+ patch = {
+ op: 'add',
+ path: '/spec/template/spec',
+ value: {
+ domain: {},
+ },
+ };
+ } else if (!has(vm.spec.template.spec, 'domain')) {
+ patch = {
+ op: 'add',
+ path: '/spec/template/spec/domain',
+ value: {},
+ };
+ }
+ return patch;
+};
+
+const getLabelsPatch = vm => {
+ if (!has(vm.metadata, 'labels')) {
+ return {
+ op: 'add',
+ path: '/metadata/labels',
+ value: {},
+ };
+ }
+ return null;
+};
+
+const getCpuPatch = (vm, cpu) => {
+ if (!has(vm.spec, 'template.spec.domain.cpu')) {
+ return {
+ op: 'add',
+ path: '/spec/template/spec/domain/cpu',
+ value: {
+ cores: parseInt(cpu, 10),
+ },
+ };
+ }
+ return {
+ op: has(vm.spec, 'template.spec.domain.cpu.cores') ? 'replace' : 'add',
+ path: '/spec/template/spec/domain/cpu/cores',
+ value: parseInt(cpu, 10),
+ };
+};
+
+const getMemoryPatch = (vm, memory) => {
+ if (!has(vm.spec, 'template.spec.domain.resources')) {
+ return {
+ op: 'add',
+ path: '/spec/template/spec/domain/resources',
+ value: {
+ requests: {
+ memory,
+ },
+ },
+ };
+ }
+ if (!has(vm.spec, 'template.spec.domain.resources.requests')) {
+ return {
+ op: 'add',
+ path: '/spec/template/spec/domain/resources/requests',
+ value: {
+ memory,
+ },
+ };
+ }
+ return {
+ op: has(vm.spec, 'template.spec.domain.resources.requests.memory') ? 'replace' : 'add',
+ path: '/spec/template/spec/domain/resources/requests/memory',
+ value: memory,
+ };
+};
+
+export const getUpdateFlavorPatch = (vm, flavor, cpu, memory) => {
+ const patch = [];
+ if (flavor !== getFlavor(vm)) {
+ const labelKey = `${TEMPLATE_FLAVOR_LABEL}/${flavor}`.replace('~', '~0').replace('/', '~1');
+ const labelPatch = getLabelsPatch(vm);
+ if (labelPatch) {
+ patch.push(labelPatch);
+ }
+ const flavorLabel = Object.keys(vm.metadata.labels || {}).find(key => key.startsWith(TEMPLATE_FLAVOR_LABEL));
+ if (flavorLabel) {
+ const flavorParts = flavorLabel.split('/');
+ if (flavorParts[flavorParts.length - 1] !== flavor) {
+ const escapedLabel = flavorLabel.replace('~', '~0').replace('/', '~1');
+ patch.push({
+ op: 'remove',
+ path: `/metadata/labels/${escapedLabel}`,
+ });
+ }
+ }
+ patch.push({
+ op: 'add',
+ path: `/metadata/labels/${labelKey}`,
+ value: 'true',
+ });
+ }
+
+ const vmCpu = getCpu(vm);
+ const vmMemory = getMemory(vm);
+
+ if (parseInt(cpu, 10) !== vmCpu || memory !== vmMemory) {
+ const domainPatch = getDomainPatch(vm);
+ if (domainPatch) {
+ patch.push(domainPatch);
+ }
+ }
+
+ if (parseInt(cpu, 10) !== vmCpu) {
+ patch.push(getCpuPatch(vm, cpu));
+ }
+
+ if (memory !== vmMemory) {
+ patch.push(getMemoryPatch(vm, memory));
+ }
+ return patch;
+};