Compare commits

...

2 Commits

Author SHA1 Message Date
Pierre Donias
74f6a67d2a feat(netbox/tags): keep user defined tags
Fixes #7078
2023-10-30 15:40:51 +01:00
Pierre Donias
27135d1128 feat(netbox): use Netbox version instead of Netbox API version
Netbox version is more precise (X.Y.Z instead of X.Y) and already semver valid
2023-10-30 14:59:14 +01:00
2 changed files with 28 additions and 15 deletions

View File

@@ -12,6 +12,7 @@
> Users must be able to say: “I had this issue, happy to know it's fixed”
- [Netbox] Fix "The selected cluster is not assigned to this site" error [Forum#7887](https://xcp-ng.org/forum/topic/7887) (PR [#7124](https://github.com/vatesfr/xen-orchestra/pull/7124))
- [Netbox] Do not override user defined tags in Netbox [#7078](https://github.com/vatesfr/xen-orchestra/issues/7078) (PR [#7133](https://github.com/vatesfr/xen-orchestra/pull/7133))
### Packages to release
@@ -29,7 +30,7 @@
<!--packages-start-->
- xo-server-netbox patch
- xo-server-netbox minor
- xo-web patch
<!--packages-end-->

View File

@@ -17,6 +17,7 @@ import slugify from './slugify'
const log = createLogger('xo:netbox')
const CLUSTER_TYPE = 'XCP-ng Pool'
const XO_TAG_COLOR = '2598d9'
const TYPES_WITH_UUID = ['virtualization.cluster', 'virtualization.virtualmachine', 'virtualization.vminterface']
const CHUNK_SIZE = 100
export const NAME_MAX_LENGTH = 64
@@ -39,7 +40,7 @@ class Netbox {
#endpoint
#intervalToken
#loaded
#netboxApiVersion
#netboxVersion
#xoPools
#removeApiMethods
#syncInterval
@@ -103,6 +104,7 @@ class Netbox {
}
async test() {
await this.#fetchNetboxVersion()
await this.#checkCustomFields()
const randomSuffix = Math.random().toString(36).slice(2, 11)
@@ -144,9 +146,6 @@ class Netbox {
const httpRequest = async () => {
try {
const response = await this.#xo.httpRequest(url, options)
// API version only follows minor version, which is less precise and is not semver-valid
// See https://github.com/netbox-community/netbox/issues/12879#issuecomment-1589190236
this.#netboxApiVersion = semver.coerce(response.headers['api-version'])?.version ?? undefined
const body = await response.text()
if (body.length > 0) {
return JSON.parse(body)
@@ -185,7 +184,7 @@ class Netbox {
response = await httpRequest()
}
if (method !== 'GET') {
if (method !== 'GET' || response.results === undefined) {
return response
}
@@ -213,9 +212,15 @@ class Netbox {
}
}
async #fetchNetboxVersion() {
// Endpoint supported since v2.10. If not supported, Netbox needs to be updated.
this.#netboxVersion = (await this.#request('/status/'))['netbox-version']
}
// ---------------------------------------------------------------------------
async #synchronize(xoPools = this.#xoPools) {
await this.#fetchNetboxVersion()
await this.#checkCustomFields()
log.info(`Synchronizing ${xoPools.length} pools with Netbox`, { pools: xoPools })
@@ -342,7 +347,7 @@ class Netbox {
// v3.3.0: "site" is REQUIRED and MUST be the same as cluster's site
// v3.3.5: "site" is OPTIONAL (auto-assigned in UI, not in API). `null` and cluster's site are accepted.
// v3.4.8: "site" is OPTIONAL and AUTO-ASSIGNED with cluster's site. If passed: ignored except if site is different from cluster's, then error.
if (this.#netboxApiVersion === undefined || semver.satisfies(this.#netboxApiVersion, '3.3.0 - 3.4.7')) {
if (this.#netboxVersion === undefined || semver.satisfies(this.#netboxVersion, '3.3.0 - 3.4.7')) {
nbVm.site = find(nbClusters, { id: nbCluster.id })?.site?.id ?? null
}
@@ -362,7 +367,7 @@ class Netbox {
nbVm.platform = nbPlatform.id
}
const nbVmTags = []
nbVm.tags = []
for (const tag of xoVm.tags) {
const slug = slugify(tag)
let nbTag = find(nbTags, { slug })
@@ -371,7 +376,7 @@ class Netbox {
nbTag = await this.#request('/extras/tags/', 'POST', {
name: tag,
slug,
color: '2598d9',
color: XO_TAG_COLOR,
description: 'XO tag',
})
nbTags[nbTag.id] = nbTag
@@ -380,16 +385,13 @@ class Netbox {
// 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 (!some(nbVmTags, { id: nbTag.id })) {
nbVmTags.push({ id: nbTag.id })
if (!some(nbVm.tags, { id: nbTag.id })) {
nbVm.tags.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 && !semver.satisfies(this.#netboxApiVersion, '>=2.7.0')) {
if (this.#netboxVersion !== undefined && !semver.satisfies(this.#netboxVersion, '>=2.7.0')) {
nbVm.status = xoVm.power_state === 'Running' ? 1 : 0
}
@@ -450,6 +452,16 @@ class Netbox {
if (nbVm !== undefined) {
// VM found in Netbox: update VM (I.1)
// Keep user defined tags by adding all existing tags that don't have XO tags color
updatedVm.tags = updatedVm.tags
.concat(
nbVm.tags
.filter(nbTag => nbTag.color !== XO_TAG_COLOR && !some(updatedVm.tags, { id: nbTag.id }))
.map(nbTag => ({ id: nbTag.id }))
)
.sort(({ id: id1 }, { id: id2 }) => (id1 < id2 ? -1 : 1))
const patch = diff(updatedVm, flattenNested(nbVm))
if (patch !== undefined) {
vmsToUpdate.push(patch)