OptionsSchemaPlugin.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. imageSuffix: string,
  43. ): boolean => {
  44. if (field.name !== migrationImageMapFieldName) {
  45. return false
  46. }
  47. field.properties = migrationImageOsTypes.map(os => {
  48. const values = (option.values as any)
  49. .filter((v: { os_type: string }) => v.os_type === os || v.os_type === 'unknown')
  50. .sort((v1: { os_type: string }, v2: { os_type: string }) => {
  51. if (v1.os_type === 'unknown' && v2.os_type !== 'unknown') {
  52. return 1
  53. } if (v1.os_type !== 'unknown' && v2.os_type === 'unknown') {
  54. return -1
  55. }
  56. return 0
  57. })
  58. const unknownIndex = values.findIndex((v: { os_type: string }) => v.os_type === 'unknown')
  59. if (unknownIndex > -1 && values.filter((v: { os_type: string }) => v.os_type === 'unknown').length < values.length) {
  60. values.splice(unknownIndex, 0, { separator: true })
  61. }
  62. return {
  63. name: `${os}${imageSuffix}`,
  64. type: 'string',
  65. enum: values,
  66. }
  67. })
  68. return true
  69. }
  70. export const defaultGetDestinationEnv = (
  71. options?: { [prop: string]: any } | null,
  72. oldOptions?: { [prop: string]: any } | null,
  73. imageSuffix?: string,
  74. ): any => {
  75. const env: any = {}
  76. const specialOptions = ['execute_now', 'separate_vm', 'skip_os_morphing', 'description']
  77. .concat(migrationFields.map(f => f.name))
  78. .concat(executionOptions.map(o => o.name))
  79. .concat(migrationImageOsTypes.map(o => `${o}${imageSuffix}`))
  80. if (!options) {
  81. return env
  82. }
  83. Object.keys(options).forEach(optionName => {
  84. if (specialOptions.find(o => o === optionName) || !options || options[optionName] == null || options[optionName] === '') {
  85. return
  86. }
  87. if (typeof options[optionName] === 'object') {
  88. const oldOption = oldOptions?.[optionName] || {}
  89. env[optionName] = {
  90. ...oldOption,
  91. ...options[optionName],
  92. }
  93. } else {
  94. env[optionName] = options ? Utils.trim(optionName, options[optionName]) : null
  95. }
  96. })
  97. return env
  98. }
  99. export const defaultGetMigrationImageMap = (
  100. options: { [prop: string]: any } | null | undefined,
  101. oldOptions: any,
  102. migrationImageMapFieldName: string,
  103. imageSuffix: string,
  104. ) => {
  105. const env: any = {}
  106. const usableOptions = options
  107. if (!usableOptions) {
  108. return env
  109. }
  110. const hasMigrationMap = Object.keys(usableOptions).find(k => k === migrationImageMapFieldName)
  111. if (!hasMigrationMap) {
  112. return env
  113. }
  114. migrationImageOsTypes.forEach(os => {
  115. let value = usableOptions[migrationImageMapFieldName][`${os}${imageSuffix}`]
  116. // Make sure the migr. image mapping has all the OSes filled,
  117. // even if only one OS mapping was updated,
  118. // ie. don't send just the updated OS map to the server, send them all if one was updated.
  119. if (!value) {
  120. value = oldOptions?.[migrationImageMapFieldName]?.[`${os}${imageSuffix}`]
  121. if (!value) {
  122. return
  123. }
  124. }
  125. if (!env[migrationImageMapFieldName]) {
  126. env[migrationImageMapFieldName] = {}
  127. }
  128. env[migrationImageMapFieldName][`${os}${imageSuffix}`] = value
  129. })
  130. return env
  131. }
  132. export default class OptionsSchemaParser {
  133. static migrationImageMapFieldName = 'migr_image_map'
  134. static imageSuffix = '_os_image'
  135. static parseSchemaToFields(
  136. schema: SchemaProperties, schemaDefinitions?: SchemaDefinitions | null, dictionaryKey?: string,
  137. ) {
  138. return defaultSchemaToFields(schema, schemaDefinitions, dictionaryKey)
  139. }
  140. static fillFieldValues(field: Field, options: OptionValues[], customFieldName?: string) {
  141. const option = options
  142. .find(f => (customFieldName ? f.name === customFieldName : f.name === field.name))
  143. if (!option) {
  144. return
  145. }
  146. if (!defaultFillMigrationImageMapValues(
  147. field,
  148. option,
  149. this.migrationImageMapFieldName,
  150. this.imageSuffix,
  151. )) {
  152. defaultFillFieldValues(field, option)
  153. }
  154. }
  155. static getDestinationEnv(options?: { [prop: string]: any } | null, oldOptions?: any) {
  156. const env = {
  157. ...defaultGetDestinationEnv(
  158. options,
  159. oldOptions,
  160. this.imageSuffix,
  161. ),
  162. ...defaultGetMigrationImageMap(
  163. options,
  164. oldOptions,
  165. this.migrationImageMapFieldName,
  166. this.imageSuffix,
  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.instanceName)
  242. if (instanceScripts.length) {
  243. payload.instances = {}
  244. instanceScripts.forEach(script => {
  245. payload.instances[script.instanceName || ''] = script.scriptContent
  246. })
  247. }
  248. return payload
  249. }
  250. }