Skip to content

Commit 1a3d774

Browse files
duarte-castanoDuarte Castaño
and
Duarte Castaño
authored
Parallel deployments (#81)
* first review for parallel deploymnets * deprecate pkg_resources package * include parallel deployment option * added new functionalities * update for package release * update test python version and changelog --------- Co-authored-by: Duarte Castaño <duarte.castañ[email protected]>
1 parent 3e4c9cf commit 1a3d774

12 files changed

+393
-69
lines changed

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,32 @@
77
[//]: # (Features)
88
[//]: # (BREAKING CHANGES)
99

10+
## Nov 14th, 2024
11+
12+
### Parallel Deployments
13+
14+
The following scripts have been updated to enable creating and running parallel deployment plans:
15+
16+
* `deploy_latest_tags_to_target_env.py`
17+
* `deploy_package_to_target_env.py`
18+
* `deploy_tags_to_target_env_with_manifest.py`
19+
20+
To enable this feature, use the following parameter:
21+
22+
* `--allow_parallel_deployments`: Skips LifeTime validation for active deployment plans.
23+
24+
### Enhanced Pipeline Operations
25+
26+
New pipeline scripts have been added to streamline operations related to manifest files:
27+
28+
* `generate_manifest_file.py`: Generates a trigger manifest file.
29+
* `validate_manifest_apps_exist_in_target_env.py`: Verifies that manifest applications exist in the target environment.
30+
31+
### Updated Package Dependencies
32+
33+
* Updated `requests` dependency to version 2.32.2
34+
* Added `packaging` dependency, version 24.1
35+
1036
## May 16th, 2024
1137

1238
### Download Application Source Code

INSTALL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ To install all the dependencies you'll need:
1717

1818
* **Dependencies**
1919
* To install the dependencies needed, you just need to install the requirements with pip.
20-
* On the root dir run: `pip install -q -I -r cd_pipelines/requirements.txt`
20+
* On the root dir run: `pip install -q -I -r cd_pipelines/requirements.txt`
21+

build-pipeline.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ jobs:
2626
matrix:
2727
Python38:
2828
python.version: '3.8'
29-
Python311:
30-
python.version: '3.11'
29+
Python312:
30+
python.version: '3.12'
3131
maxParallel: 2
3232

3333
steps:

build-requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
python-dateutil==2.9.0.post0
2-
requests==2.31.0
2+
requests==2.32.2
33
unittest-xml-reporting==3.2.0
44
xunitparser==1.3.4
55
toposort==1.10
6-
python-dotenv==1.0.1
6+
python-dotenv==1.0.1
7+
packaging==24.1

outsystems/pipeline/deploy_latest_tags_to_target_env.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
import os
44
import argparse
5-
from pkg_resources import parse_version
5+
from packaging.version import Version
66
from time import sleep
77

88
# Workaround for Jenkins:
@@ -105,10 +105,10 @@ def check_if_can_deploy(artifact_dir: str, lt_endpoint: str, lt_api_version: str
105105
# The version is not the one deployed -> need to compare the version tag
106106
app_in_env_data = get_application_version(artifact_dir, lt_endpoint, lt_token, False, app_in_env["BaseApplicationVersionKey"], app_key=app["Key"])
107107
# If the version in the environment is bigger than the one in the manifest -> stale pipeline -> abort
108-
if parse_version(app_in_env_data["Version"]) > parse_version(app["Version"]):
108+
if Version(app_in_env_data["Version"]) > Version(app["Version"]):
109109
print("The deployment manifest is stale. The Application {} needs to be deployed with version {} but then environment {} has the version {}.\nReason: VersionTag is inferior to the VersionTag already deployed.\nAborting the pipeline.".format(app["Name"], app["Version"], env_name, app_in_env_data["Version"]), flush=True)
110110
sys.exit(1)
111-
elif parse_version(app_in_env_data["Version"]) == parse_version(app["Version"]):
111+
elif Version(app_in_env_data["Version"]) == Version(app["Version"]):
112112
print("Skipping application {} with version {}, since it's already deployed in {} environment.\nReason: VersionTag is equal.".format(app["Name"], app["Version"], env_name), flush=True)
113113
else:
114114
# Generated app_keys for deployment plan based on the running version
@@ -122,7 +122,7 @@ def check_if_can_deploy(artifact_dir: str, lt_endpoint: str, lt_api_version: str
122122
return app_keys
123123

124124

125-
def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: str, lt_api_version: int, lt_token: str, source_env: str, dest_env: str, apps: list, dep_manifest: list, dep_note: str):
125+
def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: str, lt_api_version: int, lt_token: str, source_env: str, dest_env: str, apps: list, dep_manifest: list, dep_note: str, allow_parallel_deployments: bool):
126126

127127
app_data_list = [] # will contain the applications to deploy details from LT
128128
to_deploy_app_keys = [] # will contain the app keys for the apps tagged
@@ -167,17 +167,18 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
167167
raise NotImplementedError("Please make sure the API version is compatible with the module.")
168168
print("Creating deployment plan from {} to {} including applications: {} ({}).".format(source_env, dest_env, to_deploy_app_names, to_deploy_app_info), flush=True)
169169

170-
wait_counter = 0
171-
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key)
172-
while len(deployments) > 0:
173-
if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS):
174-
print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True)
175-
sys.exit(1)
176-
sleep_value = get_configuration_value("SLEEP_PERIOD_IN_SECS", SLEEP_PERIOD_IN_SECS)
177-
sleep(sleep_value)
178-
wait_counter += sleep_value
179-
print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True)
170+
if not allow_parallel_deployments:
171+
wait_counter = 0
180172
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key)
173+
while len(deployments) > 0:
174+
if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS):
175+
print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True)
176+
sys.exit(1)
177+
sleep_value = get_configuration_value("SLEEP_PERIOD_IN_SECS", SLEEP_PERIOD_IN_SECS)
178+
sleep(sleep_value)
179+
wait_counter += sleep_value
180+
print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True)
181+
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key)
181182

