diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e5f7a..c7046a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ [//]: # (Features) [//]: # (BREAKING CHANGES) +## August 15th, 2025 + +### Parallel deployment improvements + +The following scripts have been updated to write the deployment plan key of the created deployment to an output variable called DEPLOYMENT_PLAN_KEY for use in subsequent pipeline tasks and jobs. + +* `deploy_latest_tags_to_target_env.py` +* `deploy_package_to_target_env.py` +* `deploy_tags_to_target_env_with_manifest.py` + +This can be used to fill a new optional parameter `--deployment_plan_key` in the following script: + +* `continue_deployment_to_target_env.py` + +This ensures you can continue a specific deployment plan for a running deployment, instead of the first one returned by the LifeTime API. + ## Nov 14th, 2024 ### Parallel Deployments diff --git a/examples/azure_devops/multistage/CD-AzureAgent.yml b/examples/azure_devops/multistage/CD-AzureAgent.yml index 273a8d6..9684ede 100644 --- a/examples/azure_devops/multistage/CD-AzureAgent.yml +++ b/examples/azure_devops/multistage/CD-AzureAgent.yml @@ -82,6 +82,7 @@ stages: # Stage: Regression Testing # ****************************************************************** - stage: regression_testing + environment: $(Environment.Regression.Key) displayName: Regression Testing jobs: @@ -93,7 +94,6 @@ stages: # ****************************************************************** - template: ./jobs/LifeTimeDeploymentJob.yaml parameters: - EnvironmentKey: $(Environment.Regression.Key) SourceEnvironmentLabel: $(Environment.Development.Label) DestinationEnvironmentLabel: $(Environment.Regression.Label) IncludeTestApplications: true @@ -109,6 +109,7 @@ stages: # Stage: Release Acceptance # ****************************************************************** - stage: release_acceptance + environment: $(Environment.Acceptance.Key) displayName: Release Acceptance jobs: @@ -119,7 +120,6 @@ stages: # ****************************************************************** - template: ./jobs/LifeTimeDeploymentJob.yaml parameters: - EnvironmentKey: $(Environment.Acceptance.Key) SourceEnvironmentLabel: $(Environment.Regression.Label) DestinationEnvironmentLabel: $(Environment.Acceptance.Label) @@ -134,6 +134,7 @@ stages: # Stage: Dry-Run # ****************************************************************** - stage: dry_run + environment: $(Environment.PreProduction.Key) displayName: Dry-Run jobs: @@ -144,7 +145,6 @@ stages: # ****************************************************************** - template: ./jobs/LifeTimeDeploymentJob.yaml parameters: - EnvironmentKey: $(Environment.PreProduction.Key) SourceEnvironmentLabel: $(Environment.Acceptance.Label) DestinationEnvironmentLabel: $(Environment.PreProduction.Label) @@ -152,6 +152,7 @@ stages: # Stage: Go-Live # ****************************************************************** - stage: go_live + environment: $(Environment.Production.Key) displayName: Go-Live jobs: @@ -162,7 +163,6 @@ stages: # ****************************************************************** - template: ./jobs/LifeTimeDeploymentJob.yaml parameters: - EnvironmentKey: $(Environment.Production.Key) SourceEnvironmentLabel: $(Environment.PreProduction.Label) DestinationEnvironmentLabel: $(Environment.Production.Label) # To enable 2stage-deploy on this environment uncomment the line below diff --git a/examples/azure_devops/multistage/jobs/LifeTimeDeploymentJob.yaml b/examples/azure_devops/multistage/jobs/LifeTimeDeploymentJob.yaml index 3efcc86..fae8d4c 100644 --- a/examples/azure_devops/multistage/jobs/LifeTimeDeploymentJob.yaml +++ b/examples/azure_devops/multistage/jobs/LifeTimeDeploymentJob.yaml @@ -8,8 +8,6 @@ # Declare parameters # ****************************************************************** parameters: -- name: EnvironmentKey # Environment key (in Azure DevOps) - type: string - name: SourceEnvironmentLabel # Source Environment (in manifest) type: string - name: DestinationEnvironmentLabel # Destination Environment (in manifest) @@ -31,68 +29,66 @@ jobs: # ****************************************************************** # Deploy application tags list to target LifeTime environment # ****************************************************************** -- deployment: lifetime_deployment +- job: lifetime_deployment displayName: LifeTime Deployment - environment: ${{ parameters.EnvironmentKey }} - strategy: - runOnce: - deploy: - steps: - - download: current # Download current pipeline artifacts - - template: ../tasks/InstallPythonPackage.yaml # Install python package - - # ****************************************************************** - # Step: Deploy to target environment (using manifest) - # ****************************************************************** - # Deploy application list to target environment using manifest - # ****************************************************************** - - ${{ if eq(parameters.IncludeTestApplications, true) }}: - - script: > - python -m outsystems.pipeline.deploy_tags_to_target_env_with_manifest - --artifacts "$(Artifacts.Folder)" - --lt_url $(LifeTime.Hostname) - --lt_token $(LifeTime.ServiceAccountToken) - --lt_api_version $(LifeTime.APIVersion) - --source_env_label "${{ parameters.SourceEnvironmentLabel }}" - --destination_env_label "${{ parameters.DestinationEnvironmentLabel }}" - --include_test_apps - --manifest_file "$(Pipeline.Workspace)/$(Manifest.Folder)/$(Manifest.File)" - displayName: 'Deploy to ${{ parameters.DestinationEnvironmentLabel }} environment' + steps: + - download: current # Download current pipeline artifacts + - template: ../tasks/InstallPythonPackage.yaml # Install python package + + # ****************************************************************** + # Step: Deploy to target environment (using manifest) + # ****************************************************************** + # Deploy application list to target environment using manifest + # ****************************************************************** + - ${{ if eq(parameters.IncludeTestApplications, true) }}: + - script: > + python -m outsystems.pipeline.deploy_tags_to_target_env_with_manifest + --artifacts "$(Artifacts.Folder)" + --lt_url $(LifeTime.Hostname) + --lt_token $(LifeTime.ServiceAccountToken) + --lt_api_version $(LifeTime.APIVersion) + --source_env_label "${{ parameters.SourceEnvironmentLabel }}" + --destination_env_label "${{ parameters.DestinationEnvironmentLabel }}" + --include_test_apps + --manifest_file "$(Pipeline.Workspace)/$(Manifest.Folder)/$(Manifest.File)" + displayName: 'Deploy to ${{ parameters.DestinationEnvironmentLabel }} environment' + name: deploy_tags_to_target_step - - ${{ if eq(parameters.IncludeTestApplications, false) }}: - - script: > - python -m outsystems.pipeline.deploy_tags_to_target_env_with_manifest - --artifacts "$(Artifacts.Folder)" - --lt_url $(LifeTime.Hostname) - --lt_token $(LifeTime.ServiceAccountToken) - --lt_api_version $(LifeTime.APIVersion) - --source_env_label "${{ parameters.SourceEnvironmentLabel }}" - --destination_env_label "${{ parameters.DestinationEnvironmentLabel }}" - --manifest_file "$(Pipeline.Workspace)/$(Manifest.Folder)/$(Manifest.File)" - displayName: 'Deploy to ${{ parameters.DestinationEnvironmentLabel }} environment' + - ${{ if eq(parameters.IncludeTestApplications, false) }}: + - script: > + python -m outsystems.pipeline.deploy_tags_to_target_env_with_manifest + --artifacts "$(Artifacts.Folder)" + --lt_url $(LifeTime.Hostname) + --lt_token $(LifeTime.ServiceAccountToken) + --lt_api_version $(LifeTime.APIVersion) + --source_env_label "${{ parameters.SourceEnvironmentLabel }}" + --destination_env_label "${{ parameters.DestinationEnvironmentLabel }}" + --manifest_file "$(Pipeline.Workspace)/$(Manifest.Folder)/$(Manifest.File)" + displayName: 'Deploy to ${{ parameters.DestinationEnvironmentLabel }} environment' + name: deploy_tags_to_target_step - # ****************************************************************** - # Step: Apply configuration values - # ****************************************************************** - # Apply configuration values (if any) to target environment - # ****************************************************************** - - ${{ if eq(parameters.Use2StepDeployment, false) }}: - - template: ../tasks/ApplyConfigurationValues.yaml - parameters: - TargetEnvironmentLabel: ${{ parameters.DestinationEnvironmentLabel }} + # ****************************************************************** + # Step: Apply configuration values + # ****************************************************************** + # Apply configuration values (if any) to target environment + # ****************************************************************** + - ${{ if eq(parameters.Use2StepDeployment, false) }}: + - template: ../tasks/ApplyConfigurationValues.yaml + parameters: + TargetEnvironmentLabel: ${{ parameters.DestinationEnvironmentLabel }} - # ****************************************************************** - # Step: Print deployment conflicts - # ****************************************************************** - # Check if there any Deployment Conflicts and show them in the - # console log - # ****************************************************************** - - task: PowerShell@2 - inputs: - targetType: 'inline' - script: Get-Content -Path "$(Artifacts.Folder)\DeploymentConflicts" | Write-Host - condition: failed() - displayName: 'Show content of DeploymentConflicts file' + # ****************************************************************** + # Step: Print deployment conflicts + # ****************************************************************** + # Check if there any Deployment Conflicts and show them in the + # console log + # ****************************************************************** + - task: PowerShell@2 + inputs: + targetType: 'inline' + script: Get-Content -Path "$(Artifacts.Folder)\DeploymentConflicts" | Write-Host + condition: failed() + displayName: 'Show content of DeploymentConflicts file' # ****************************************************************** # Job: Wait for confirmation @@ -127,7 +123,11 @@ jobs: - ${{ if eq(parameters.Use2StepDeployment, true) }}: - job: finalize_deployment displayName: Finalize Deployment - dependsOn: wait_confirmation + dependsOn: + - lifetime_deployment + - wait_confirmation + variables: + DEPLOYMENT_PLAN_KEY: $[ dependencies.lifetime_deployment.outputs['deploy_tags_to_target_step.DEPLOYMENT_PLAN_KEY'] ] steps: - checkout: none # Avoid repository checkout - download: current # Download current pipeline artifacts @@ -146,7 +146,8 @@ jobs: --lt_url $(LifeTime.Hostname) --lt_token $(LifeTime.ServiceAccountToken) --lt_api_version $(LifeTime.APIVersion) - --destination_env "${{ parameters.DestinationEnvironmentLabel }}" + --destination_env "${{ parameters.DestinationEnvironmentLabel }}" + --deployment_plan_key $(DEPLOYMENT_PLAN_KEY) displayName: 'Continue deployment to ${{ parameters.DestinationEnvironmentLabel }} environment' # ****************************************************************** diff --git a/outsystems/lifetime/lifetime_deployments.py b/outsystems/lifetime/lifetime_deployments.py index ae26723..5c84eeb 100644 --- a/outsystems/lifetime/lifetime_deployments.py +++ b/outsystems/lifetime/lifetime_deployments.py @@ -117,7 +117,7 @@ def get_deployment_status(artifact_dir: str, endpoint: str, auth_token: str, dep # Returns the details of the running deployment plan to a specific target environment or empty if nothing is running -def get_running_deployment(artifact_dir: str, endpoint: str, auth_token: str, dest_env_key: str): +def get_running_deployments(artifact_dir: str, endpoint: str, auth_token: str, dest_env_key: str): # List of running deployments running_deployments = [] # Date 24h prior to now @@ -125,11 +125,11 @@ def get_running_deployment(artifact_dir: str, endpoint: str, auth_token: str, de date = date.date() try: latest_deployments = get_deployments(artifact_dir, endpoint, auth_token, date) - for deplyoment in latest_deployments: - if deplyoment["TargetEnvironmentKey"] == dest_env_key: - deployment_status = get_deployment_status(artifact_dir, endpoint, auth_token, deplyoment["Key"]) + for deployment in latest_deployments: + if deployment["TargetEnvironmentKey"] == dest_env_key: + deployment_status = get_deployment_status(artifact_dir, endpoint, auth_token, deployment["Key"]) if deployment_status["DeploymentStatus"] in DEPLOYMENT_STATUS_LIST: - running_deployments.append(deplyoment) + running_deployments.append(deployment) return running_deployments diff --git a/outsystems/pipeline/continue_deployment_to_target_env.py b/outsystems/pipeline/continue_deployment_to_target_env.py index 0c9e279..84027b2 100644 --- a/outsystems/pipeline/continue_deployment_to_target_env.py +++ b/outsystems/pipeline/continue_deployment_to_target_env.py @@ -22,7 +22,7 @@ # Functions from outsystems.lifetime.lifetime_environments import get_environment_key from outsystems.lifetime.lifetime_deployments import get_deployment_status, check_deployment_two_step_deploy_status, \ - continue_deployment, get_running_deployment + continue_deployment, get_running_deployments from outsystems.file_helpers.file import store_data from outsystems.lifetime.lifetime_base import build_lt_endpoint from outsystems.vars.vars_base import get_configuration_value, load_configuration_file @@ -31,7 +31,7 @@ # ############################################################# SCRIPT ############################################################## -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: str): +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: str, dep_plan_key: str = None): # Builds the LifeTime endpoint lt_endpoint = build_lt_endpoint(lt_http_proto, lt_url, lt_api_endpoint, lt_api_version) @@ -39,15 +39,22 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st # Gets the environment key for the destination environment dest_env_key = get_environment_key(artifact_dir, lt_endpoint, lt_token, dest_env) - # Find running deployment plan in destination environment - deployment = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key) - if len(deployment) == 0: + # Use dep_plan_key if provided, otherwise find running deployment plan in destination environment + if dep_plan_key is None: + deployment = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_key) + if len(deployment) == 0: + print("Continue skipped because no running deployment plan was found on {} environment.".format(dest_env)) + sys.exit(0) + + # Grab the key from the deployment plan found + dep_plan_key = deployment[0]["Key"] + print("Deployment plan {} was found.".format(dep_plan_key), flush=True) + # Cases where no deployment plan is created are handled by setting the deployment plan key to no_deployment_required. + elif dep_plan_key == "no_deployment_required": print("Continue skipped because no running deployment plan was found on {} environment.".format(dest_env)) sys.exit(0) - - # Grab the key from the deployment plan found - dep_plan_key = deployment[0]["Key"] - print("Deployment plan {} was found.".format(dep_plan_key), flush=True) + else: + print("Using provided deployment plan key: {}".format(dep_plan_key), flush=True) # Check deployment plan status dep_status = get_deployment_status( @@ -111,6 +118,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st help="Name, as displayed in LifeTime, of the destination environment where you want to continue the deployment plan.") parser.add_argument("-cf", "--config_file", type=str, help="Config file path. Contains configuration values to override the default ones.") + parser.add_argument("-dpk", "--deployment_plan_key", type=str, + help="An optional deployment key for resuming a specific deployment, otherwise the first deployment plan in running state is used.") args = parser.parse_args() @@ -138,6 +147,8 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st lt_token = args.lt_token # Parse Destination Environment dest_env = args.destination_env + # Parse the optionally provided deployment plan key + dep_plan_key = args.deployment_plan_key # Calls the main script - main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, dest_env) + main(artifact_dir, lt_http_proto, lt_url, lt_api_endpoint, lt_version, lt_token, dest_env, dep_plan_key) diff --git a/outsystems/pipeline/deploy_latest_tags_to_target_env.py b/outsystems/pipeline/deploy_latest_tags_to_target_env.py index 6eb1eb1..ba543cb 100644 --- a/outsystems/pipeline/deploy_latest_tags_to_target_env.py +++ b/outsystems/pipeline/deploy_latest_tags_to_target_env.py @@ -24,7 +24,7 @@ from outsystems.lifetime.lifetime_environments import get_environment_app_version, get_environment_key from outsystems.lifetime.lifetime_applications import get_running_app_version, get_application_version from outsystems.lifetime.lifetime_deployments import get_deployment_status, get_deployment_info, \ - send_deployment, delete_deployment, start_deployment, continue_deployment, get_running_deployment + send_deployment, delete_deployment, start_deployment, continue_deployment, get_running_deployments from outsystems.file_helpers.file import store_data, load_data from outsystems.lifetime.lifetime_base import build_lt_endpoint from outsystems.vars.vars_base import get_configuration_value, load_configuration_file @@ -147,6 +147,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st # Check if there are apps to be deployed if len(to_deploy_app_keys) == 0: + print("##vso[task.setvariable variable=DEPLOYMENT_PLAN_KEY;isOutput=true]no_deployment_required", flush=True) print("Deployment skipped because {} environment already has the target application deployed with the same tags.".format(dest_env), flush=True) sys.exit(0) @@ -169,7 +170,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st if not allow_parallel_deployments: wait_counter = 0 - deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key) + deployments = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_key) while len(deployments) > 0: if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS): print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True) @@ -178,13 +179,16 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st sleep(sleep_value) wait_counter += sleep_value print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True) - deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key) + deployments = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_key) # LT is free to deploy # Send the deployment plan and grab the key dep_plan_key = send_deployment(artifact_dir, lt_endpoint, lt_token, lt_api_version, to_deploy_app_keys, dep_note, source_env, dest_env) print("Deployment plan {} created successfully.".format(dep_plan_key), flush=True) + # Write dep_plan_key to Azure DevOps variable + print(f"##vso[task.setvariable variable=DEPLOYMENT_PLAN_KEY;isOutput=true]{dep_plan_key}", flush=True) + # Check if created deployment plan has conflicts dep_details = get_deployment_info(artifact_dir, lt_endpoint, lt_token, dep_plan_key) if len(dep_details["ApplicationConflicts"]) > 0: diff --git a/outsystems/pipeline/deploy_package_to_target_env.py b/outsystems/pipeline/deploy_package_to_target_env.py index 606292d..f4711e1 100644 --- a/outsystems/pipeline/deploy_package_to_target_env.py +++ b/outsystems/pipeline/deploy_package_to_target_env.py @@ -22,7 +22,7 @@ # Functions from outsystems.lifetime.lifetime_environments import get_environment_key from outsystems.lifetime.lifetime_deployments import get_deployment_status, get_deployment_info, \ - send_binary_deployment, delete_deployment, start_deployment, continue_deployment, get_running_deployment, \ + send_binary_deployment, delete_deployment, start_deployment, continue_deployment, get_running_deployments, \ check_deployment_two_step_deploy_status from outsystems.file_helpers.file import store_data, is_valid_os_package from outsystems.lifetime.lifetime_base import build_lt_endpoint @@ -42,7 +42,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st if not allow_parallel_deployments: wait_counter = 0 - deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key) + deployments = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_key) while len(deployments) > 0: if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS): print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True) @@ -51,7 +51,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st sleep(sleep_value) wait_counter += sleep_value print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True) - deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_key) + deployments = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_key) # LT is free to deploy # Validate if file has OutSystems package extension @@ -62,6 +62,9 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st dep_plan_key = send_binary_deployment(artifact_dir, lt_endpoint, lt_token, lt_api_version, dest_env_key, package_path) print("Deployment plan {} created successfully.".format(dep_plan_key), flush=True) + # Write dep_plan_key to Azure DevOps variable + print(f"##vso[task.setvariable variable=DEPLOYMENT_PLAN_KEY;isOutput=true]{dep_plan_key}", flush=True) + # Check if created deployment plan has conflicts dep_details = get_deployment_info(artifact_dir, lt_endpoint, lt_token, dep_plan_key) has_conflicts = len(dep_details["ApplicationConflicts"]) > 0 @@ -113,6 +116,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st elif not alert_user: alert_user = True print("A manual intervention is required to continue the execution of the deployment plan {}.".format(dep_plan_key), flush=True) + print(f"##vso[task.logissue type=warning;]A manual intervention is required to continue the execution of the deployment plan {dep_plan_key}.", flush=True) elif dep_status["DeploymentStatus"] in DEPLOYMENT_ERROR_STATUS_LIST: print("Deployment plan finished with status {}.".format(dep_status["DeploymentStatus"]), flush=True) store_data(artifact_dir, DEPLOY_ERROR_FILE, dep_status) diff --git a/outsystems/pipeline/deploy_tags_to_target_env_with_manifest.py b/outsystems/pipeline/deploy_tags_to_target_env_with_manifest.py index 3da835f..ad9dbb0 100644 --- a/outsystems/pipeline/deploy_tags_to_target_env_with_manifest.py +++ b/outsystems/pipeline/deploy_tags_to_target_env_with_manifest.py @@ -26,7 +26,7 @@ from outsystems.lifetime.lifetime_environments import get_environment_app_version, get_environment_deployment_zones from outsystems.lifetime.lifetime_applications import get_application_version from outsystems.lifetime.lifetime_deployments import get_deployment_status, get_deployment_info, \ - send_deployment, delete_deployment, start_deployment, continue_deployment, get_running_deployment, \ + send_deployment, delete_deployment, start_deployment, continue_deployment, get_running_deployments, \ check_deployment_two_step_deploy_status from outsystems.file_helpers.file import store_data, load_data from outsystems.lifetime.lifetime_base import build_lt_endpoint @@ -148,6 +148,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st # Check if there are apps to be deployed if len(to_deploy_app_keys) == 0: + print("##vso[task.setvariable variable=DEPLOYMENT_PLAN_KEY;isOutput=true]no_deployment_required", flush=True) print("Deployment skipped because {} environment already has the target application deployed with the same tags.".format(dest_env_tuple[0]), flush=True) sys.exit(0) @@ -170,7 +171,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st if not allow_parallel_deployments: wait_counter = 0 - deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1]) + deployments = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1]) while len(deployments) > 0: if wait_counter >= get_configuration_value("QUEUE_TIMEOUT_IN_SECS", QUEUE_TIMEOUT_IN_SECS): print("Timeout occurred while waiting for LifeTime to be free, to create the new deployment plan.", flush=True) @@ -179,13 +180,16 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st sleep(sleep_value) wait_counter += sleep_value print("Waiting for LifeTime to be free. Elapsed time: {} seconds...".format(wait_counter), flush=True) - deployments = get_running_deployment(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1]) + deployments = get_running_deployments(artifact_dir, lt_endpoint, lt_token, dest_env_tuple[1]) # LT is free to deploy # Send the deployment plan and grab the key dep_plan_key = send_deployment(artifact_dir, lt_endpoint, lt_token, lt_api_version, to_deploy_app_keys, get_deployment_notes(trigger_manifest), src_env_tuple[0], dest_env_tuple[0]) print("Deployment plan {} created successfully.".format(dep_plan_key), flush=True) + # Write dep_plan_key to Azure DevOps variable + print(f"##vso[task.setvariable variable=DEPLOYMENT_PLAN_KEY;isOutput=true]{dep_plan_key}", flush=True) + # Check if created deployment plan has conflicts dep_details = get_deployment_info(artifact_dir, lt_endpoint, lt_token, dep_plan_key) has_conflicts = len(dep_details["ApplicationConflicts"]) > 0 @@ -237,6 +241,7 @@ def main(artifact_dir: str, lt_http_proto: str, lt_url: str, lt_api_endpoint: st elif not alert_user: alert_user = True print("A manual intervention is required to continue the execution of the deployment plan {}.".format(dep_plan_key), flush=True) + print(f"##vso[task.logissue type=warning;]A manual intervention is required to continue the execution of the deployment plan {dep_plan_key}.", flush=True) elif dep_status["DeploymentStatus"] in DEPLOYMENT_ERROR_STATUS_LIST: print("Deployment plan finished with status {}.".format(dep_status["DeploymentStatus"]), flush=True) store_data(artifact_dir, DEPLOY_ERROR_FILE, dep_status)