Completed
Branch feature/ignore-task-placement-... (472d66)
by Fabian
01:48
created

wait_for_finish()   B

Complexity

Conditions 3

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 28
ccs 14
cts 14
cp 1
cc 3
crap 3
rs 8.8571
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 getpass
8 1
from datetime import datetime, timedelta
9
10 1
from ecs_deploy.ecs import DeployAction, ScaleAction, RunAction, EcsClient, \
11
    EcsActionError
12 1
from ecs_deploy.newrelic import Deployment
13
14
15 1
@click.group()
16
def ecs():  # pragma: no cover
17
    pass
18
19
20 1
def get_client(access_key_id, secret_access_key, region, profile):
21 1
    return EcsClient(access_key_id, secret_access_key, region, profile)
22
23
24 1
@click.command()
25 1
@click.argument('cluster')
26 1
@click.argument('service')
27 1
@click.option('-t', '--tag', help='Changes the tag for ALL container images')
28 1
@click.option('-i', '--image', type=(str, str), multiple=True, help='Overwrites the image for a container: <container> <image>')
29 1
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
30 1
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
31 1
@click.option('-r', '--role', type=str, help='Sets the task\'s role ARN: <task role ARN>')
32 1
@click.option('--region', required=False, help='AWS region')
33 1
@click.option('--access-key-id', required=False, help='AWS access key id')
34 1
@click.option('--secret-access-key', required=False, help='AWS secret access yey')
35 1
@click.option('--profile', required=False, help='AWS configuration profile')
36 1
@click.option('--timeout', required=False, default=300, type=int, help='Amount of seconds to wait for deployment before command fails (default: 300)')
37 1
@click.option('--ignore-warnings', is_flag=True, help='Do not fail deployment, if warnings occur (e.g. insufficient mempory, CPU or port already in use.')
38 1
@click.option('--newrelic-apikey', required=False, help='New Relic API Key for recording the deployment')
39 1
@click.option('--newrelic-appid', required=False, help='New Relic App ID for recording the deployment')
40 1
@click.option('--comment', required=False, help='Description/comment for recording the deployment')
41 1
@click.option('--user', required=False, help='User who executes the deployment (used for recording)')
42
def deploy(cluster, service, tag, image, command, env, role, access_key_id, secret_access_key, region, profile, timeout,
43
           newrelic_apikey, newrelic_appid, comment, user, ignore_warnings):
44
    """
45
    Redeploy or modify a service.
46
47
    \b
48
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
49
    SERVICE is the name of your service (e.g. 'my-app') within ECS.
50
51
    When not giving any other options, the task definition will not be changed. It will just be duplicated, so that
52
    all container images will be pulled and redeployed.
53
    """
54
55 1
    try:
56 1
        client = get_client(access_key_id, secret_access_key, region, profile)
57 1
        deployment = DeployAction(client, cluster, service)
58 1
        task_definition = deployment.get_current_task_definition(deployment.service)
59
60 1
        task_definition.set_images(tag, **{key: value for (key, value) in image})
61 1
        task_definition.set_commands(**{key: value for (key, value) in command})
62 1
        task_definition.set_environment(env)
63 1
        task_definition.set_role_arn(role)
64 1
        print_diff(task_definition)
65
66 1
        click.secho('Creating new task definition revision')
67 1
        new_task_definition = deployment.update_task_definition(task_definition)
68 1
        click.secho('Successfully created revision: %d' % new_task_definition.revision, fg='green')
69 1
        click.secho('Successfully deregistered revision: %d\n' % task_definition.revision, fg='green')
70
71 1
        record_deployment(tag, newrelic_apikey, newrelic_appid, comment, user)
72
73 1
        click.secho('Updating service')
74 1
        deployment.deploy(new_task_definition)
75 1
        click.secho('Successfully changed task definition to: %s:%s\n' %
76
                    (new_task_definition.family, new_task_definition.revision), fg='green')
77
78 1
        wait_for_finish(deployment, timeout, 'Deploying task definition', 'Deployment successful', 'Deployment failed', ignore_warnings)
79
80 1
    except Exception as e:
81 1
        click.secho('%s\n' % str(e), fg='red', err=True)
82 1
        exit(1)
83
84
85 1
@click.command()
86 1
@click.argument('cluster')
87 1
@click.argument('service')
88 1
@click.argument('desired_count', type=int)
89 1
@click.option('--region', help='AWS region')
90 1
@click.option('--access-key-id', help='AWS access key id')
91 1
@click.option('--secret-access-key', help='AWS secret access yey')
92 1
@click.option('--profile', help='AWS configuration profile')
93 1
@click.option('--timeout', default=300, type=int, help='AWS configuration profile')
94 1
@click.option('--ignore-warnings', is_flag=True, help='Do not fail deployment, if warnings occur (e.g. insufficient mempory, CPU or port already in use.')
95
def scale(cluster, service, desired_count, access_key_id, secret_access_key, region, profile, timeout, ignore_warnings):
96
    """
97
    Scale a service up or down.
98
99
    \b
100
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
101
    SERVICE is the name of your service (e.g. 'my-app') within ECS.
102
    DESIRED_COUNT is the number of tasks your service should run.
103
    """
104 1
    try:
105 1
        client = get_client(access_key_id, secret_access_key, region, profile)
106 1
        scaling = ScaleAction(client, cluster, service)
107 1
        click.secho('Updating service')
108 1
        scaling.scale(desired_count)
109 1
        click.secho('Successfully changed desired count to: %s\n' % desired_count, fg='green')
