1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-22 22:03:39 +03:00

B OpenNebula/one#5833: Fix serivce template parsing (#3167)

Signed-off-by: Victor Hansson <vhansson@opennebula.io>
This commit is contained in:
vichansson 2024-07-24 12:46:45 +03:00 committed by GitHub
parent 8ac48b54d1
commit 997843633d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 148 additions and 45 deletions

View File

@ -89,10 +89,10 @@ const NAME = {
type: INPUT_TYPES.TEXT,
validation: string()
.trim()
.lowercase()
.matches(/^[a-z0-9_]*$/, {
.uppercase()
.matches(/^[A-Z0-9_]*$/, {
message:
'Name must only contain lowercase alphanumeric characters and no spaces',
'Name must only contain uppercase alphanumeric characters and no spaces',
excludeEmptyString: true,
})
.required()

View File

@ -180,9 +180,6 @@ const RoleNetwork = ({ stepId, selectedRoleIndex }) => {
if (index === fieldArrayIndex) {
return { ...f, rowSelected: rowToggle, aliasSelected: false }
}
if (f.aliasIdx === fieldArrayIndex) {
return { ...f, aliasIdx: -1 }
}
return f
})

View File

@ -129,6 +129,9 @@ const Steps = createSteps([General, Extra, RoleDefinition, RoleConfig], {
}, []),
false
),
vm_template_contents: reversedVmTc?.map(
(content) => content?.remainingContent
),
MINMAXVMS: ServiceTemplate?.TEMPLATE?.BODY?.roles
?.filter((role) => role != null)
?.map((role) => ({
@ -162,7 +165,7 @@ const Steps = createSteps([General, Extra, RoleDefinition, RoleConfig], {
[ROLE_DEFINITION_ID]: roleDefinitionData,
[ROLE_CONFIG_ID]: { ...roleConfigData },
},
{ stripUnknown: true }
{ stripUnknown: false }
)
return knownTemplate
@ -178,8 +181,11 @@ const Steps = createSteps([General, Extra, RoleDefinition, RoleConfig], {
const getVmTemplateContents = (index) => {
const contents = parseVmTemplateContents({
networks: roleConfigData?.NETWORKS?.[index],
networks:
roleConfigData?.NETWORKS?.[index] ||
roleConfigData?.NETWORKDEFS?.[index],
rdpConfig: roleConfigData?.RDP?.[index],
remainingContent: roleConfigData?.vm_template_contents?.[index],
schedActions: extraData?.SCHED_ACTION,
})
@ -274,7 +280,16 @@ const Steps = createSteps([General, Extra, RoleDefinition, RoleConfig], {
custom_attrs: getCustomAttributes(),
}
const cleanedTemplate = convertKeysToCase(formatTemplate)
const cleanedTemplate = {
...convertKeysToCase(formatTemplate, true, 1),
...(formatTemplate?.roles || formatTemplate?.ROLES
? {
roles: convertKeysToCase(
formatTemplate?.roles || formatTemplate?.ROLES
),
}
: {}),
}
return cleanedTemplate
} catch (error) {}

View File

@ -67,7 +67,12 @@ const Steps = createSteps([General, UserInputs, Network, Charter], {
} = formData
const formatTemplate = {
custom_attrs_values: { ...userInputsData },
custom_attrs_values: Object.fromEntries(
Object.entries(userInputsData).map(([key, value]) => [
key.toUpperCase(),
String(value),
])
),
networks_values: networkData?.NETWORKS?.map((network) => ({
[network?.name]: {
[['existing', 'reserve'].includes(network?.tableType)

View File

@ -66,11 +66,17 @@ function CreateServiceTemplate() {
const onSubmit = async (jsonTemplate) => {
try {
if (!templateId) {
const newTemplateId = await allocate({
const {
DOCUMENT: { ID: newTemplateId, NAME: templateName },
} = await allocate({
template: jsonTemplate,
}).unwrap()
history.push(PATH.TEMPLATE.SERVICES.LIST)
enqueueSuccess(T.SuccessServiceTemplateCreated, [newTemplateId, NAME])
enqueueSuccess(T.SuccessServiceTemplateCreated, [
newTemplateId,
templateName,
])
} else {
await update({
id: templateId,

View File

@ -75,20 +75,28 @@ export const parseNetworkString = (network, reverse = false) => {
*
* @param {object | Array} obj - The object or array whose keys are to be converted.
* @param {boolean} [toLower=true] - Whether to convert keys to lower case. If false, keys are converted to upper case.
* @param {number} depth - Control depth of conversion
* @returns {object | Array} - The input object or array with keys converted to the specified case.
*/
export const convertKeysToCase = (obj, toLower = true) => {
export const convertKeysToCase = (obj, toLower = true, depth = Infinity) => {
if (depth < 1) return obj
if (_.isArray(obj)) {
return obj.map((item) => convertKeysToCase(item, toLower))
return obj.map((item) => convertKeysToCase(item, toLower, depth))
}
if (_.isObject(obj) && !_.isDate(obj) && !_.isFunction(obj)) {
return _.mapValues(
_.mapKeys(obj, (_value, key) =>
toLower ? key.toLowerCase() : key.toUpperCase()
),
(value) => convertKeysToCase(value, toLower)
const convertedObj = _.mapKeys(obj, (_value, key) =>
toLower ? key.toLowerCase() : key.toUpperCase()
)
if (depth > 1) {
return _.mapValues(convertedObj, (value) =>
convertKeysToCase(value, toLower, depth - 1)
)
}
return convertedObj
}
return obj

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import _ from 'lodash'
/* eslint-disable no-useless-escape */
const formatNic = (nic, parent, rdp) => {
@ -21,24 +22,23 @@ const formatNic = (nic, parent, rdp) => {
return `${
parent ? 'NIC_ALIAS' : 'NIC'
} = [\n NAME = \"${NIC}\",\n NETWORK_ID = \"$${
NETWORK_ID !== undefined ? NETWORK_ID.toLowerCase() : ''
NETWORK_ID !== undefined ? NETWORK_ID : ''
}\"${rdp ? `,\n RDP = \"YES\"` : ''}${
parent ? `,\n PARENT = \"${parent}\"` : ''
} ]\n`
}
const formatAlias = (fNics) => {
const formatAlias = (fNics) =>
fNics?.map((fnic) => {
if (fnic?.alias) {
const parent = fNics?.find(
(nic) => nic?.NIC_NAME === fnic?.alias?.name
)?.NIC_ID
if (fnic?.alias || fnic?.PARENT) {
const parent =
fnic?.PARENT ??
fNics?.find((nic) => nic?.NIC_NAME === fnic?.alias?.name)?.NIC_ID
fnic.formatNic = formatNic({ [fnic?.NIC_ID]: fnic?.NIC_NAME }, parent)
}
return ''
})
}
const formatSchedActions = (schedAction) => {
const { ACTION, TIME, DAYS, END_TYPE, END_VALUE, REPEAT, ID } = schedAction
@ -79,15 +79,69 @@ const parseSection = (section) => {
return { header, content }
}
const extractSections = (content) => {
const sections = []
const regex = /(NIC|NIC_ALIAS|SCHED_ACTION)\s*=\s*\[[^\]]+\]/g
let match
while ((match = regex.exec(content))) {
sections.push(match[0])
}
return sections
}
const extractPropertiesToArray = (content) => {
const properties = []
const regex = /(\w+\s*=\s*"[^"]*")/g
let match
while ((match = regex.exec(content))) {
properties.push(match[1])
}
return properties
}
const formatInstantiate = (contents) => {
const { vmTemplateContents, customAttrsValues } = contents
const formatUserInputs = Object.entries(customAttrsValues)
?.map(([input, value]) => `${input.toLowerCase()} = "${value}"`)
?.join('\n')
?.concat('\n')
const sections = extractSections(vmTemplateContents)
.map(parseSection)
.filter(Boolean)
return vmTemplateContents + formatUserInputs
const nonNicContent = vmTemplateContents.replace(
/(NIC|NIC_ALIAS|SCHED_ACTION)\s*=\s*\[[^\]]+\]/g,
''
)
const templateProperties = extractPropertiesToArray(nonNicContent)
const customProperties = Object.entries(customAttrsValues).map(
([key, value]) => `${key.toUpperCase()} = "${value}"`
)
const combinedProperties = _.uniqWith(
[...templateProperties, ...customProperties],
_.isEqual
)
const filteredProperties = combinedProperties.filter(
(property) => !property.includes('= ""')
)
const combinedContent = [
...sections.map(({ header, content }) => {
const props = Object.entries(content)
.map(([key, value]) => ` ${key.toUpperCase()} = "${value}"`)
.join(',\n')
return `${header} = [\n${props} ]`
}),
...filteredProperties,
]
const formattedTemplate = combinedContent.join('\n') + '\n'
return formattedTemplate
}
/**
@ -115,8 +169,11 @@ const formatVmTemplateContents = (
const sections = contents.match(
/(NIC_ALIAS|NIC|SCHED_ACTION)\s*=\s*\[[^\]]+\]/g
)
const remainingContent = contents
.replace(/(NIC_ALIAS|NIC|SCHED_ACTION)\s*=\s*\[[^\]]+\]/g, '')
.trim()
if (!sections) return { networks: nics, schedActions }
if (!sections) return { networks: nics, schedActions, remainingContent }
sections.forEach((section) => {
const parsedSection = parseSection(section)
@ -130,37 +187,52 @@ const formatVmTemplateContents = (
}
})
return { networks: nics, schedActions }
return { networks: nics, schedActions, remainingContent }
} else {
const { networks, rdpConfig, schedActions } = contents
if (!networks) {
return ''
}
const { networks, rdpConfig, schedActions, remainingContent } = contents
const preformattedNetworks = networks.every(
(network) =>
network?.NAME &&
network?.NETWORK_ID &&
network?.NAME?.replace(/_/g, '')?.startsWith('NIC') &&
network?.NETWORK_ID?.includes('$')
)
const formattedActions = schedActions?.map((action, index) =>
formatSchedActions({ ...action, ID: index })
)
const formattedNics = networks
?.filter((net) => net?.rowSelected)
?.filter((net) => net?.rowSelected || preformattedNetworks)
?.map((nic, index) => ({
formatNic: formatNic(
{
[`_NIC${index}`]: nic?.name,
[`_${
preformattedNetworks
? nic?.NAME?.replace(/_/g, '')
: `NIC${index}`
}`]: preformattedNetworks
? nic?.NETWORK_ID?.replace(/\$/g, '')
: nic?.name,
},
false,
nic?.name === rdpConfig
preformattedNetworks ? nic?.RDP === 'YES' : nic?.name === rdpConfig
),
NIC_ID: `_NIC${index}`,
NIC_NAME: nic?.name,
NIC_NAME: preformattedNetworks
? nic?.NETWORK_ID?.replace(/\$/g, '')
: nic?.name,
...(nic?.aliasIdx !== -1 && { alias: networks?.[nic?.aliasIdx] }),
...(preformattedNetworks && nic?.PARENT ? { PARENT: nic?.PARENT } : {}),
}))
formatAlias(formattedNics)
const vmTemplateContents = formattedNics
?.map((nic) => nic.formatNic)
.join('')
.concat(formattedActions?.join('') ?? '')
let vmTemplateContents =
formattedNics?.map((nic) => nic.formatNic).join('') ?? ''
vmTemplateContents += formattedActions?.join('') ?? ''
vmTemplateContents += remainingContent ? `\n${remainingContent}` : ''
return vmTemplateContents
}