From 30818104507220adc885652dc130b770d490113c Mon Sep 17 00:00:00 2001 From: Pierre Donias Date: Fri, 28 Jul 2023 17:12:54 +0200 Subject: [PATCH] feat(xo-server-netbox): synchronize VM tags Fixes #5899 See Zammad#12478 See https://xcp-ng.org/forum/topic/6902 --- CHANGELOG.unreleased.md | 3 +++ packages/xo-server-netbox/src/diff.js | 10 +++++++ packages/xo-server-netbox/src/index.js | 36 +++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 6d72e3991..ffd12c063 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [Netbox] Synchronize VM tags [#5899](https://github.com/vatesfr/xen-orchestra/issues/5899) [Forum#6902](https://xcp-ng.org/forum/topic/6902) (PR [#6957](https://github.com/vatesfr/xen-orchestra/pull/6957)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” @@ -34,6 +36,7 @@ - xen-api patch - xo-server patch - xo-server-auth-ldap patch +- xo-server-netbox minor - xo-web patch diff --git a/packages/xo-server-netbox/src/diff.js b/packages/xo-server-netbox/src/diff.js index 35c1ef253..2a2e9417a 100644 --- a/packages/xo-server-netbox/src/diff.js +++ b/packages/xo-server-netbox/src/diff.js @@ -16,6 +16,16 @@ export default function diff(newer, older) { return newer === older ? undefined : newer } + // For arrays, they must be exactly the same or we pass the new one entirely + if (Array.isArray(newer)) { + if (newer.length !== older.length || newer.some((value, index) => diff(value, older[index]) !== undefined)) { + return newer + } + + return + } + + // For objects, we only need to pass the properties that are different newer = { ...newer } Object.keys(newer).forEach(key => { if ((key === 'name' && compareNames(newer[key], older[key])) || diff(newer[key], older?.[key]) === undefined) { diff --git a/packages/xo-server-netbox/src/index.js b/packages/xo-server-netbox/src/index.js index bccc9e008..f878639fa 100644 --- a/packages/xo-server-netbox/src/index.js +++ b/packages/xo-server-netbox/src/index.js @@ -308,7 +308,7 @@ class Netbox { log.info('Synchronizing VMs') - const createNbVm = async (xoVm, { nbCluster, nbPlatforms }) => { + const createNbVm = async (xoVm, { nbCluster, nbPlatforms, nbTags }) => { const nbVm = { custom_fields: { uuid: xoVm.uuid }, name: xoVm.name_label.slice(0, NAME_MAX_LENGTH).trim(), @@ -325,6 +325,7 @@ class Netbox { cluster: nbCluster.id, status: xoVm.power_state === 'Running' ? 'active' : 'offline', platform: null, + tags: [], } const distro = xoVm.os_version?.distro @@ -343,6 +344,32 @@ class Netbox { nbVm.platform = nbPlatform.id } + const nbVmTags = [] + for (const tag of xoVm.tags) { + const slug = slugify(tag) + let nbTag = find(nbTags, { slug }) + if (nbTag === undefined) { + // TODO: Should we also delete/update tags in Netbox? + nbTag = await this.#request('/extras/tags/', 'POST', { + name: tag, + slug, + color: '2598d9', + description: 'XO tag', + }) + nbTags[nbTag.id] = nbTag + } + + // Edge case: tags "foo" and "Foo" would have the same slug. It's + // allowed in XO but not in Netbox so in that case, we only add it once + // to Netbox. + if (find(nbVmTags, { id: nbTag.id }) === undefined) { + nbVmTags.push({ id: nbTag.id }) + } + } + + // Sort them so that they can be compared by diff() + nbVm.tags = nbVmTags.sort(({ id: id1 }, { id: id2 }) => (id1 < id2 ? -1 : 1)) + // https://netbox.readthedocs.io/en/stable/release-notes/version-2.7/#api-choice-fields-now-use-string-values-3569 if ( this.#netboxApiVersion !== undefined && @@ -360,9 +387,12 @@ class Netbox { cluster: nbVm.cluster?.id ?? null, status: nbVm.status?.value ?? null, platform: nbVm.platform?.id ?? null, + // Sort them so that they can be compared by diff() + tags: nbVm.tags.map(nbTag => ({ id: nbTag.id })).sort(({ id: id1 }, { id: id2 }) => (id1 < id2 ? -1 : 1)), }) const nbPlatforms = keyBy(await this.#request('/dcim/platforms/'), 'id') + const nbTags = keyBy(await this.#request('/extras/tags/'), 'id') // Get all the VMs in the cluster type "XCP-ng Pool" even from clusters // we're not synchronizing right now, so we can "migrate" them back if @@ -401,7 +431,7 @@ class Netbox { const nbVm = allNbVms[xoVm.uuid] delete xoPoolNbVms[xoVm.uuid] - const updatedVm = await createNbVm(xoVm, { nbCluster, nbPlatforms }) + const updatedVm = await createNbVm(xoVm, { nbCluster, nbPlatforms, nbTags }) if (nbVm !== undefined) { // VM found in Netbox: update VM (I.1) @@ -427,7 +457,7 @@ class Netbox { const nbCluster = allNbClusters[xoPool?.uuid] if (nbCluster !== undefined) { // If the VM is found in XO: update it if necessary (II.1) - const updatedVm = await createNbVm(xoVm, { nbCluster, nbPlatforms }) + const updatedVm = await createNbVm(xoVm, { nbCluster, nbPlatforms, nbTags }) const patch = diff(updatedVm, flattenNested(nbVm)) if (patch === undefined) {