DeploymentSource.ts 12 KB

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