OptionsSchemaPlugin.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. /* eslint-disable no-param-reassign */
  15. import { defaultSchemaToFields } from './ConnectionSchemaPlugin'
  16. import Utils from '../../../utils/ObjectUtils'
  17. import type { Field } from '../../../@types/Field'
  18. import type { OptionValues, StorageMap } from '../../../@types/Endpoint'
  19. import type { SchemaProperties, SchemaDefinitions } from '../../../@types/Schema'
  20. import type { NetworkMap } from '../../../@types/Network'
  21. import type { InstanceScript } from '../../../@types/Instance'
  22. import { executionOptions, migrationFields } from '../../../constants'
  23. const migrationImageOsTypes = ['windows', 'linux']
  24. export const defaultFillFieldValues = (field: Field, option: OptionValues) => {
  25. if (field.type === 'string') {
  26. field.enum = [...option.values]
  27. if (option.config_default) {
  28. field.default = typeof option.config_default === 'string' ? option.config_default : option.config_default.id
  29. }
  30. }
  31. if (field.type === 'array') {
  32. field.enum = [...option.values]
  33. }
  34. if (field.type === 'boolean' && option.config_default != null) {
  35. field.default = typeof option.config_default === 'boolean' ? option.config_default : option.config_default === 'true'
  36. }
  37. }
  38. export const defaultFillMigrationImageMapValues = (
  39. field: Field,
  40. option: OptionValues,
  41. migrationImageMapFieldName: string,
  42. ): boolean => {
  43. if (field.name !== migrationImageMapFieldName) {
  44. return false
  45. }
  46. field.properties = migrationImageOsTypes.map(os => {
  47. const values = (option.values as any)
  48. .filter((v: { os_type: string }) => v.os_type === os || v.os_type === 'unknown')
  49. .sort((v1: { os_type: string }, v2: { os_type: string }) => {
  50. if (v1.os_type === 'unknown' && v2.os_type !== 'unknown') {
  51. return 1
  52. } if (v1.os_type !== 'unknown' && v2.os_type === 'unknown') {
  53. return -1
  54. }
  55. return 0
  56. })
  57. const unknownIndex = values.findIndex((v: { os_type: string }) => v.os_type === 'unknown')
  58. if (unknownIndex > -1 && values.filter((v: { os_type: string }) => v.os_type === 'unknown').length < values.length) {
  59. values.splice(unknownIndex, 0, { separator: true })
  60. }
  61. return {
  62. name: os,
  63. type: 'string',
  64. enum: values,
  65. }
  66. })
  67. return true
  68. }
  69. export const defaultGetDestinationEnv = (
  70. options?: { [prop: string]: any } | null,
  71. oldOptions?: { [prop: string]: any } | null,
  72. ): any => {
  73. const env: any = {}
  74. const specialOptions = ['execute_now', 'separate_vm', 'skip_os_morphing', 'description']
  75. .concat(migrationFields.map(f => f.name))
  76. .concat(executionOptions.map(o => o.name))
  77. .concat(migrationImageOsTypes)
  78. if (!options) {
  79. return env
  80. }
  81. Object.keys(options).forEach(optionName => {
  82. const value = options[optionName]
  83. if (specialOptions.find(o => o === optionName) || value == null || value === '') {
  84. return
  85. }
  86. if (Array.isArray(value)) {
  87. env[optionName] = value
  88. } else if (typeof value === 'object') {
  89. const oldValue = oldOptions?.[optionName] || {}
  90. const mergedValue: any = { ...oldValue, ...value }
  91. const newValue: any = {}
  92. Object.keys(mergedValue).forEach(k => {
  93. if (mergedValue[k] !== null) {
  94. newValue[k] = mergedValue[k]
  95. }
  96. })
  97. env[optionName] = newValue
  98. } else {
  99. env[optionName] = options ? Utils.trim(optionName, value) : null
  100. }
  101. })
  102. return env
  103. }
  104. export const defaultGetMigrationImageMap = (
  105. options: { [prop: string]: any } | null | undefined,
  106. oldOptions: any,
  107. migrationImageMapFieldName: string,
  108. ) => {
  109. const env: any = {}
  110. const usableOptions = options
  111. if (!usableOptions) {
  112. return env
  113. }
  114. const hasMigrationMap = Object.keys(usableOptions).find(k => k === migrationImageMapFieldName)
  115. if (!hasMigrationMap) {
  116. return env
  117. }
  118. migrationImageOsTypes.forEach(os => {
  119. let value = usableOptions[migrationImageMapFieldName][os]
  120. // Make sure the migr. image mapping has all the OSes filled,
  121. // even if only one OS mapping was updated,
  122. // ie. don't send just the updated OS map to the server, send them all if one was updated.
  123. if (!value) {
  124. value = oldOptions?.[migrationImageMapFieldName]?.[os]
  125. if (!value) {
  126. return
  127. }
  128. }
  129. if (!env[migrationImageMapFieldName]) {
  130. env[migrationImageMapFieldName] = {}
  131. }
  132. env[migrationImageMapFieldName][os] = value
  133. })
  134. return env
  135. }
  136. export default class OptionsSchemaParser {
  137. static migrationImageMapFieldName = 'migr_image_map'
  138. static parseSchemaToFields(
  139. schema: SchemaProperties, schemaDefinitions?: SchemaDefinitions | null, dictionaryKey?: string,
  140. ) {
  141. return defaultSchemaToFields(schema, schemaDefinitions, dictionaryKey)
  142. }
  143. static fillFieldValues(field: Field, options: OptionValues[], customFieldName?: string) {
  144. const option = options
  145. .find(f => (customFieldName ? f.name === customFieldName : f.name === field.name))
  146. if (!option) {
  147. return
  148. }
  149. if (!defaultFillMigrationImageMapValues(
  150. field,
  151. option,
  152. this.migrationImageMapFieldName,
  153. )) {
  154. defaultFillFieldValues(field, option)
  155. }
  156. }
  157. static getDestinationEnv(options?: { [prop: string]: any } | null, oldOptions?: any) {
  158. const env = {
  159. ...defaultGetDestinationEnv(
  160. options,
  161. oldOptions,
  162. ),
  163. ...defaultGetMigrationImageMap(
  164. options,
  165. oldOptions,
  166. this.migrationImageMapFieldName,
  167. ),
  168. }
  169. return env
  170. }
  171. static getNetworkMap(networkMappings: NetworkMap[] | null | undefined) {
  172. const payload: any = {}
  173. if (networkMappings && networkMappings.length) {
  174. const hasSecurityGroups = Boolean(networkMappings
  175. .find(nm => nm.targetNetwork.security_groups))
  176. networkMappings.forEach(mapping => {
  177. let target
  178. if (hasSecurityGroups) {
  179. target = {
  180. id: mapping.targetNetwork.id,
  181. security_groups: mapping.targetSecurityGroups
  182. ? mapping.targetSecurityGroups.map(s => (typeof s === 'string' ? s : s.id))
  183. : [],
  184. }
  185. } else {
  186. target = mapping.targetNetwork.id
  187. }
  188. payload[mapping.sourceNic.network_name] = target
  189. })
  190. }
  191. return payload
  192. }
  193. static getStorageMap(
  194. defaultStorage: string | null | undefined,
  195. storageMap: StorageMap[] | null,
  196. configDefault?: string | null,
  197. ) {
  198. if (!defaultStorage && !storageMap) {
  199. return null
  200. }
  201. const payload: any = {}
  202. if (defaultStorage) {
  203. payload.default = defaultStorage
  204. }
  205. if (!storageMap) {
  206. return payload
  207. }
  208. storageMap.forEach(mapping => {
  209. if (mapping.target.id === null && !configDefault) {
  210. return
  211. }
  212. if (mapping.type === 'backend') {
  213. if (!payload.backend_mappings) {
  214. payload.backend_mappings = []
  215. }
  216. payload.backend_mappings.push({
  217. source: mapping.source.storage_backend_identifier,
  218. destination: mapping.target.id === null ? configDefault : mapping.target.name,
  219. })
  220. } else {
  221. if (!payload.disk_mappings) {
  222. payload.disk_mappings = []
  223. }
  224. payload.disk_mappings.push({
  225. disk_id: mapping.source.id.toString(),
  226. destination: mapping.target.id === null ? configDefault : mapping.target.name,
  227. })
  228. }
  229. })
  230. return payload
  231. }
  232. static getUserScripts(uploadedUserScripts: InstanceScript[]) {
  233. const payload: any = {}
  234. const globalScripts = uploadedUserScripts.filter(s => s.global)
  235. if (globalScripts.length) {
  236. payload.global = {}
  237. globalScripts.forEach(script => {
  238. payload.global[script.global || ''] = script.scriptContent
  239. })
  240. }
  241. const instanceScripts = uploadedUserScripts.filter(s => s.instanceId)
  242. if (instanceScripts.length) {
  243. payload.instances = {}
  244. instanceScripts.forEach(script => {
  245. payload.instances[script.instanceId || ''] = script.scriptContent
  246. })
  247. }
  248. return payload
  249. }
  250. }