Completed
Pull Request — develop (#16)
by
unknown
01:57
created

wait_for_finish()   C

Complexity

Conditions 7

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7.0572

Importance

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