PK!ecs_tool/__init__.pyPK!фh11ecs_tool/cli.pyimport sys import time from datetime import datetime import boto3 import click from botocore.exceptions import NoRegionError, NoCredentialsError from click import UsageError from ecs_tool.exceptions import WaitParameterException from ecs_tool.tables import ServicesTable, TasksTable, TaskDefinitionsTable SERVICE_STATUS_COLOUR = { "ACTIVE": "autogreen", "DRAINING": "autoyellow", "INACTIVE": "autored", } TASK_STATUS_COLOUR = { "PROVISIONING": "autoblue", "PENDING": "automagenta", "ACTIVATING": "autoyellow", "RUNNING": "autogreen", "DEACTIVATING": "autoyellow", "STOPPING": "automagenta", "DEPROVISIONING": "autoblue", "STOPPED": "autored", } DATE_FORMAT = "%Y-%m-%d %H:%M:%S %Z" class EcsClusterCommand(click.core.Command): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.params.insert( 0, click.core.Option( ("--cluster",), default="default", help="Cluster name or ARN.", show_default=True, ), ) @click.group() @click.pass_context def cli(ctx): ctx.obj = {} try: ecs_client = boto3.client("ecs") except (NoRegionError, NoCredentialsError) as e: raise UsageError(f"AWS Configuration: {e}") ctx.obj["ecs_client"] = ecs_client @cli.command(cls=EcsClusterCommand) @click.option( "--launch-type", type=click.Choice(["EC2", "FARGATE"]), help="Launch type" ) @click.option( "--scheduling-strategy", type=click.Choice(["REPLICA", "DAEMON"]), help="Scheduling strategy", ) @click.pass_context def services(ctx, cluster, launch_type=None, scheduling_strategy=None): """ List of services. D - Desired count P - Pending count R - Running count """ args = {"cluster": cluster} if launch_type: args["launchType"] = launch_type if scheduling_strategy: args["schedulingStrategy"] = scheduling_strategy list_services = ctx.obj["ecs_client"].list_services(**args) if not list_services["serviceArns"]: click.secho("No results found.", fg="red") sys.exit() describe_services = ctx.obj["ecs_client"].describe_services( cluster=cluster, services=list_services["serviceArns"] ) print(ServicesTable.build(describe_services["services"]).table) @cli.command(cls=EcsClusterCommand) @click.option( "--status", type=click.Choice(["RUNNING", "STOPPED"]), default="RUNNING", help="Task status", show_default=True, ) @click.option("--service-name", help="Service name") @click.option("--family", help="Family name") @click.option( "--launch-type", type=click.Choice(["EC2", "FARGATE"]), help="Launch type" ) @click.pass_context def tasks(ctx, cluster, status, service_name=None, family=None, launch_type=None): """ List of tasks. """ args = {"cluster": cluster} if service_name: args["serviceName"] = service_name if family: args["family"] = family if status: args["desiredStatus"] = status if launch_type: args["launchType"] = launch_type list_tasks = ctx.obj["ecs_client"].list_tasks(**args) if not list_tasks["taskArns"]: click.secho("No results found.", fg="red") sys.exit() describe_tasks = ctx.obj["ecs_client"].describe_tasks( cluster=cluster, tasks=list_tasks["taskArns"] ) print(TasksTable.build(describe_tasks["tasks"]).table) @cli.command() @click.option("--family", help="Family name") @click.option("--status", type=click.Choice(["ACTIVE", "INACTIVE"]), help="Status") @click.pass_context def task_definitions(ctx, family=None, status=None): """ List of task definitions. """ args = {} if family: args["familyPrefix"] = family if status: args["status"] = status response = ctx.obj["ecs_client"].list_task_definitions(**args) print(TaskDefinitionsTable.build(response["taskDefinitionArns"]).table) @cli.command(cls=EcsClusterCommand) @click.option("--wait", is_flag=True, help="Wait till task will reach STOPPED status.") @click.argument("task-definition", required=True) @click.argument("command", nargs=-1) @click.pass_context def run_task(ctx, cluster, task_definition, wait, command=None): """ Run task. task_definition: Task definition.\n command: Command passed to task. Needs to be passed after "--" e.g. ecs run-task my_definition:1 -- my_script/ """ started_by = "ecs-tool:" + str(datetime.timestamp(datetime.now())) args = { "cluster": cluster, "taskDefinition": task_definition, "startedBy": started_by, } if command: args["overrides"] = { "containerOverrides": [ {"name": task_definition.rsplit(":", 1)[0], "command": command} ] } ctx.obj["ecs_client"].run_task(**args) if wait: while True: list_tasks = ctx.obj["ecs_client"].list_tasks( cluster=cluster, startedBy=started_by, desiredStatus="STOPPED" ) if list_tasks["taskArns"]: describe_tasks = ctx.obj["ecs_client"].describe_tasks( cluster=cluster, tasks=list_tasks["taskArns"] ) if len(describe_tasks["tasks"]) > 1: raise WaitParameterException tasks = describe_tasks["tasks"] break time.sleep(2) print(TasksTable.build(tasks).table) else: list_tasks = ctx.obj["ecs_client"].list_tasks( cluster=cluster, startedBy=started_by ) describe_tasks = ctx.obj["ecs_client"].describe_tasks( cluster=cluster, tasks=list_tasks["taskArns"] ) print(TasksTable.build(describe_tasks["tasks"]).table) if __name__ == "__main__": cli() PK!5Lecs_tool/exceptions.pyclass EcsToolException: pass class WaitParameterException(EcsToolException): """ Exception used when "describe_tasks" returns more than one task. """ PK!~cecs_tool/tables.pyfrom textwrap import wrap from colorclass import Color from terminaltables import SingleTable SERVICE_STATUS_COLOUR = { "ACTIVE": "autogreen", "DRAINING": "autoyellow", "INACTIVE": "autored", } TASK_STATUS_COLOUR = { "PROVISIONING": "autoblue", "PENDING": "automagenta", "ACTIVATING": "autoyellow", "RUNNING": "autogreen", "DEACTIVATING": "autoyellow", "STOPPING": "automagenta", "DEPROVISIONING": "autoblue", "STOPPED": "autored", } DATE_FORMAT = "%Y-%m-%d %H:%M:%S %Z" class EcsTable(SingleTable): def __init__(self, data): super().__init__(data) self.inner_row_border = True class ServicesTable(EcsTable): HEADER = ( "Service name", "Task definition", "Status", "D", "P", "R", "Service type", "Launch type", ) @classmethod def build(cls, services): data = [ServicesTable.HEADER] for service in services: status_colour = SERVICE_STATUS_COLOUR.get(service["status"]) data.append( [ service["serviceName"], service["taskDefinition"].rsplit("task-definition/", 1)[-1], Color( f"{{{status_colour}}}{service['status']}{{/{status_colour}}}" ), service["desiredCount"], service["pendingCount"], service["runningCount"], service["schedulingStrategy"], service["launchType"], ] ) return cls(data) class TasksTable(EcsTable): HEADER = ( "Task", "Task definition", "Status", "Command", "Started at", "Stopped at", "Execution time", "Exit code", "Exit reason", "Stopped reason", ) @classmethod def build(cls, tasks): data = [TasksTable.HEADER] for task in tasks: status_colour = TASK_STATUS_COLOUR.get(task["lastStatus"]) data.append( [ task["taskArn"].rsplit("task/", 1)[-1], task["taskDefinitionArn"].rsplit("task-definition/", 1)[-1], Color( f"{{{status_colour}}}{task['lastStatus']}{{/{status_colour}}}" ), " ".join( task["overrides"]["containerOverrides"][0].get("command", "") ), task.get("startedAt").strftime(DATE_FORMAT) if task.get("startedAt") else "", task.get("stoppedAt").strftime(DATE_FORMAT) if task.get("stoppedAt") else "", task.get("stoppedAt") - task.get("startedAt") if all((task.get("startedAt"), task.get("stoppedAt"))) else "", task.get("containers")[0].get("exitCode") if task.get("containers")[0].get("exitCode") else "", _wrap(task.get("containers")[0].get("reason"), 10), _wrap(task.get("stoppedReason"), 10), ] ) return cls(data) class TaskDefinitionsTable(EcsTable): HEADER = ("Task definition",) @classmethod def build(cls, task_definitions): data = [TaskDefinitionsTable.HEADER] for definition in task_definitions: data.append([definition.rsplit("task-definition/", 1)[-1]]) return cls(data) def _wrap(text, size): if not text: return "" return "\n".join(wrap(text, size)) + "\n" PK!H. '()ecs_tool-0.0.3.dist-info/entry_points.txtN+I/N.,()JM.L+ PK!!O.. ecs_tool-0.0.3.dist-info/LICENSEMIT License Copyright (c) 2019 Daniel Ancuta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hu)GTUecs_tool-0.0.3.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O-)$qzd&Y)r$UV&UrPK!H-pgC.!ecs_tool-0.0.3.dist-info/METADATAUo6~_qC I77)0c`(-%ɑ'(i[&}w[$YJo胲&Iv.~ EHZ-8E4w9\as ܅}TK j[60?2I)PdLcMB>UvjF?zPs 4ogv֗!0&)jsF)ZtG?V};UCz1}u9^$fcqej0-j4 sB 3jy}c[cteMO sChJ,a֖}J"3=s>e[n.艼ܪ*Y+/MQ_6{;z?1TSe-CuN'eOn4g7~SIԧx! Ҕ౉O܃`  0} GMmib^u7fx~s'g ^=C PKV@5*k) f!w_Y=Mecn>cZ1Ejh[ǹ=F:Ŝ"[@ub&n5 o oRG}!9-/̐9 aG7PK!H"ecs_tool-0.0.3.dist-info/RECORD}ιz@@>2v€,!,221/SdCB0FIRuIE?LYAKZ^JB)R+Mo+QQ< U/`\>e)+샹y kwo9;TnZ661I * Yӣh)'Z[7e M`DW#H&XJ"^N}B_>2ky>u$.(^ nW?\UGyg!ưZbQwgU'.aXҰT{1Ugrgaf>*cmƻ+\쨾r&Q55Γ%WkʌD1ixwrJcեl:DueLqxlw&"u;h1@: )d69CPK!ecs_tool/__init__.pyPK!фh112ecs_tool/cli.pyPK!5Lecs_tool/exceptions.pyPK!~clecs_tool/tables.pyPK!H. '()U'ecs_tool-0.0.3.dist-info/entry_points.txtPK!!O.. 'ecs_tool-0.0.3.dist-info/LICENSEPK!Hu)GTU/,ecs_tool-0.0.3.dist-info/WHEELPK!H-pgC.!,ecs_tool-0.0.3.dist-info/METADATAPK!H"A0ecs_tool-0.0.3.dist-info/RECORDPK 12