182183
# LT is free to deploy
183184
# Send the deployment plan and grab the key
@@ -262,7 +263,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
262263
help="(optional) Manifest file path, used to promote the same application versions throughout the pipeline execution.")
263264
parser.add_argument("-cf", "--config_file", type=str,
264265
help="Config file path. Contains configuration values to override the default ones.")
265-
266+
parser.add_argument("-p", "--allow_parallel_deployments", action='store_true',
267+
help="Skip LifeTime validation for active deployment plans.")
266268
args = parser.parse_args()
267269

268270
# Load config file if exists
@@ -302,6 +304,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
302304
manifest_file = None
303305
# Parse Deployment Message
304306
dep_note = args.deploy_msg
307+
# Parse Allow Parallel Deployments
308+
allow_parallel_deployments = args.allow_parallel_deployments
305309

306310
# Calls the main script
307-
main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, source_env, dest_env, apps, manifest_file, dep_note)
311+
main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, source_env, dest_env, apps, manifest_file, dep_note, allow_parallel_deployments)

outsystems/pipeline/deploy_package_to_target_env.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,26 @@
3232

3333

3434
# ############################################################# SCRIPT ##############################################################
35-
def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: str, lt_api_version: int, lt_token: str, dest_env_label: str, force_two_step_deployment: bool, package_path: str):
35+
def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: str, lt_api_version: int, lt_token: str, dest_env_label: str, force_two_step_deployment: bool, package_path: str, allow_parallel_deployments: bool):
3636

3737
# Builds the LifeTime endpoint
3838
lt_endpoint = build_lt_endpoint(lt_http_proto, lt_url, lt_api_endpoint, lt_api_version)
3939

4040
# Gets the environment key for the destination environment
4141
dest_env_key = get_environment_key(artifact_dir, lt_endpoint, lt_token, dest_env_label)
4242

43-
wait_counter = 0
44-
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key)
45-
while len(deployments) > 0:
46-
if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS):
47-
print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True)
48-
sys.exit(1)
49-
sleep_value = get_configuration_value("SLEEP_PERIOD_IN_SECS", SLEEP_PERIOD_IN_SECS)
50-
sleep(sleep_value)
51-
wait_counter += sleep_value
52-
print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True)
43+
if not allow_parallel_deployments:
44+
wait_counter = 0
5345
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key)
46+
while len(deployments) > 0:
47+
if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS):
48+
print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True)
49+
sys.exit(1)
50+
sleep_value = get_configuration_value("SLEEP_PERIOD_IN_SECS", SLEEP_PERIOD_IN_SECS)
51+
sleep(sleep_value)
52+
wait_counter += sleep_value
53+
print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True)
54+
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key)
5455

5556
# LT is free to deploy
5657
# Validate if file has OutSystems package extension
@@ -156,6 +157,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
156157
help="Force the execution of the 2-Step deployment.")
157158
parser.add_argument("-cf", "--config_file", type=str,
158159
help="Config file path. Contains configuration values to override the default ones.")
160+
parser.add_argument("-p", "--allow_parallel_deployments", action='store_true',
161+
help="Skip LifeTime validation for active deployment plans.")
159162

160163
args = parser.parse_args()
161164

