| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- /*
- Copyright (C) 2017 Cloudbase Solutions SRL
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- /* eslint-disable no-param-reassign */
- import Utils from '@src/utils/ObjectUtils'
- import type { Field } from '@src/@types/Field'
- import type { OptionValues, StorageMap } from '@src/@types/Endpoint'
- import type { SchemaProperties, SchemaDefinitions } from '@src/@types/Schema'
- import type { NetworkMap } from '@src/@types/Network'
- import type { InstanceScript } from '@src/@types/Instance'
- import { executionOptions, migrationFields } from '@src/constants'
- import { UserScriptData } from '@src/@types/MainItem'
- import { defaultSchemaToFields } from './ConnectionSchemaPlugin'
- const migrationImageOsTypes = ['windows', 'linux']
- export const defaultFillFieldValues = (field: Field, option: OptionValues) => {
- if (field.type === 'string') {
- field.enum = [...option.values]
- if (option.config_default) {
- field.default = typeof option.config_default === 'string' ? option.config_default : option.config_default.id
- }
- }
- if (field.type === 'array') {
- field.enum = [...option.values]
- }
- if (field.type === 'boolean' && option.config_default != null) {
- field.default = typeof option.config_default === 'boolean' ? option.config_default : option.config_default === 'true'
- }
- }
- export const defaultFillMigrationImageMapValues = (opts: {
- field: Field,
- option: OptionValues,
- migrationImageMapFieldName: string,
- requiresWindowsImage: boolean,
- }): boolean => {
- const {
- field, option, migrationImageMapFieldName, requiresWindowsImage,
- } = opts
- if (field.name !== migrationImageMapFieldName) {
- return false
- }
- field.properties = migrationImageOsTypes.map(os => {
- const values = (option.values as any)
- .filter((v: { os_type: string }) => v.os_type === os || v.os_type === 'unknown')
- .sort((v1: { os_type: string }, v2: { os_type: string }) => {
- if (v1.os_type === 'unknown' && v2.os_type !== 'unknown') {
- return 1
- } if (v1.os_type !== 'unknown' && v2.os_type === 'unknown') {
- return -1
- }
- return 0
- })
- const unknownIndex = values.findIndex((v: { os_type: string }) => v.os_type === 'unknown')
- if (unknownIndex > -1 && values.filter((v: { os_type: string }) => v.os_type === 'unknown').length < values.length) {
- values.splice(unknownIndex, 0, { separator: true })
- }
- let defaultValue = null
- if (option?.config_default && Object.prototype.hasOwnProperty.call(option.config_default, os)) {
- // @ts-ignore
- defaultValue = option.config_default[os]
- }
- return {
- name: os,
- type: 'string',
- enum: values,
- default: defaultValue,
- required: os === 'linux' || (requiresWindowsImage && os === 'windows'),
- }
- })
- return true
- }
- export const defaultGetDestinationEnv = (
- options?: { [prop: string]: any } | null,
- oldOptions?: { [prop: string]: any } | null,
- ): any => {
- const env: any = {}
- const specialOptions = ['execute_now', 'execute_now_options', 'separate_vm', 'skip_os_morphing', 'title', 'minion_pool_id']
- .concat(migrationFields.map(f => f.name))
- .concat(executionOptions.map(o => o.name))
- .concat(migrationImageOsTypes)
- if (!options) {
- return env
- }
- Object.keys(options).forEach(optionName => {
- const value = options[optionName]
- if (specialOptions.find(o => o === optionName) || value == null || value === '') {
- return
- }
- if (Array.isArray(value)) {
- env[optionName] = value
- } else if (typeof value === 'object') {
- const oldValue = oldOptions?.[optionName] || {}
- const mergedValue: any = { ...oldValue, ...value }
- const newValue: any = {}
- Object.keys(mergedValue).forEach(k => {
- if (mergedValue[k] !== null) {
- newValue[k] = mergedValue[k]
- }
- })
- env[optionName] = newValue
- } else {
- env[optionName] = options ? Utils.trim(optionName, value) : null
- }
- })
- return env
- }
- export const defaultGetMigrationImageMap = (
- options: { [prop: string]: any } | null | undefined,
- oldOptions: any,
- migrationImageMapFieldName: string,
- ) => {
- const env: any = {}
- const usableOptions = options
- if (!usableOptions) {
- return env
- }
- const hasMigrationMap = Object.keys(usableOptions).find(k => k === migrationImageMapFieldName)
- if (!hasMigrationMap) {
- return env
- }
- migrationImageOsTypes.forEach(os => {
- let value = usableOptions[migrationImageMapFieldName][os]
- // Make sure the migr. image mapping has all the OSes filled,
- // even if only one OS mapping was updated,
- // ie. don't send just the updated OS map to the server, send them all if one was updated.
- if (!value) {
- value = oldOptions?.[migrationImageMapFieldName]?.[os]
- if (!value) {
- return
- }
- }
- if (!env[migrationImageMapFieldName]) {
- env[migrationImageMapFieldName] = {}
- }
- env[migrationImageMapFieldName][os] = value
- })
- return env
- }
- export default class OptionsSchemaParser {
- static migrationImageMapFieldName = 'migr_image_map'
- static parseSchemaToFields(opts: {
- schema: SchemaProperties,
- schemaDefinitions?: SchemaDefinitions | null,
- dictionaryKey?: string,
- requiresWindowsImage?: boolean,
- }) {
- const {
- schema, schemaDefinitions, dictionaryKey,
- } = opts
- return defaultSchemaToFields(schema, schemaDefinitions, dictionaryKey)
- }
- static sortFields(fields: Field[]) {
- fields.sort((a, b) => {
- if (a.required && !b.required) {
- return -1
- }
- if (!a.required && b.required) {
- return 1
- }
- return a.name.localeCompare(b.name)
- })
- }
- static fillFieldValues(opts: {
- field: Field,
- options: OptionValues[],
- requiresWindowsImage: boolean,
- customFieldName?: string,
- }) {
- const {
- field, options, requiresWindowsImage, customFieldName,
- } = opts
- const option = options.find(f => (customFieldName ? f.name === customFieldName : f.name === field.name))
- if (!option) {
- return
- }
- if (!defaultFillMigrationImageMapValues({
- field,
- option,
- migrationImageMapFieldName: this.migrationImageMapFieldName,
- requiresWindowsImage,
- })) {
- defaultFillFieldValues(field, option)
- }
- }
- static getDestinationEnv(options?: { [prop: string]: any } | null, oldOptions?: any) {
- const env = {
- ...defaultGetDestinationEnv(
- options,
- oldOptions,
- ),
- ...defaultGetMigrationImageMap(
- options,
- oldOptions,
- this.migrationImageMapFieldName,
- ),
- }
- return env
- }
- static getNetworkMap(networkMappings: NetworkMap[] | null | undefined) {
- const payload: any = {}
- if (!networkMappings?.length) {
- return payload
- }
- const hasSecurityGroups = Boolean(networkMappings.find(nm => nm.targetNetwork!.security_groups))
- networkMappings.forEach(mapping => {
- let target
- if (hasSecurityGroups) {
- target = {
- id: mapping.targetNetwork!.id,
- security_groups: mapping.targetSecurityGroups ? mapping.targetSecurityGroups.map(s => (typeof s === 'string' ? s : s.id)) : [],
- }
- } else if (mapping.targetPortKey != null) {
- target = `${mapping.targetNetwork!.id}:${mapping.targetPortKey}`
- } else {
- target = mapping.targetNetwork!.id
- }
- payload[mapping.sourceNic.network_name] = target
- })
- return payload
- }
- static getStorageMap(
- defaultStorage: { value: string | null, busType?: string | null } | undefined,
- storageMap?: StorageMap[] | null,
- configDefault?: string | null,
- ) {
- if (!defaultStorage?.value && !storageMap) {
- return null
- }
- const payload: any = {}
- if (defaultStorage?.value) {
- payload.default = defaultStorage.value
- if (defaultStorage.busType) {
- payload.default += `:${defaultStorage.busType}`
- }
- }
- if (!storageMap) {
- return payload
- }
- storageMap.forEach(mapping => {
- if (mapping.target.id === null && !configDefault) {
- return
- }
- const getDestination = () => {
- let destination = mapping.target.id === null ? configDefault : mapping.target.name
- if (mapping.targetBusType) {
- destination += `:${mapping.targetBusType}`
- }
- return destination
- }
- if (mapping.type === 'backend') {
- if (!payload.backend_mappings) {
- payload.backend_mappings = []
- }
- payload.backend_mappings.push({
- source: mapping.source.storage_backend_identifier,
- destination: getDestination(),
- })
- } else {
- if (!payload.disk_mappings) {
- payload.disk_mappings = []
- }
- payload.disk_mappings.push({
- disk_id: mapping.source.id.toString(),
- destination: getDestination(),
- })
- }
- })
- return payload
- }
- static getUserScripts(
- uploadedUserScripts: InstanceScript[],
- removedUserScripts: InstanceScript[],
- userScriptData: UserScriptData | null | undefined,
- ) {
- const payload: any = userScriptData || {}
- const setPayload = (scripts: InstanceScript[], scriptProp: 'global' | 'instanceId', payloadProp: 'global' | 'instances') => {
- if (!scripts.length) {
- return
- }
- payload[payloadProp] = payload[payloadProp] || {}
- scripts.forEach(script => {
- const scriptValue = script[scriptProp]
- if (!scriptValue) {
- throw new Error(`The uploaded script structure is missing the '${scriptProp}' property`)
- }
- payload[payloadProp][scriptValue] = script.scriptContent
- })
- }
- setPayload(removedUserScripts.filter(s => s.global), 'global', 'global')
- setPayload(removedUserScripts.filter(s => s.instanceId), 'instanceId', 'instances')
- setPayload(uploadedUserScripts.filter(s => s.global), 'global', 'global')
- setPayload(uploadedUserScripts.filter(s => s.instanceId), 'instanceId', 'instances')
- return payload
- }
- }
|