Passed
Pull Request — master (#151)
by Fabian
18:38 queued 08:37
created

ecs_deploy.cli.print_diff()   A

Complexity

Conditions 3

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 2
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 3
rs 10
c 0
b 0
f 0
1 1
from __future__ import print_function, absolute_import
2
3 1
from os import getenv
4 1
from time import sleep
5
6 1
import click
7 1
import json
8 1
import getpass
9 1
from datetime import datetime, timedelta
10
from ecs_deploy import VERSION
11 1
from ecs_deploy.ecs import DeployAction, ScaleAction, RunAction, EcsClient, DiffAction, \
12 1
    TaskPlacementError, EcsError, UpdateAction, LAUNCH_TYPE_EC2, LAUNCH_TYPE_FARGATE
13
from ecs_deploy.newrelic import Deployment, NewRelicException
14 1
from ecs_deploy.slack import SlackNotification
15 1
16
17
@click.group()
18 1
@click.version_option(version=VERSION, prog_name='ecs-deploy')
19 1
def ecs():  # pragma: no cover
20
    pass
21
22
23
def get_client(access_key_id, secret_access_key, region, profile):
24 1
    return EcsClient(access_key_id, secret_access_key, region, profile)
25 1
26
27
@click.command()
28 1
@click.argument('cluster')
29 1
@click.argument('service')
30 1
@click.option('-t', '--tag', help='Changes the tag for ALL container images')
31 1
@click.option('-i', '--image', type=(str, str), multiple=True, help='Overwrites the image for a container: <container> <image>')
32 1
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
33 1
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
34 1
@click.option('--env-file', type=(str, str), default=((None, None),), multiple=True, required=False, help='Load environment variables from .env-file')
35 1
@click.option('-s', '--secret', type=(str, str, str), multiple=True, help='Adds or changes a secret environment variable from the AWS Parameter Store (Not available for Fargate): <container> <name> <parameter name>')
36 1
@click.option('-r', '--role', type=str, help='Sets the task\'s role ARN: <task role ARN>')
37 1
@click.option('-x', '--execution-role', type=str, help='Sets the execution\'s role ARN: <execution role ARN>')
38 1
@click.option('--task', type=str, help='Task definition to be deployed. Can be a task ARN or a task family with optional revision')
39 1
@click.option('--region', required=False, help='AWS region (e.g. eu-central-1)')
40 1
@click.option('--access-key-id', required=False, help='AWS access key id')
41 1
@click.option('--secret-access-key', required=False, help='AWS secret access key')
42 1
@click.option('--profile', required=False, help='AWS configuration profile name')
43 1
@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')
44 1
@click.option('--ignore-warnings', is_flag=True, help='Do not fail deployment on warnings (port already in use or insufficient memory/CPU)')
45 1
@click.option('--newrelic-apikey', required=False, help='New Relic API Key for recording the deployment. Can also be defined via environment variable NEW_RELIC_API_KEY')
46 1
@click.option('--newrelic-appid', required=False, help='New Relic App ID for recording the deployment. Can also be defined via environment variable NEW_RELIC_APP_ID')
47 1
@click.option('--newrelic-region', required=False, help='New Relic region: US or EU (default: US). Can also be defined via environment variable NEW_RELIC_REGION')
48 1
@click.option('--newrelic-revision', required=False, help='New Relic revision for recording the deployment (default: --tag value). Can also be defined via environment variable NEW_RELIC_REVISION')
49 1
@click.option('--comment', required=False, help='Description/comment for recording the deployment')
50 1
@click.option('--user', required=False, help='User who executes the deployment (used for recording)')
51 1
@click.option('--diff/--no-diff', default=True, help='Print which values were changed in the task definition (default: --diff)')
52 1
@click.option('--deregister/--no-deregister', default=True, help='Deregister or keep the old task definition (default: --deregister)')
53 1
@click.option('--rollback/--no-rollback', default=False, help='Rollback to previous revision, if deployment failed (default: --no-rollback)')
54 1
@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')
55 1
@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')
56 1
@click.option('--sleep-time', default=1, type=int, help='Amount of seconds to wait between each check of the service (default: 1)')
57 1
@click.option('--slack-url', required=False, help='Webhook URL of the Slack integration. Can also be defined via environment variable SLACK_URL')
58 1
@click.option('--slack-service-match', default=".*", required=False, help='A regular expression for defining, which services should be notified. (default: .* =all). Can also be defined via environment variable SLACK_SERVICE_MATCH')
59
def deploy(cluster, service, tag, image, command, env, env_file, secret, role, execution_role, task, region, access_key_id, secret_access_key, profile, timeout, newrelic_apikey, newrelic_appid, newrelic_region, newrelic_revision, comment, user, ignore_warnings, diff, deregister, rollback, exclusive_env, exclusive_secrets, sleep_time, slack_url, slack_service_match='.*'):
60
    """
61
    Redeploy or modify a service.
62
63
    \b
64
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
65
    SERVICE is the name of your service (e.g. 'my-app') within ECS.
66
67
    When not giving any other options, the task definition will not be changed.
68
    It will just be duplicated, so that all container images will be pulled
69
    and redeployed.
70
    """
71 1
72 1
    try:
73 1
        client = get_client(access_key_id, secret_access_key, region, profile)
74
        deployment = DeployAction(client, cluster, service)
75 1
76 1
        td = get_task_definition(deployment, task)
77 1
        td.set_images(tag, **{key: value for (key, value) in image})
78 1
        td.set_commands(**{key: value for (key, value) in command})
79 1
        td.set_environment(env, exclusive_env, env_file)
80 1
        td.set_secrets(secret, exclusive_secrets)
81 1
        td.set_role_arn(role)
82
        td.set_execution_role_arn(execution_role)
83 1
84
        slack = SlackNotification(
85
            getenv('SLACK_URL', slack_url),
86
            getenv('SLACK_SERVICE_MATCH', slack_service_match)
87 1
        )
88
        slack.notify_start(cluster, tag, td, comment, user, service=service)
89 1
90
        click.secho('Deploying based on task definition: %s\n' % td.family_revision)
91 1
92 1
        if diff:
93
            print_diff(td)
94 1
95
        new_td = create_task_definition(deployment, td)
96 1
97 1
        try:
98
            deploy_task_definition(
99
                deployment=deployment,
100
                task_definition=new_td,
101
                title='Deploying new task definition',
102
                success_message='Deployment successful',
103
                failure_message='Deployment failed',
104
                timeout=timeout,
105
                deregister=deregister,
106
                previous_task_definition=td,
107
                ignore_warnings=ignore_warnings,
108
                sleep_time=sleep_time
109
            )
110 1
111 1
        except TaskPlacementError as e:
112 1
            slack.notify_failure(cluster, str(e), service=service)
113 1
            if rollback:
114 1
                click.secho('%s\n' % str(e), fg='red', err=True)
115 1
                rollback_task_definition(deployment, td, new_td, sleep_time=sleep_time)
116
                exit(1)
117 1
            else:
118
                raise
119 1
120
        record_deployment(tag, newrelic_apikey, newrelic_appid, newrelic_region, newrelic_revision, comment, user)
121 1
122
        slack.notify_success(cluster, td.revision, service=service)
123 1
124 1
    except (EcsError, NewRelicException) as e:
125 1
        click.secho('%s\n' % str(e), fg='red', err=True)
126
        exit(1)
127
128 1
129 1
@click.command()
130 1
@click.argument('cluster')
131 1
@click.argument('task')
132 1
@click.argument('rule')
133 1
@click.option('-i', '--image', type=(str, str), multiple=True, help='Overwrites the image for a container: <container> <image>')
134 1
@click.option('-t', '--tag', help='Changes the tag for ALL container images')
135 1
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
136 1
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
137 1
@click.option('--env-file', type=(str, str), default=((None, None),), multiple=True, required=False, help='Load environment variables from .env-file')
138 1
@click.option('-r', '--role', type=str, help='Sets the task\'s role ARN: <task role ARN>')
139 1
@click.option('--region', help='AWS region (e.g. eu-central-1)')
140 1
@click.option('--access-key-id', help='AWS access key id')
141 1
@click.option('--secret-access-key', help='AWS secret access key')
142 1
@click.option('--newrelic-apikey', required=False, help='New Relic API Key for recording the deployment. Can also be defined via environment variable NEW_RELIC_API_KEY')
143 1
@click.option('--newrelic-appid', required=False, help='New Relic App ID for recording the deployment. Can also be defined via environment variable NEW_RELIC_APP_ID')
144 1
@click.option('--newrelic-region', required=False, help='New Relic region: US or EU (default: US). Can also be defined via environment variable NEW_RELIC_REGION')
145 1
@click.option('--newrelic-revision', required=False, help='New Relic revision for recording the deployment (default: --tag value). Can also be defined via environment variable NEW_RELIC_REVISION')
146 1
@click.option('--comment', required=False, help='Description/comment for recording the deployment')
147 1
@click.option('--user', required=False, help='User who executes the deployment (used for recording)')
148 1
@click.option('--profile', help='AWS configuration profile name')
149 1
@click.option('--diff/--no-diff', default=True, help='Print what values were changed in the task definition')
150 1
@click.option('--deregister/--no-deregister', default=True, help='Deregister or keep the old task definition (default: --deregister)')
151
@click.option('--rollback/--no-rollback', default=False, help='Rollback to previous revision, if deployment failed (default: --no-rollback)')
152
@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')
153
@click.option('--slack-url', required=False, help='Webhook URL of the Slack integration. Can also be defined via environment variable SLACK_URL')
154
@click.option('--slack-service-match', default=".*", required=False, help='A regular expression for defining, deployments of which crons should be notified. (default: .* =all). Can also be defined via environment variable SLACK_SERVICE_MATCH')
155
def cron(cluster, task, rule, image, tag, command, env, env_file, role, region, access_key_id, secret_access_key, newrelic_apikey, newrelic_appid, newrelic_region, newrelic_revision, comment, user, profile, diff, deregister, rollback, exclusive_env, slack_url, slack_service_match):
156
    """
157
    Update a scheduled task.
158
159
    \b
160 1
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
161 1
    TASK is the name of your task definition (e.g. 'my-task') within ECS.
162 1
    RULE is the name of the rule to use the new task definition.
163
    """
164 1
    try:
165 1
        client = get_client(access_key_id, secret_access_key, region, profile)
166
        action = RunAction(client, cluster)
167 1
168 1
        td = action.get_task_definition(task)
169 1
        click.secho('Update task definition based on: %s\n' % td.family_revision)
170 1
171
        td.set_images(tag, **{key: value for (key, value) in image})
172 1
        td.set_commands(**{key: value for (key, value) in command})
173
        td.set_environment(env, exclusive_env, env_file)
174
        td.set_role_arn(role)
175
176 1
        slack = SlackNotification(
177
            getenv('SLACK_URL', slack_url),
178 1
            getenv('SLACK_SERVICE_MATCH', slack_service_match)
179 1
        )
180
        slack.notify_start(cluster, tag, td, comment, user, rule=rule)
181 1
182
        if diff:
183 1
            print_diff(td)
184
185
        new_td = create_task_definition(action, td)
186
187
        client.update_rule(
188 1
            cluster=cluster,
189 1
            rule=rule,
190
            task_definition=new_td
191 1
        )
192
        click.secho('Updating scheduled task')
193 1
        click.secho('Successfully updated scheduled task %s\n' % rule, fg='green')
194
195 1
        slack.notify_success(cluster, td.revision, rule=rule)
196 1
197
        record_deployment(tag, newrelic_apikey, newrelic_appid, newrelic_region, newrelic_revision, comment, user)
198 1
199 1
        if deregister:
200 1
            deregister_task_definition(action, td)
201
202
    except EcsError as e:
203 1
        click.secho('%s\n' % str(e), fg='red', err=True)
204 1
        exit(1)
205 1
206 1
207 1
@click.command()
208 1
@click.argument('task')
209 1
@click.option('-i', '--image', type=(str, str), multiple=True, help='Overwrites the image for a container: <container> <image>')
210 1
@click.option('-t', '--tag', help='Changes the tag for ALL container images')
211 1
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
212 1
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
213 1
@click.option('--env-file', type=(str, str), default=((None, None),), multiple=True, required=False, help='Load environment variables from .env-file')
214 1
@click.option('-s', '--secret', type=(str, str, str), multiple=True, help='Adds or changes a secret environment variable from the AWS Parameter Store (Not available for Fargate): <container> <name> <parameter name>')
215 1
@click.option('-r', '--role', type=str, help='Sets the task\'s role ARN: <task role ARN>')
216 1
@click.option('--region', help='AWS region (e.g. eu-central-1)')
217 1
@click.option('--access-key-id', help='AWS access key id')
218 1
@click.option('--secret-access-key', help='AWS secret access key')
219
@click.option('--profile', help='AWS configuration profile name')
220
@click.option('--diff/--no-diff', default=True, help='Print what values were changed in the task definition')
221
@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')
222
@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')
223
@click.option('--deregister/--no-deregister', default=True, help='Deregister or keep the old task definition (default: --deregister)')
224
def update(task, image, tag, command, env, env_file, secret, role, region, access_key_id, secret_access_key, profile, diff, exclusive_env, exclusive_secrets, deregister):
225
    """
226 1
    Update a task definition.
227 1
228 1
    \b
229
    TASK is the name of your task definition family (e.g. 'my-task') within ECS.
230 1
    """
231 1
    try:
232
        client = get_client(access_key_id, secret_access_key, region, profile)
233 1
        action = UpdateAction(client)
234 1
235 1
        td = action.get_task_definition(task)
236 1
        click.secho('Update task definition based on: %s\n' % td.family_revision)
237 1
238
        td.set_images(tag, **{key: value for (key, value) in image})
239 1
        td.set_commands(**{key: value for (key, value) in command})
240 1
        td.set_environment(env, exclusive_env, env_file)
241
        td.set_secrets(secret, exclusive_secrets)
242 1
        td.set_role_arn(role)
243
244 1
        if diff:
245 1
            print_diff(td)
246
247 1
        create_task_definition(action, td)
248 1
249 1
        if deregister:
250
            deregister_task_definition(action, td)
251
252 1
    except EcsError as e:
253 1
        click.secho('%s\n' % str(e), fg='red', err=True)
254 1
        exit(1)
255 1
256 1
257 1
@click.command()
258 1
@click.argument('cluster')
259 1
@click.argument('service')
260 1
@click.argument('desired_count', type=int)
261 1
@click.option('--region', help='AWS region (e.g. eu-central-1)')
262 1
@click.option('--access-key-id', help='AWS access key id')
263
@click.option('--secret-access-key', help='AWS secret access key')
264
@click.option('--profile', help='AWS configuration profile name')
265
@click.option('--timeout', 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')
266
@click.option('--ignore-warnings', is_flag=True, help='Do not fail deployment on warnings (port already in use or insufficient memory/CPU)')
267
@click.option('--sleep-time', default=1, type=int, help='Amount of seconds to wait between each check of the service (default: 1)')
268
def scale(cluster, service, desired_count, access_key_id, secret_access_key, region, profile, timeout, ignore_warnings, sleep_time):
269
    """
270
    Scale a service up or down.
271
272 1
    \b
273 1
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
274 1
    SERVICE is the name of your service (e.g. 'my-app') within ECS.
275 1
    DESIRED_COUNT is the number of tasks your service should run.
276 1
    """
277 1
    try:
278
        client = get_client(access_key_id, secret_access_key, region, profile)
279
        scaling = ScaleAction(client, cluster, service)
280
        click.secho('Updating service')
281 1
        scaling.scale(desired_count)
282
        click.secho(
283
            'Successfully changed desired count to: %s\n' % desired_count,
284
            fg='green'
285
        )
286
        wait_for_finish(
287
            action=scaling,
288
            timeout=timeout,
289
            title='Scaling service',
290
            success_message='Scaling successful',
291 1
            failure_message='Scaling failed',
292 1
            ignore_warnings=ignore_warnings,
293 1
            sleep_time=sleep_time
294
        )
295
296 1
    except EcsError as e:
297 1
        click.secho('%s\n' % str(e), fg='red', err=True)
298 1
        exit(1)
299 1
300 1
301 1
@click.command()
302 1
@click.argument('cluster')
303 1
@click.argument('task')
304 1
@click.argument('count', required=False, default=1)
305 1
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
306 1
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
307 1
@click.option('--env-file', type=(str, str), default=((None, None),), multiple=True, required=False, help='Load environment variables from .env-file')
308 1
@click.option('-s', '--secret', type=(str, str, str), multiple=True, help='Adds or changes a secret environment variable from the AWS Parameter Store (Not available for Fargate): <container> <name> <parameter name>')
309 1
@click.option('--launchtype', type=click.Choice([LAUNCH_TYPE_EC2, LAUNCH_TYPE_FARGATE]), default=LAUNCH_TYPE_EC2, help='ECS Launch type (default: EC2)')
310 1
@click.option('--subnet', type=str, multiple=True, help='A subnet ID to launch the task within. Required for launch type FARGATE (multiple values possible)')
311 1
@click.option('--securitygroup', type=str, multiple=True, help='A security group ID to launch the task within. Required for launch type FARGATE (multiple values possible)')
312 1
@click.option('--public-ip', is_flag=True, default=False, help='Should a public IP address be assigned to the task (default: False)')
313
@click.option('--platform-version', help='The version of the Fargate platform on which to run the task. Optional, FARGATE launch type only.', required=False)
314
@click.option('--region', help='AWS region (e.g. eu-central-1)')
315
@click.option('--access-key-id', help='AWS access key id')
316
@click.option('--secret-access-key', help='AWS secret access key')
317
@click.option('--profile', help='AWS configuration profile name')
318
@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')
319
@click.option('--diff/--no-diff', default=True, help='Print what values were changed in the task definition')
320
def run(cluster, task, count, command, env, env_file, secret, launchtype, subnet, securitygroup, public_ip, platform_version, region, access_key_id, secret_access_key, profile, exclusive_env, diff):
321
    """
322 1
    Run a one-off task.
323 1
324 1
    \b
325
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
326 1
    TASK is the name of your task definition (e.g. 'my-task') within ECS.
327 1
    COUNT is the number of tasks your service should run.
328 1
    """
329 1
    try:
330
        client = get_client(access_key_id, secret_access_key, region, profile)
331 1
        action = RunAction(client, cluster)
332 1
333
        td = action.get_task_definition(task)
334 1
        td.set_commands(**{key: value for (key, value) in command})
335
        td.set_environment(env, exclusive_env, env_file)
336 1
        td.set_secrets(secret)
337
338
        if diff:
339
            print_diff(td, 'Using task definition: %s' % task)
340
341
        action.run(td, count, 'ECS Deploy', launchtype, subnet, securitygroup, public_ip, platform_version)
342
343
        click.secho(
344 1
            'Successfully started %d instances of task: %s' % (
345 1
                len(action.started_tasks),
346 1
                td.family_revision
347
            ),
348 1
            fg='green'
349 1
        )
350 1
351
        for started_task in action.started_tasks:
352
            click.secho('- %s' % started_task['taskArn'], fg='green')
353 1
        click.secho(' ')
354 1
355 1
    except EcsError as e:
356 1
        click.secho('%s\n' % str(e), fg='red', err=True)
357 1
        exit(1)
358 1
359 1
360 1
@click.command()
361
@click.argument('task')
362
@click.argument('revision_a')
363
@click.argument('revision_b')
364
@click.option('--region', help='AWS region (e.g. eu-central-1)')
365
@click.option('--access-key-id', help='AWS access key id')
366
@click.option('--secret-access-key', help='AWS secret access key')
367
@click.option('--profile', help='AWS configuration profile name')
368
def diff(task, revision_a, revision_b, region, access_key_id, secret_access_key, profile):
369
    """
370 1
    Compare two task definition revisions.
371 1
372 1
    \b
373
    TASK is the name of your task definition (e.g. 'my-task') within ECS.
374 1
    COUNT is the number of tasks your service should run.
375 1
    """
376
377 1
    try:
378 1
        client = get_client(access_key_id, secret_access_key, region, profile)
379 1
        action = DiffAction(client)
380 1
381 1
        td_a = action.get_task_definition('%s:%s' % (task, revision_a))
382 1
        td_b = action.get_task_definition('%s:%s' % (task, revision_b))
383
384 1
        result = td_a.diff_raw(td_b)
385 1
        for difference in result:
386 1
            if difference[0] == 'add':
387 1
                click.secho('%s: %s' % (difference[0], difference[1]), fg='green')
388
                for added in difference[2]:
389 1
                    click.secho('    + %s: %s' % (added[0], json.dumps(added[1])), fg='green')
390 1
391 1
            if difference[0] == 'change':
392 1
                click.secho('%s: %s' % (difference[0], difference[1]), fg='yellow')
393
                click.secho('    - %s' % json.dumps(difference[2][0]), fg='red')
394 1
                click.secho('    + %s' % json.dumps(difference[2][1]), fg='green')
395 1
396 1
            if difference[0] == 'remove':
397
                click.secho('%s: %s' % (difference[0], difference[1]), fg='red')
398
                for removed in difference[2]:
399 1
                    click.secho('    - %s: %s' % removed, fg='red')
400
401 1
    except EcsError as e:
402 1
        click.secho('%s\n' % str(e), fg='red', err=True)
403 1
        exit(1)
404 1
405
406 1
def wait_for_finish(action, timeout, title, success_message, failure_message,
407 1
                    ignore_warnings, sleep_time=1):
408
    click.secho(title, nl=False)
409 1
    waiting_timeout = datetime.now() + timedelta(seconds=timeout)
410
    service = action.get_service()
411 1
    inspected_until = None
412 1
413 1
    if timeout == -1:
414 1
        waiting = False
415
    else:
416
        waiting = True
417
418
    while waiting and datetime.now() < waiting_timeout:
419
        click.secho('.', nl=False)
420
        service = action.get_service()
421 1
        inspected_until = inspect_errors(
422
            service=service,
423 1
            failure_message=failure_message,
424 1
            ignore_warnings=ignore_warnings,
425
            since=inspected_until,
426 1
            timeout=False
427
        )
428
        waiting = not action.is_deployed(service)
429
430
        if waiting:
431
            sleep(sleep_time)
432
433
    inspect_errors(
434 1
        service=service,
435
        failure_message=failure_message,
436
        ignore_warnings=ignore_warnings,
437 1
        since=inspected_until,
438
        timeout=waiting
439
    )
440 1
441 1
    click.secho('\n%s\n' % success_message, fg='green')
442
443 1
444
def deploy_task_definition(deployment, task_definition, title, success_message,
445
                           failure_message, timeout, deregister,
446
                           previous_task_definition, ignore_warnings, sleep_time):
447
    click.secho('Updating service')
448 1
    deployment.deploy(task_definition)
449
450 1
    message = 'Successfully changed task definition to: %s:%s\n' % (
451
        task_definition.family,
452
        task_definition.revision
453
    )
454
455
    click.secho(message, fg='green')
456
457
    wait_for_finish(
458
        action=deployment,
459
        timeout=timeout,
460 1
        title=title,
461 1
        success_message=success_message,
462
        failure_message=failure_message,
463
        ignore_warnings=ignore_warnings,
464 1
        sleep_time=sleep_time
465 1
    )
466 1
467
    if deregister:
468 1
        deregister_task_definition(deployment, previous_task_definition)
469 1
470
471
def get_task_definition(action, task):
472 1
    if task:
473 1
        task_definition = action.get_task_definition(task)
474 1
    else:
475
        task_definition = action.get_current_task_definition(action.service)
476 1
    return task_definition
477
478
479
def create_task_definition(action, task_definition):
480
    click.secho('Creating new task definition revision')
481 1
    new_td = action.update_task_definition(task_definition)
482
483
    click.secho(
484 1
        'Successfully created revision: %d\n' % new_td.revision,
485 1
        fg='green'
486 1
    )
487 1
488
    return new_td
489
490
491
def deregister_task_definition(action, task_definition):
492
    click.secho('Deregister task definition revision')
493 1
    action.deregister_task_definition(task_definition)
494 1
    click.secho(
495
        'Successfully deregistered revision: %d\n' % task_definition.revision,
496
        fg='green'
497
    )
498 1
499
500
def rollback_task_definition(deployment, old, new, timeout=600, sleep_time=1):
501
    click.secho(
502
        'Rolling back to task definition: %s\n' % old.family_revision,
503
        fg='yellow',
504
    )
505
    deploy_task_definition(
506
        deployment=deployment,
507
        task_definition=old,
508
        title='Deploying previous task definition',
509
        success_message='Rollback successful',
510 1
        failure_message='Rollback failed. Please check ECS Console',
511
        timeout=timeout,
512
        deregister=True,
513
        previous_task_definition=new,
514
        ignore_warnings=False,
515
        sleep_time=sleep_time
516 1
    )
517 1
    click.secho(
518 1
        'Deployment failed, but service has been rolled back to previous '
519 1
        'task definition: %s\n' % old.family_revision, fg='yellow', err=True
520
    )
521 1
522 1
523
def record_deployment(tag, api_key, app_id, region, revision, comment, user):
524 1
    api_key = getenv('NEW_RELIC_API_KEY', api_key)
525
    app_id = getenv('NEW_RELIC_APP_ID', app_id)
526 1
    region = getenv('NEW_RELIC_REGION', region)
527
    revision = getenv('NEW_RELIC_REVISION', revision) or tag
528 1
529 1
    if not revision or not api_key or not app_id:
530
        if api_key:
531 1
            click.secho('Missing required parameters for recording New Relic deployment.' \
532
                        'Please see https://github.com/fabfuel/ecs-deploy#new-relic')
533 1
        return False
534
535
    user = user or getpass.getuser()
536 1
537 1
    click.secho('Recording deployment in New Relic', nl=False)
538 1
539 1
    deployment = Deployment(api_key, app_id, user, region)
540 1
    deployment.deploy(revision, '', comment)
541 1
542
    click.secho('\nDone\n', fg='green')
543
544 1
    return True
545 1
546 1
547
def print_diff(task_definition, title='Updating task definition'):
548 1
    if task_definition.diff:
549 1
        click.secho(title)
550 1
        for diff in task_definition.diff:
551 1
            click.secho(str(diff), fg='blue')
552 1
        click.secho('')
553 1
554 1
555
def inspect_errors(service, failure_message, ignore_warnings, since, timeout):
556
    error = False
557
    last_error_timestamp = since
558
559 1
    warnings = service.get_warnings(since)
560
    for timestamp in warnings:
561 1
        message = warnings[timestamp]
562
        click.secho('')
563
        if ignore_warnings:
564
            last_error_timestamp = timestamp
565
            click.secho(
566 1
                text='%s\nWARNING: %s' % (timestamp, message),
567
                fg='yellow',
568 1
                err=False
569 1
            )
570 1
            click.secho('Continuing.', nl=False)
571 1
        else:
572 1
            click.secho(
573
                text='%s\nERROR: %s\n' % (timestamp, message),
574
                fg='red',
575
                err=True
576
            )
577
            error = True
578 1
579 1
    if service.older_errors:
580 1
        click.secho('')
581
        click.secho('Older errors', fg='yellow', err=True)
582 1
        for timestamp in service.older_errors:
583
            click.secho(
584 1
                text='%s\n%s\n' % (timestamp, service.older_errors[timestamp]),
585 1
                fg='yellow',
586
                err=True
587 1
            )
588
589
    if timeout:
590 1
        error = True
591 1
        failure_message += ' due to timeout. Please see: ' \
592 1
                           'https://github.com/fabfuel/ecs-deploy#timeout'
593 1
        click.secho('')
594 1
595 1
    if error:
596
        raise TaskPlacementError(failure_message)
597
598
    return last_error_timestamp
599
600
601
ecs.add_command(deploy)
602
ecs.add_command(scale)
603
ecs.add_command(run)
604
ecs.add_command(cron)
605
ecs.add_command(update)
606
ecs.add_command(diff)
607
608
if __name__ == '__main__':  # pragma: no cover
609
    ecs()
610