@@ -187,6 +190,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
187190
package_path = args.package_path
188191
# Parse Force Two-step Deployment flag
189192
force_two_step_deployment = args.force_two_step_deployment
193+
# Parse Allow Parallel Deployments
194+
allow_parallel_deployments = args.allow_parallel_deployments
190195

191196
# Calls the main script
192-
main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, dest_env_label, force_two_step_deployment, package_path)
197+
main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, dest_env_label, force_two_step_deployment, package_path, allow_parallel_deployments)

outsystems/pipeline/deploy_tags_to_target_env_with_manifest.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
import os
44
import argparse
5-
from pkg_resources import parse_version
5+
from packaging.version import Version
66
from time import sleep
77
import json
88

@@ -97,7 +97,7 @@ def check_if_can_deploy(artifact_dir: str, lt_endpoint: str, lt_api_version: str
9797
# The version is not the one deployed -> need to compare the version tag
9898
app_in_env_data = get_application_version(artifact_dir, lt_endpoint, lt_token, False, app_in_env["BaseApplicationVersionKey"], app_key=app["Key"])
9999
# If the version in the target environment has the same version number -> skip deployment
100-
if parse_version(app_in_env_data["Version"]) == parse_version(app["Version"]):
100+
if Version(app_in_env_data["Version"]) == Version(app["Version"]):
101101
print("Skipping application {} with version {}, since it's already deployed in {} environment.\nReason: VersionTag is equal.".format(app["Name"], app["Version"], env_name), flush=True)
102102
else:
103103
# Generated app_keys for deployment plan based on the target version
@@ -127,7 +127,7 @@ def check_if_can_deploy(artifact_dir: str, lt_endpoint: str, lt_api_version: str
127127
return app_keys
128128

129129

130-
def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: str, lt_api_version: int, lt_token: str, source_env_label: str, dest_env_label: str, include_test_apps: bool, trigger_manifest: dict, force_two_step_deployment: bool, include_deployment_zones: bool):
130+
def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: str, lt_api_version: int, lt_token: str, source_env_label: str, dest_env_label: str, include_test_apps: bool, trigger_manifest: dict, force_two_step_deployment: bool, include_deployment_zones: bool, allow_parallel_deployments: bool):
131131

132132
app_data_list = [] # will contain the applications to deploy details from LT
133133
to_deploy_app_keys = [] # will contain the app keys for the apps tagged
@@ -168,17 +168,18 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
168168
raise NotImplementedError("Please make sure the API version is compatible with the module.")
169169
print("Creating deployment plan from {} (Label: {}) to {} (Label: {}) including applications: {} ({}).".format(src_env_tuple[0], source_env_label, dest_env_tuple[0], dest_env_label, to_deploy_app_names, to_deploy_app_info), flush=True)
170170

171-
wait_counter = 0
172-
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1])
173-
while len(deployments) > 0:
174-
if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS):
175-
print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True)
176-
sys.exit(1)
177-
sleep_value = get_configuration_value("SLEEP_PERIOD_IN_SECS", SLEEP_PERIOD_IN_SECS)
178-
sleep(sleep_value)
179-
wait_counter += sleep_value
180-
print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True)
171+
if not allow_parallel_deployments:
172+
wait_counter = 0
181173
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1])
174+
while len(deployments) > 0:
175+
if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS):
176+
print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True)
177+
sys.exit(1)
178+
sleep_value = get_configuration_value("SLEEP_PERIOD_IN_SECS", SLEEP_PERIOD_IN_SECS)
179+
sleep(sleep_value)
180+
wait_counter += sleep_value
181+
print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True)
182+
deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1])
182183

183184
# LT is free to deploy
184185
# Send the deployment plan and grab the key
@@ -288,6 +289,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
288289
help="Flag that indicates if deployment zone selection is included in the deployment plan. Applicable to self-managed environments only.")
289290
parser.add_argument("-cf", "--config_file", type=str,
290291
help="Config file path. Contains configuration values to override the default ones.")
292+
parser.add_argument("-p", "--allow_parallel_deployments", action='store_true',
293+
help="Skip LifeTime validation for active deployment plans.")
291294

292295
args = parser.parse_args()
293296

@@ -336,5 +339,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st
336339
# Parse Include Deployment Zones flag
337340
include_deployment_zones = args.include_deployment_zones
338341

342+
# Parse Allow Parallel Deployments
343+
allow_parallel_deployments = args.allow_parallel_deployments
344+
339345
# Calls the main script
340-
main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, source_env_label, dest_env_label, include_test_apps, trigger_manifest, force_two_step_deployment, include_deployment_zones)
346+
main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, source_env_label, dest_env_label, include_test_apps, trigger_manifest, force_two_step_deployment, include_deployment_zones, allow_parallel_deployments)

0 commit comments

Comments
 (0)