From f5dd34369c77310e95b5ca16a1cc4c07bc40a873 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 13 Feb 2019 01:23:58 +0100 Subject: [PATCH] [run] wait command to finish --- ecs_deploy/cli.py | 38 ++++++++++++++++++++- ecs_deploy/ecs.py | 7 ++++ tests/test_cli.py | 87 +++++++++++++++++++---------------------------- tests/test_ecs.py | 12 +++++-- 4 files changed, 88 insertions(+), 56 deletions(-) diff --git a/ecs_deploy/cli.py b/ecs_deploy/cli.py index bca96be..c837553 100644 --- a/ecs_deploy/cli.py +++ b/ecs_deploy/cli.py @@ -161,10 +161,11 @@ def scale(cluster, service, desired_count, access_key_id, secret_access_key, reg @click.option('--access-key-id', required=False, help='AWS access key id') @click.option('--secret-access-key', required=False, help='AWS secret access key') @click.option('--profile', required=False, help='AWS configuration profile name') +@click.option('--timeout', required=False, default=300, type=int, help='Amount of seconds to wait for deployment before command fails (default: 300). To disable timeout (fire and forget) set to -1') @click.option('--diff/--no-diff', default=True, help='Print which values were changed in the task definition (default: --diff)') @click.option('--exclusive-env', is_flag=True, default=False, help='Set the given environment variables exclusively and remove all other pre-existing env variables from all containers') @click.option('--exclusive-secrets', is_flag=True, default=False, help='Set the given secrets exclusively and remove all other pre-existing secrets from all containers') -def run(cluster, task, count, tag, image, command, env, secret, role, region, access_key_id, secret_access_key, profile, diff, exclusive_env, exclusive_secrets): +def run(cluster, task, count, tag, image, command, env, secret, role, region, access_key_id, secret_access_key, profile, timeout, diff, exclusive_env, exclusive_secrets): """ Run a one-off task. @@ -204,11 +205,46 @@ def run(cluster, task, count, tag, image, command, env, secret, role, region, ac click.secho('- %s' % started_task['taskArn'], fg='green') click.secho(' ') + exit_code = wait_for_task( + action=action, + timeout=timeout, + title='Running task' + ) + exit(exit_code) + except EcsError as e: click.secho('%s\n' % str(e), fg='red', err=True) exit(1) +def wait_for_task(action: RunAction, timeout, title): + click.secho(title, nl=False) + waiting_timeout = datetime.now() + timedelta(seconds=timeout) + + if timeout == -1: + waiting = False + else: + waiting = True + + exit_code = 0 + + while waiting and datetime.now() < waiting_timeout: + click.secho('.', nl=False) + waiting = False + + for started_task in action.started_tasks: + task = action.get_task(started_task[u'taskArn']) + if task[u'lastStatus'] != u'STOPPED': + waiting = True + else: + for container in task[u'containers']: + exit_code = exit_code or container[u'exitCode'] + if waiting: + sleep(1) + + return exit_code + + def wait_for_finish(action, timeout, title, success_message, failure_message, ignore_warnings): click.secho(title, nl=False) diff --git a/ecs_deploy/ecs.py b/ecs_deploy/ecs.py index f2ad0d8..b8e3279 100644 --- a/ecs_deploy/ecs.py +++ b/ecs_deploy/ecs.py @@ -453,6 +453,13 @@ def get_service(self): service_definition=services_definition[u'services'][0] ) + def get_task(self, task_arn): + tasks_details = self._client.describe_tasks( + cluster_name=self._cluster_name, + task_arns=[task_arn] + ) + return tasks_details[u'tasks'][0] + def get_current_task_definition(self, service): return self.get_task_definition(service.task_definition) diff --git a/tests/test_cli.py b/tests/test_cli.py index 16a3e28..d8dd616 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -602,11 +602,10 @@ def test_scale_without_credentials(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_task(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task')) - assert not result.exception - assert result.exit_code == 0 + assert result.exit_code == 123 assert u"Successfully started 2 instances of task: test-task:2" in result.output assert u"- arn:foo:bar" in result.output @@ -615,10 +614,9 @@ def test_run_task(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_with_role_arn(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task:1', '2', '-r', 'arn:new:role')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed role_arn to: "arn:new:role" (was: "arn:test:role:1")' in result.output assert u"Creating new task definition revision" in result.output @@ -630,10 +628,9 @@ def test_run_with_role_arn(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_new_tag(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-t', 'latest')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u"Creating new task definition revision" in result.output assert u'Changed image of container "webserver" to: "webserver:latest" (was: "webserver:123")' in result.output @@ -646,10 +643,9 @@ def test_run_new_tag(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_one_new_image(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-i', 'application', 'application:latest')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u"Creating new task definition revision" in result.output assert u'Changed image of container "application" to: "application:latest" (was: "application:123")' in result.output @@ -661,11 +657,10 @@ def test_run_one_new_image(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_two_new_images(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-i', 'application', 'application:latest', '-i', 'webserver', 'webserver:latest')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u"Creating new task definition revision" in result.output assert u'Changed image of container "webserver" to: "webserver:latest" (was: "webserver:123")' in result.output @@ -677,10 +672,9 @@ def test_run_two_new_images(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_one_new_command(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-c', 'application', 'date')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed command of container "application" to: "date" (was: "run")' in result.output assert u"Successfully started 2 instances of task: test-task:2" in result.output @@ -690,11 +684,10 @@ def test_run_one_new_command(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_one_new_environment_variable(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'application', 'foo', 'bar')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed environment "foo" of container "application" to: "bar"' in result.output @@ -705,11 +698,10 @@ def test_run_one_new_environment_variable(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_change_environment_variable_empty_string(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'application', 'foo', '')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed environment "foo" of container "application" to: ""' in result.output @@ -720,11 +712,10 @@ def test_run_change_environment_variable_empty_string(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_new_empty_environment_variable(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'application', 'new', '')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed environment "new" of container "application" to: ""' in result.output @@ -735,11 +726,10 @@ def test_run_new_empty_environment_variable(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_empty_environment_variable_again(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'webserver', 'empty', '')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" not in result.output assert u'Changed environment' not in result.output @@ -750,11 +740,10 @@ def test_run_empty_environment_variable_again(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_previously_empty_environment_variable_with_value(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'webserver', 'empty', 'not-empty')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed environment "empty" of container "webserver" to: "not-empty"' in result.output @@ -765,11 +754,10 @@ def test_run_previously_empty_environment_variable_with_value(get_client, runner @patch('ecs_deploy.cli.get_client') def test_run_exclusive_environment(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'webserver', 'new-env', 'new-value', '--exclusive-env')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed environment "new-env" of container "webserver" to: "new-value"' in result.output @@ -786,11 +774,10 @@ def test_run_exclusive_environment(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_exclusive_secret(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-s', 'webserver', 'new-secret', 'new-place', '--exclusive-secrets')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed secret "new-secret" of container "webserver" to: "new-place"' in result.output @@ -807,13 +794,12 @@ def test_run_exclusive_secret(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_one_new_secret_variable(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-s', 'application', 'baz', 'qux', '-s', 'webserver', 'baz', 'quux')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" in result.output assert u'Changed secret "baz" of container "application" to: "qux"' in result.output @@ -826,11 +812,10 @@ def test_run_one_new_secret_variable(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_without_changing_environment_value(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'webserver', 'foo', 'bar')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" not in result.output assert u'Changed environment' not in result.output @@ -841,11 +826,10 @@ def test_run_without_changing_environment_value(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_without_changing_secrets_value(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-s', 'webserver', 'baz', 'qux')) - assert result.exit_code == 0 - assert not result.exception + assert result.exit_code == 123 assert u"Using task definition: test-task" not in result.output assert u'Changed secrets' not in result.output @@ -856,11 +840,10 @@ def test_run_without_changing_secrets_value(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_task_without_diff(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, (CLUSTER_NAME, 'test-task', '2', '-e', 'application', 'foo', 'bar', '--no-diff')) - assert not result.exception - assert result.exit_code == 0 + assert result.exit_code == 123 assert u"Using task definition: test-task" not in result.output assert u'Changed environment' not in result.output @@ -888,7 +871,7 @@ def test_run_task_without_credentials(get_client, runner): @patch('ecs_deploy.cli.get_client') def test_run_task_with_invalid_cluster(get_client, runner): - get_client.return_value = EcsTestClient('acces_key', 'secret_key') + get_client.return_value = EcsTestClient('acces_key', 'secret_key', task_status=u'STOPPED') result = runner.invoke(cli.run, ('unknown-cluster', 'test-task')) assert result.exit_code == 1 assert result.output == u'An error occurred (ClusterNotFoundException) when calling the RunTask operation: Cluster not found.\n\n' diff --git a/tests/test_ecs.py b/tests/test_ecs.py index c93372b..50c9d3a 100644 --- a/tests/test_ecs.py +++ b/tests/test_ecs.py @@ -79,7 +79,9 @@ u'overrides': {u'containerOverrides': []}, u'lastStatus': u'RUNNING', u'desiredStatus': u'RUNNING', - u'containers': TASK_DEFINITION_CONTAINERS_1, + u'containers': [{ + u'exitCode': 123, + }], u'startedBy': SERVICE_ARN } @@ -802,7 +804,7 @@ def test_run_action_run(client, task_definition): class EcsTestClient(object): def __init__(self, access_key_id=None, secret_access_key=None, region=None, profile=None, deployment_errors=False, client_errors=False, - wait=0): + wait=0, task_status=u'RUNNING'): super(EcsTestClient, self).__init__() self.access_key_id = access_key_id self.secret_access_key = secret_access_key @@ -811,6 +813,7 @@ def __init__(self, access_key_id=None, secret_access_key=None, region=None, self.deployment_errors = deployment_errors self.client_errors = client_errors self.wait_until = datetime.now() + timedelta(seconds=wait) + self.task_status = task_status def describe_services(self, cluster_name, service_name): if not self.access_key_id or not self.secret_access_key: @@ -841,7 +844,10 @@ def list_tasks(self, cluster_name, service_name): return deepcopy(RESPONSE_LIST_TASKS_0) def describe_tasks(self, cluster_name, task_arns): - return deepcopy(RESPONSE_DESCRIBE_TASKS) + tasks = deepcopy(RESPONSE_DESCRIBE_TASKS) + for task in tasks['tasks']: + task[u'lastStatus'] = self.task_status + return tasks def register_task_definition(self, family, containers, volumes, role_arn, additional_properties): return deepcopy(RESPONSE_TASK_DEFINITION_2)