diff --git a/runtime-destroy-action/script.py b/runtime-destroy-action/script.py index 6f9d631..e82696c 100644 --- a/runtime-destroy-action/script.py +++ b/runtime-destroy-action/script.py @@ -5,12 +5,18 @@ from typing import List # Configure logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) STK_IAM_DOMAIN = os.getenv("STK_IAM_DOMAIN", "https://idm.stackspot.com") -STK_RUNTIME_MANAGER_DOMAIN = os.getenv("STK_RUNTIME_MANAGER_DOMAIN", "https://runtime-manager.v1.stackspot.com") -CONTAINER_DESTROY_URL = os.getenv("CONTAINER_DESTROY_URL", "stackspot/runtime-job-destroy:latest") +STK_RUNTIME_MANAGER_DOMAIN = os.getenv( + "STK_RUNTIME_MANAGER_DOMAIN", "https://runtime-manager.v1.stackspot.com" +) +CONTAINER_DESTROY_URL = os.getenv( + "CONTAINER_DESTROY_URL", "stackspot/runtime-job-destroy:latest" +) FEATURES_BASEPATH_TMP = "/tmp/runtime/deploys" FEATURES_BASEPATH_EBS = "/opt/runtime" @@ -20,7 +26,7 @@ def check(result: subprocess.Popen) -> None: """ - Checks the result of a subprocess execution. If the return code is non-zero, + Checks the result of a subprocess execution. If the return code is non-zero, it logs an error message and exits the program. Args: @@ -46,12 +52,14 @@ def run_command(command: List[str]) -> subprocess.Popen: try: logging.info(f"Running command: {' '.join(command)}") # Start the process - process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + # Read and print output in real-time for line in process.stdout: print(line, end="") # Print each line as it is produced - + # Check the result after the process completes check(process) return process @@ -62,33 +70,31 @@ def run_command(command: List[str]) -> subprocess.Popen: def build_flags(inputs: dict) -> list: - + docker_flags: dict = dict( FEATURES_LEVEL_LOG=inputs.get("features_level_log") or "info", FEATURES_TERRAFORM_LOGPROVIDER=inputs.get("tf_log_provider") or "info", FEATURES_RELEASE_LOCALEXEC=inputs.get("localexec_enabled") or "False", - FEATURES_TERRAFORM_MODULES=inputs.get("features_terraform_modules") or '[]', - - AWS_ACCESS_KEY_ID=inputs['aws_access_key_id'], - AWS_SECRET_ACCESS_KEY=inputs['aws_secret_access_key'], - AWS_SESSION_TOKEN=inputs['aws_session_token'], + FEATURES_TERRAFORM_MODULES=inputs.get("features_terraform_modules") or "[]", + AWS_ACCESS_KEY_ID=inputs["aws_access_key_id"], + AWS_SECRET_ACCESS_KEY=inputs["aws_secret_access_key"], + AWS_SESSION_TOKEN=inputs["aws_session_token"], AUTHENTICATE_CLIENT_ID=inputs["client_id"], AUTHENTICATE_CLIENT_SECRET=inputs["client_key"], AUTHENTICATE_CLIENT_REALMS=inputs["client_realm"], REPOSITORY_NAME=inputs["repository_name"], AWS_REGION=inputs["aws_region"], - AUTHENTICATE_URL=STK_IAM_DOMAIN, FEATURES_API_MANAGER=STK_RUNTIME_MANAGER_DOMAIN, FEATURES_BASEPATH_TMP=FEATURES_BASEPATH_TMP, FEATURES_BASEPATH_EBS=FEATURES_BASEPATH_EBS, FEATURES_TEMPLATES_FILEPATH=FEATURES_TEMPLATES_FILEPATH, - FEATURES_BASEPATH_TERRAFORM=FEATURES_BASEPATH_TERRAFORM + FEATURES_BASEPATH_TERRAFORM=FEATURES_BASEPATH_TERRAFORM, ) flags = [] for k, v in docker_flags.items(): flags += ["-e", f"{k}={v}"] - + return flags @@ -96,14 +102,18 @@ def run(metadata): inputs: dict = metadata.inputs run_task_id: str = inputs["run_task_id"] path_to_mount: str = inputs.get("path_to_mount") or "." - path_to_mount = f"{path_to_mount}:/app-volume" + path_to_mount = f"{path_to_mount}:/app-volume" flags = build_flags(inputs) - cmd = ["docker", "run", "--rm", "-v", path_to_mount] + flags + [ - "--entrypoint=/app/stackspot-runtime-job-destroy", - CONTAINER_DESTROY_URL, - "start", - f"--run-task-id={run_task_id}", - ] - - run_command(cmd) \ No newline at end of file + cmd = ( + ["docker", "run", "--rm", "-v", path_to_mount] + + flags + + [ + "--entrypoint=/app/stackspot-runtime-job-destroy", + CONTAINER_DESTROY_URL, + "start", + f"--run-task-id={run_task_id}", + ] + ) + + run_command(cmd) diff --git a/runtime-rollback-action/script.py b/runtime-rollback-action/script.py index 19a6534..b92ef0e 100644 --- a/runtime-rollback-action/script.py +++ b/runtime-rollback-action/script.py @@ -9,11 +9,15 @@ from sys import exit # Constants -STK_RUNTIME_MANAGER_DOMAIN = os.getenv("STK_RUNTIME_MANAGER_DOMAIN", "https://runtime-manager.v1.stackspot.com") -STK_WORKSPACE_DOMAIN = os.getenv("STK_WORKSPACE_DOMAIN", "https://runtime-manager.v1.stackspot.com") -STK_FILE = '.stk/stk.yaml' -OUTPUT_FILE = 'rollback-output.log' -HEADERS = {'Content-Type': 'application/json'} +STK_RUNTIME_MANAGER_DOMAIN = os.getenv( + "STK_RUNTIME_MANAGER_DOMAIN", "https://runtime-manager.v1.stackspot.com" +) +STK_WORKSPACE_DOMAIN = os.getenv( + "STK_WORKSPACE_DOMAIN", "https://runtime-manager.v1.stackspot.com" +) +STK_FILE = ".stk/stk.yaml" +OUTPUT_FILE = "rollback-output.log" +HEADERS = {"Content-Type": "application/json"} TIMEOUT = 20 @@ -25,6 +29,7 @@ def yaml() -> YAML: yml.preserve_quotes = True return yml + def safe_load(content: str) -> dict: yml = yaml() return yml.load(StringIO(content)) @@ -32,7 +37,7 @@ def safe_load(content: str) -> dict: def get_stk_yaml() -> dict: try: - with open(Path(STK_FILE), 'r') as file: + with open(Path(STK_FILE), "r") as file: stk_yaml = file.read() stk_yaml = safe_load(stk_yaml) return stk_yaml @@ -46,82 +51,96 @@ def get_stk_yaml() -> dict: def save_output(value: dict): - with open(OUTPUT_FILE, 'w') as output_file: + with open(OUTPUT_FILE, "w") as output_file: json.dump(value, output_file, indent=4) print(f"> Output saved to {OUTPUT_FILE}") - + def build_request(action_inputs: dict, env_id: str, stk_yaml: dict) -> dict: try: # Extract the type of deployment (app or infra) from the stack YAML configuration type = get_type(stk_yaml) - + # Define a parser to map the type to the appropriate key and value for the request payload type_parser = { "app": {"key": "appId", "value": stk_yaml["spec"].get("app-id")}, - "infra": {"key": "infraId", "value": stk_yaml["spec"].get("infra-id")} - } - + "infra": {"key": "infraId", "value": stk_yaml["spec"].get("infra-id")}, + } + # Build the request payload using the provided inputs and the parsed type information request_data = { - f"{type_parser[type]['key']}": type_parser[type]['value'], + f"{type_parser[type]['key']}": type_parser[type]["value"], "envId": env_id, "tag": action_inputs["version_tag"], "config": { "tfstate": { "bucket": action_inputs["tf_state_bucket_name"], - "region": action_inputs["tf_state_region"] + "region": action_inputs["tf_state_region"], }, "iac": { "bucket": action_inputs["iac_bucket_name"], - "region": action_inputs["iac_region"] - } + "region": action_inputs["iac_region"], + }, }, "pipelineUrl": "http://stackspot.com", } - - print(f"> Runtime manager run self hosted rollback request data:\n{json.dumps(request_data, indent=4)}") + + print( + f"> Runtime manager run self hosted rollback request data:\n{json.dumps(request_data, indent=4)}" + ) return request_data except KeyError as e: print(f"> Error: Missing required input {e}") exit(1) + def get_type(stk_yaml: dict) -> str: return stk_yaml["spec"]["type"] -def runtime_manager_run_self_hosted_rollback(request_data: dict, stk_yaml: dict, version_tag: str): + +def runtime_manager_run_self_hosted_rollback( + request_data: dict, stk_yaml: dict, version_tag: str +): type = get_type(stk_yaml) url = f"{STK_RUNTIME_MANAGER_DOMAIN}/v1/run/self-hosted/rollback/{type}" - + print("> Calling runtime manager to rollback tasks...") - response = post_with_authorization(url=url, body=request_data, headers=HEADERS, timeout=TIMEOUT) + response = post_with_authorization( + url=url, body=request_data, headers=HEADERS, timeout=TIMEOUT + ) if response.ok: - if(response.status_code == 201): + if response.status_code == 201: response_data = response.json() - print(f"> Rollback successfully started:\n{json.dumps(response_data, indent=4)}") + print( + f"> Rollback successfully started:\n{json.dumps(response_data, indent=4)}" + ) else: - print(f"> Rollback successfully but no modifications detected for tag {version_tag}") + print( + f"> Rollback successfully but no modifications detected for tag {version_tag}" + ) response_data = {} save_output(response_data) - + else: - print(f"> Error: Failed to start self-hosted rollback run. Status: {response.status_code}") + print( + f"> Error: Failed to start self-hosted rollback run. Status: {response.status_code}" + ) print(f"> Response: {response.text}") exit(1) def get_environment_id(environment_slug): - + url = f"{STK_WORKSPACE_DOMAIN}/v1/environments" print("> Calling workspace to load environments...") - response = get_with_authorization(url=url,headers=HEADERS,timeout=TIMEOUT) - - if response.ok: + response = get_with_authorization(url=url, headers=HEADERS, timeout=TIMEOUT) + + if response.ok: env_list = response.json() for env in env_list: if env["name"] == environment_slug: @@ -131,7 +150,7 @@ def get_environment_id(environment_slug): print(f"> Error: Failed to load environments. Status: {response.status_code}") print(f"> Response: {response.text}") exit(1) - + def run(metadata): # Load the manifest file @@ -139,9 +158,11 @@ def run(metadata): # Load environment Id env_id = get_environment_id(metadata.inputs.get("environment")) - + # Build the request data request = build_request(metadata.inputs, env_id, stk_yaml) - + # Execute the rollback request - runtime_manager_run_self_hosted_rollback(request, stk_yaml, metadata.inputs['version_tag']) \ No newline at end of file + runtime_manager_run_self_hosted_rollback( + request, stk_yaml, metadata.inputs["version_tag"] + ) diff --git a/script.py b/script.py index 8398c6b..fa70af6 100644 --- a/script.py +++ b/script.py @@ -35,30 +35,37 @@ def deploy_workflow(run_action: RunAction): run_tasks("manager-output.log", run_action) + def cancel_deploy_run(run_action: RunAction): run_action("runtime-cancel-run-action") + def rollback_deploy_run(run_action: RunAction): run_action("runtime-rollback-action") run_tasks("rollback-output.log", run_action) - + def run_tasks(file_tasks: str, run_action: RunAction): - with open(file_tasks, 'r') as file: - data = json.loads(file.read().replace("\'", "\"")) - + with open(file_tasks, "r") as file: + data = json.loads(file.read().replace("'", '"')) + task_runners = dict( IAC_SELF_HOSTED=lambda **i: run_action("runtime-iac-action", **i), DEPLOY_SELF_HOSTED=lambda **i: run_action("runtime-deploy-action", **i), DESTROY_SELF_HOSTED=lambda **i: run_action("runtime-destroy-action", **i), ) - - for t in data.get('tasks') or []: + + for t in data.get("tasks") or []: runner = task_runners.get(t["taskType"]) runner and runner(run_task_id=t["runTaskId"]) + def run(metadata): - workflows = dict(deploy=deploy_workflow, cancel_deploy=cancel_deploy_run, rollback_deploy=rollback_deploy_run) + workflows = dict( + deploy=deploy_workflow, + cancel_deploy=cancel_deploy_run, + rollback_deploy=rollback_deploy_run, + ) run_action = RunAction(metadata) workflow_runner = workflows.get(metadata.inputs["workflow_type"]) workflow_runner and workflow_runner(run_action=run_action)