Skip to content

Commit

Permalink
feat(xo-server-netbox): synchronize tags
Browse files Browse the repository at this point in the history
Fixes #5899
See Zammad#12478
See https://xcp-ng.org/forum/topic/6902
  • Loading branch information
pdonias committed Jul 28, 2023
1 parent 8f2cfaa commit 9f2fc81
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [Netbox] Synchronize tags [#5899](https://github.com/vatesfr/xen-orchestra/issues/5899) [Forum#6902](https://xcp-ng.org/forum/topic/6902)

### Bug fixes

> Users must be able to say: “I had this issue, happy to know it's fixed”
Expand All @@ -27,4 +29,6 @@
<!--packages-start-->

- xo-server-netbox minor

<!--packages-end-->
10 changes: 10 additions & 0 deletions packages/xo-server-netbox/src/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
48 changes: 39 additions & 9 deletions packages/xo-server-netbox/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class Netbox {

log.info('Synchronizing VMs')

const createNetboxVm = async (vm, { cluster, platforms }) => {
const createNetboxVm = async (vm, { cluster, platforms, netboxTags }) => {
const netboxVm = {
custom_fields: { uuid: vm.uuid },
name: vm.name_label.slice(0, NAME_MAX_LENGTH).trim(),
Expand All @@ -323,6 +323,7 @@ class Netbox {
cluster: cluster.id,
status: vm.power_state === 'Running' ? 'active' : 'offline',
platform: null,
tags: [],
}

const distro = vm.os_version?.distro
Expand All @@ -341,6 +342,32 @@ class Netbox {
netboxVm.platform = platform.id
}

const netboxVmTags = []
for (const tag of vm.tags) {
const slug = slugify(tag)
let netboxTag = find(netboxTags, { slug })
if (netboxTag === undefined) {
// TODO: Should we also delete/update tags in Netbox?
netboxTag = await this.#request('/extras/tags/', 'POST', {
name: tag,
slug,
color: '2598d9',
description: 'XO tag',
})
netboxTags[netboxTag.id] = netboxTag
}

// 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(netboxVmTags, { id: netboxTag.id }) === undefined) {
netboxVmTags.push({ id: netboxTag.id })
}
}

// Sort them so that they can be compared by diff()
netboxVm.tags = netboxVmTags.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 &&
Expand All @@ -353,14 +380,17 @@ class Netbox {
}

// Some props need to be flattened to satisfy the POST request schema
const flattenNested = vm => ({
...vm,
cluster: vm.cluster?.id ?? null,
status: vm.status?.value ?? null,
platform: vm.platform?.id ?? null,
const flattenNested = netboxVm => ({
...netboxVm,
cluster: netboxVm.cluster?.id ?? null,
status: netboxVm.status?.value ?? null,
platform: netboxVm.platform?.id ?? null,
// Sort them so that they can be compared by diff()
tags: netboxVm.tags.map(tag => ({ id: tag.id })).sort(({ id: id1 }, { id: id2 }) => (id1 < id2 ? -1 : 1)),
})

const platforms = keyBy(await this.#request('/dcim/platforms'), 'id')
const platforms = keyBy(await this.#request('/dcim/platforms/'), 'id')
const netboxTags = 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
Expand Down Expand Up @@ -401,7 +431,7 @@ class Netbox {
const netboxVm = allNetboxVms[vm.uuid]
delete poolNetboxVms[vm.uuid]

const updatedVm = await createNetboxVm(vm, { cluster, platforms })
const updatedVm = await createNetboxVm(vm, { cluster, platforms, netboxTags })

if (netboxVm !== undefined) {
// VM found in Netbox: update VM (I.1)
Expand All @@ -427,7 +457,7 @@ class Netbox {
const cluster = allClusters[pool?.uuid]
if (cluster !== undefined) {
// If the VM is found in XO: update it if necessary (II.1)
const updatedVm = await createNetboxVm(vm, { cluster, platforms })
const updatedVm = await createNetboxVm(vm, { cluster, platforms, netboxTags })
const patch = diff(updatedVm, flattenNested(netboxVm))

if (patch === undefined) {
Expand Down

0 comments on commit 9f2fc81

Please sign in to comment.