DeploymentSource.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /*
  2. Copyright (C) 2024 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. import {
  15. DeploymentItem,
  16. DeploymentItemDetails,
  17. DeploymentItemOptions,
  18. UserScriptData,
  19. } from "@src/@types/MainItem";
  20. import { ProgressUpdate, Task } from "@src/@types/Task";
  21. import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions";
  22. import { OptionsSchemaPlugin } from "@src/plugins";
  23. import DefaultOptionsSchemaPlugin from "@src/plugins/default/OptionsSchemaPlugin";
  24. import Api from "@src/utils/ApiCaller";
  25. import configLoader from "@src/utils/Config";
  26. import { sortTasks } from "./TransferSource";
  27. import type { InstanceScript } from "@src/@types/Instance";
  28. import type { Field } from "@src/@types/Field";
  29. import type { NetworkMap } from "@src/@types/Network";
  30. import type { Endpoint, StorageMap } from "@src/@types/Endpoint";
  31. class DeploymentSourceUtils {
  32. static sortTaskUpdates(updates: ProgressUpdate[]) {
  33. if (!updates) {
  34. return;
  35. }
  36. updates.sort((a, b) => {
  37. const sortNull = !a && b ? 1 : a && !b ? -1 : !a && !b ? 0 : false;
  38. if (sortNull !== false) {
  39. return sortNull;
  40. }
  41. return a.index - b.index;
  42. });
  43. }
  44. static sortDeployments(deployments: any[]) {
  45. deployments.sort(
  46. (a: any, b: any) =>
  47. new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
  48. );
  49. deployments.forEach((deployment: { tasks: Task[] }) => {
  50. sortTasks(deployment.tasks, DeploymentSourceUtils.sortTaskUpdates);
  51. });
  52. }
  53. }
  54. class DeploymentSource {
  55. async getDeployments(skipLog?: boolean): Promise<DeploymentItem[]> {
  56. const response = await Api.send({
  57. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`,
  58. skipLog,
  59. });
  60. const deployments = response.data.deployments;
  61. DeploymentSourceUtils.sortDeployments(deployments);
  62. return deployments;
  63. }
  64. async getDeployment(
  65. deploymentId: string,
  66. skipLog?: boolean
  67. ): Promise<DeploymentItemDetails> {
  68. const response = await Api.send({
  69. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments/${deploymentId}`,
  70. skipLog,
  71. cancelId: deploymentId,
  72. });
  73. const deployment = response.data.deployment;
  74. sortTasks(deployment.tasks, DeploymentSourceUtils.sortTaskUpdates);
  75. return deployment;
  76. }
  77. async recreateFullCopy(
  78. deployment: DeploymentItemOptions
  79. ): Promise<DeploymentItem> {
  80. const {
  81. // eslint-disable-next-line @typescript-eslint/naming-convention
  82. origin_endpoint_id,
  83. destination_endpoint_id,
  84. destination_environment,
  85. // eslint-disable-next-line @typescript-eslint/naming-convention
  86. network_map,
  87. instances,
  88. storage_mappings,
  89. notes,
  90. destination_minion_pool_id,
  91. // eslint-disable-next-line @typescript-eslint/naming-convention
  92. origin_minion_pool_id,
  93. instance_osmorphing_minion_pool_mappings,
  94. } = deployment;
  95. const payload: any = {
  96. deployment: {
  97. origin_endpoint_id,
  98. destination_endpoint_id,
  99. destination_environment,
  100. network_map,
  101. instances,
  102. storage_mappings,
  103. notes,
  104. destination_minion_pool_id,
  105. origin_minion_pool_id,
  106. instance_osmorphing_minion_pool_mappings,
  107. },
  108. };
  109. if (deployment.skip_os_morphing != null) {
  110. payload.deployment.skip_os_morphing = deployment.skip_os_morphing;
  111. }
  112. if (deployment.source_environment) {
  113. payload.deployment.source_environment = deployment.source_environment;
  114. }
  115. payload.deployment.shutdown_instances = Boolean(
  116. deployment.shutdown_instances
  117. );
  118. // payload.deployment.replication_count = deployment.replication_count || 2;
  119. const response = await Api.send({
  120. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`,
  121. method: "POST",
  122. data: payload,
  123. });
  124. return response.data.deployment;
  125. }
  126. async recreate(opts: {
  127. sourceEndpoint: Endpoint;
  128. destEndpoint: Endpoint;
  129. instanceNames: string[];
  130. destEnv: { [prop: string]: any } | null;
  131. updatedDestEnv: { [prop: string]: any } | null;
  132. sourceEnv?: { [prop: string]: any } | null;
  133. updatedSourceEnv?: { [prop: string]: any } | null;
  134. storageMappings?: { [prop: string]: any } | null;
  135. updatedStorageMappings: StorageMap[] | null;
  136. defaultStorage?: { value: string | null; busType?: string | null };
  137. updatedDefaultStorage?: { value: string | null; busType?: string | null };
  138. networkMappings?: any;
  139. updatedNetworkMappings: NetworkMap[] | null;
  140. defaultSkipOsMorphing: boolean | null;
  141. // replicationCount?: number | null;
  142. deployment: DeploymentItemDetails;
  143. uploadedScripts: InstanceScript[];
  144. removedScripts: InstanceScript[];
  145. }): Promise<DeploymentItemDetails> {
  146. const getValue = (fieldName: string): string | null => {
  147. const updatedDestEnv =
  148. opts.updatedDestEnv && opts.updatedDestEnv[fieldName];
  149. return updatedDestEnv != null
  150. ? updatedDestEnv
  151. : opts.destEnv && opts.destEnv[fieldName];
  152. };
  153. const sourceParser = OptionsSchemaPlugin.for(opts.sourceEndpoint.type);
  154. const destParser = OptionsSchemaPlugin.for(opts.destEndpoint.type);
  155. const payload: any = {};
  156. payload.deployment = {
  157. origin_endpoint_id: opts.sourceEndpoint.id,
  158. destination_endpoint_id: opts.destEndpoint.id,
  159. shutdown_instances: Boolean(
  160. opts.updatedDestEnv && opts.updatedDestEnv.shutdown_instances
  161. ),
  162. // replication_count:
  163. // (opts.updatedDestEnv && opts.updatedDestEnv.replication_count) ||
  164. // opts.replicationCount ||
  165. // 2,
  166. instances: opts.instanceNames,
  167. notes: opts.updatedDestEnv?.title || opts.deployment.notes || "",
  168. };
  169. const skipOsMorphingValue = getValue("skip_os_morphing");
  170. if (skipOsMorphingValue != null) {
  171. payload.deployment.skip_os_morphing = skipOsMorphingValue;
  172. } else if (opts.defaultSkipOsMorphing != null) {
  173. payload.deployment.skip_os_morphing = opts.defaultSkipOsMorphing;
  174. }
  175. if (
  176. opts.networkMappings ||
  177. (opts.updatedNetworkMappings && opts.updatedNetworkMappings.length)
  178. ) {
  179. payload.deployment.network_map = {
  180. ...opts.networkMappings,
  181. ...destParser.getNetworkMap(opts.updatedNetworkMappings),
  182. };
  183. }
  184. if (
  185. (opts.storageMappings && Object.keys(opts.storageMappings).length) ||
  186. (opts.updatedStorageMappings && opts.updatedStorageMappings.length)
  187. ) {
  188. payload.deployment.storage_mappings = {
  189. ...opts.storageMappings,
  190. ...destParser.getStorageMap(
  191. opts.updatedDefaultStorage || opts.defaultStorage,
  192. opts.updatedStorageMappings
  193. ),
  194. };
  195. }
  196. const { deployment } = opts;
  197. const sourceEnv: any = {
  198. ...opts.sourceEnv,
  199. };
  200. const updatedSourceEnv = opts.updatedSourceEnv
  201. ? sourceParser.getDestinationEnv(opts.updatedSourceEnv)
  202. : {};
  203. const sourceMinionPoolId =
  204. opts?.updatedSourceEnv?.minion_pool_id ||
  205. deployment.origin_minion_pool_id;
  206. if (sourceMinionPoolId) {
  207. payload.deployment.origin_minion_pool_id = sourceMinionPoolId;
  208. }
  209. payload.deployment.source_environment = {
  210. ...sourceEnv,
  211. ...updatedSourceEnv,
  212. };
  213. const destEnv: any = {
  214. ...opts.destEnv,
  215. };
  216. const updatedDestEnv = opts.updatedDestEnv
  217. ? sourceParser.getDestinationEnv(opts.updatedDestEnv)
  218. : {};
  219. const destMinionPoolId =
  220. opts?.updatedDestEnv?.minion_pool_id ||
  221. deployment.destination_minion_pool_id;
  222. if (destMinionPoolId) {
  223. payload.deployment.destination_minion_pool_id = destMinionPoolId;
  224. }
  225. const updatedDestEnvMappings =
  226. updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {};
  227. const oldMappings =
  228. deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {};
  229. const mergedMappings = { ...oldMappings, ...updatedDestEnvMappings };
  230. if (Object.keys(mergedMappings).length) {
  231. const newMappings: any = {};
  232. Object.keys(mergedMappings).forEach(k => {
  233. if (mergedMappings[k] !== null) {
  234. newMappings[k] = mergedMappings[k];
  235. }
  236. });
  237. payload.deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] =
  238. newMappings;
  239. }
  240. delete updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS];
  241. payload.deployment.destination_environment = {
  242. ...destEnv,
  243. ...updatedDestEnv,
  244. };
  245. if (
  246. opts.uploadedScripts?.length ||
  247. opts.removedScripts?.length ||
  248. deployment.user_scripts
  249. ) {
  250. payload.deployment.user_scripts =
  251. new DefaultOptionsSchemaPlugin().getUserScripts(
  252. opts.uploadedScripts || [],
  253. opts.removedScripts || [],
  254. deployment.user_scripts
  255. );
  256. }
  257. const response = await Api.send({
  258. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`,
  259. method: "POST",
  260. data: payload,
  261. });
  262. return response.data.deployment;
  263. }
  264. async cancel(deploymentId: string, force?: boolean | null): Promise<string> {
  265. const data: any = { cancel: null };
  266. if (force) {
  267. data.cancel = { force: true };
  268. }
  269. await Api.send({
  270. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments/${deploymentId}/actions`,
  271. method: "POST",
  272. data,
  273. });
  274. return deploymentId;
  275. }
  276. async delete(deploymentId: string): Promise<string> {
  277. await Api.send({
  278. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments/${deploymentId}`,
  279. method: "DELETE",
  280. });
  281. return deploymentId;
  282. }
  283. async deployTransfer(opts: {
  284. transferId: string;
  285. options: Field[];
  286. uploadedUserScripts: InstanceScript[];
  287. removedUserScripts: InstanceScript[];
  288. userScriptData: UserScriptData | null | undefined;
  289. minionPoolMappings: { [instance: string]: string };
  290. }): Promise<DeploymentItem> {
  291. const {
  292. transferId: transferId,
  293. options,
  294. uploadedUserScripts,
  295. removedUserScripts,
  296. userScriptData,
  297. minionPoolMappings,
  298. } = opts;
  299. const payload: any = {
  300. deployment: {
  301. transfer_id: transferId,
  302. },
  303. };
  304. options.forEach(o => {
  305. payload.deployment[o.name] = o.value || o.default || false;
  306. });
  307. if (
  308. uploadedUserScripts.length ||
  309. removedUserScripts.length ||
  310. userScriptData
  311. ) {
  312. payload.deployment.user_scripts =
  313. new DefaultOptionsSchemaPlugin().getUserScripts(
  314. uploadedUserScripts,
  315. removedUserScripts,
  316. userScriptData
  317. );
  318. }
  319. if (Object.keys(minionPoolMappings).length) {
  320. const newMappings: any = {};
  321. Object.keys(minionPoolMappings).forEach(k => {
  322. if (minionPoolMappings[k] !== null) {
  323. newMappings[k] = minionPoolMappings[k];
  324. }
  325. });
  326. payload.deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] =
  327. newMappings;
  328. } else {
  329. payload.deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = null;
  330. }
  331. const response = await Api.send({
  332. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`,
  333. method: "POST",
  334. data: payload,
  335. });
  336. return response.data.deployment;
  337. }
  338. }
  339. export default new DeploymentSource();