110 1
        wait_for_finish(scaling, timeout, 'Scaling service', 'Scaling successful', 'Scaling failed', ignore_warnings)
111
112 1
    except Exception as e:
113 1
        click.secho('%s\n' % str(e), fg='red', err=True)
114 1
        exit(1)
115
116
117 1
@click.command()
118 1
@click.argument('cluster')
119 1
@click.argument('task')
120 1
@click.argument('count', required=False, default=1)
121 1
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
122 1
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
123 1
@click.option('--region', help='AWS region')
124 1
@click.option('--access-key-id', help='AWS access key id')
125 1
@click.option('--secret-access-key', help='AWS secret access yey')
126 1
@click.option('--profile', help='AWS configuration profile')
127
def run(cluster, task, count, command, env, region, access_key_id, secret_access_key, profile):
128
    """
129
    Run a one-off task.
130
131
    \b
132
    CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
133
    TASK is the name of your task definintion (e.g. 'mytask') within ECS.
134
    COMMAND is the number of tasks your service should run.
135
    """
136 1
    try:
137 1
        client = get_client(access_key_id, secret_access_key, region, profile)
138 1
        action = RunAction(client, cluster)
139
140 1
        task_definition = action.get_task_definition(task)
141 1
        task_definition.set_commands(**{key: value for (key, value) in command})
142 1
        task_definition.set_environment(env)
143 1
        print_diff(task_definition, 'Using task definition: %s' % task)
144
145 1
        action.run(task_definition, count, 'ECS Deploy')
146
147 1
        click.secho('Successfully started %d instances of task: %s' % (len(action.started_tasks), task_definition.family_revision), fg='green')
148 1
        for started_task in action.started_tasks:
149 1
            click.secho('- %s' % started_task['taskArn'], fg='green')
150 1
        click.secho(' ')
151
152 1
    except Exception as e:
153 1
        click.secho('%s\n' % str(e), fg='red', err=True)
154 1
        exit(1)
155
156
157 1
def wait_for_finish(action, timeout, title, success_message, failure_message, ignore_warnings):
158 1
    click.secho(title, nl=False)
159 1
    waiting = True
160 1
    waiting_timeout = datetime.now() + timedelta(seconds=timeout)
161 1
    service = action.get_service()
162 1
    inspected_until = None
163 1
    while waiting and datetime.now() < waiting_timeout:
164 1
        click.secho('.', nl=False)
165 1
        sleep(1)
166 1
        service = action.get_service()
167 1
        inspected_until = inspect_errors(
168
            service=service,
169
            failure_message=failure_message,
170
            ignore_warnings=ignore_warnings,
171
            since=inspected_until,
172
            timeout=False
173
        )
174 1
        waiting = not action.is_deployed(service)
175
176 1
    inspect_errors(
177
        service=service,
178
        failure_message=failure_message,
179
        ignore_warnings=ignore_warnings,
180
        since=inspected_until,
181
        timeout=waiting
182
    )
183
184 1
    click.secho('\n%s\n' % success_message, fg='green')
185
186
187 1
def record_deployment(revision, newrelic_apikey, newrelic_appid, comment, user):
188 1
    newrelic_apikey = getenv('NEW_RELIC_API_KEY', newrelic_apikey)
189 1
    newrelic_appid = getenv('NEW_RELIC_APP_ID', newrelic_appid)
190
191 1
    if not revision or not newrelic_apikey or not newrelic_appid:
192 1
        return False
193
194 1
    user = user or getpass.getuser()
195
196 1
    click.secho('Recording deployment in New Relic', nl=False)
197
198 1
    deployment = Deployment(newrelic_apikey, newrelic_appid, user)
199 1
    deployment.deploy(revision, '', comment)
200
201 1
    click.secho('\nDone\n', fg='green')
202
203 1
    return True
204
205
206 1
def print_diff(task_definition, title='Updating task definition'):
207 1
    if task_definition.diff:
208 1
        click.secho(title)
209 1
        for diff in task_definition.diff:
210 1
            click.secho(str(diff), fg='blue')
211 1
        click.secho('')
212
213
214 1
def inspect_errors(service, failure_message, ignore_warnings, since, timeout):
215 1
    error = False
216 1
    last_error_timestamp = since
217
218 1
    warnings = service.get_warnings(since)
219 1
    for timestamp in warnings:
220 1
        message = warnings[timestamp]
221 1
        click.secho('')
222 1
        if ignore_warnings:
223 1
            last_error_timestamp = timestamp
224 1
            click.secho('%s\nWARNING: %s' % (timestamp, message), fg='yellow', err=False)
225 1
            click.secho('Continuing.', nl=False)
226
        else:
227 1
            click.secho('%s\nERROR: %s\n' % (timestamp, message), fg='red', err=True)
228 1
            error = True
229
230 1
    if service.older_errors:
231 1
        click.secho('')
232 1
        click.secho('Older errors', fg='yellow', err=True)
233 1
        for timestamp in service.older_errors:
234 1
            click.secho('%s\n%s\n' % (timestamp, service.older_errors[timestamp]), fg='yellow', err=True)
235
236 1
    if timeout:
237 1
        error = True
238 1
        failure_message += ' (timeout)'
239
240 1
    if error:
241 1
        raise EcsActionError(failure_message)
242
243 1
    return last_error_timestamp
244
245
246 1
ecs.add_command(deploy)
247 1
ecs.add_command(scale)
248 1
ecs.add_command(run)
249
250
if __name__ == '__main__':  # pragma: no cover
251
    ecs()
252