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

ecs_deploy.ecs.EcsClient.__init__()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 9
nop 6
dl 0
loc 9
ccs 3
cts 3
cp 1
crap 1
rs 9.95
c 0
b 0
f 0
1 1
from datetime import datetime
2 1
import json
3 1
import re
4
5 1
from boto3.session import Session
6 1
from botocore.exceptions import ClientError, NoCredentialsError
7 1
from dateutil.tz.tz import tzlocal
8 1
from dictdiffer import diff
9
10 1
JSON_LIST_REGEX = re.compile(r'^\[.*\]$')
11
12
# Python2 raises ValueError
13 1
try:
14 1
    JSONDecodeError = json.JSONDecodeError
15 1
except AttributeError:
16 1
    JSONDecodeError = ValueError
17
18
LAUNCH_TYPE_EC2 = 'EC2'
19 1
LAUNCH_TYPE_FARGATE = 'FARGATE'
20 1
21
22
def read_env_file(container_name,file):
23 1
    env_vars = []
24 1
    try:
25
        with open(file) as f:
26 1
            for line in f:
27
                if line.startswith('#') or not line.strip() or '=' not in line:
28
                    continue
29
                key, value = line.strip().split('=', 1)
30
                env_vars.append((container_name,key,value))
31 1
    except Exception as e:
32 1
        raise EcsTaskDefinitionCommandError(str(e))
33
    return tuple(env_vars)
34 1
35 1
36
class EcsClient(object):
37
    def __init__(self, access_key_id=None, secret_access_key=None,
38
                 region=None, profile=None, session_token=None):
39
        session = Session(aws_access_key_id=access_key_id,
40 1
                          aws_secret_access_key=secret_access_key,
41 1
                          aws_session_token=session_token,
42 1
                          region_name=region,
43
                          profile_name=profile)
44
        self.boto = session.client(u'ecs')
45
        self.events = session.client(u'events')
46
47
    def describe_services(self, cluster_name, service_name):
48 1
        return self.boto.describe_services(
49 1
            cluster=cluster_name,
50
            services=[service_name]
51
        )
52
53 1
    def describe_task_definition(self, task_definition_arn):
54 1
        try:
55
            return self.boto.describe_task_definition(
56
                taskDefinition=task_definition_arn,
57
                include=[
58
                    'TAGS',
59 1
                ]
60 1
            )
61
        except ClientError:
62 1
            raise UnknownTaskDefinitionError(
63
                u'Unknown task definition arn: %s' % task_definition_arn
64 1
            )
65 1
66
    def list_tasks(self, cluster_name, service_name):
67 1
        return self.boto.list_tasks(
68
            cluster=cluster_name,
69
            serviceName=service_name
70
        )
71
72
    def describe_tasks(self, cluster_name, task_arns):
73
        return self.boto.describe_tasks(cluster=cluster_name, tasks=task_arns)
74
75
    def register_task_definition(self, family, containers, volumes, role_arn,
76 1
                                 execution_role_arn, tags, additional_properties):
77 1
        if tags:
78
            additional_properties['tags'] = tags
79
80
        return self.boto.register_task_definition(
81 1
            family=family,
82 1
            containerDefinitions=containers,
83 1
            volumes=volumes,
84
            taskRoleArn=role_arn,
85
            executionRoleArn=execution_role_arn,
86
            **additional_properties
87
        )
88 1
89
    def deregister_task_definition(self, task_definition_arn):
90
        return self.boto.deregister_task_definition(
91
            taskDefinition=task_definition_arn
92
        )
93
94
    def update_service(self, cluster, service, desired_count, task_definition):
95 1
        if desired_count is None:
96
            return self.boto.update_service(
97
                cluster=cluster,
98
                service=service,
99 1
                taskDefinition=task_definition
100
            )
101
        return self.boto.update_service(
102
            cluster=cluster,
103
            service=service,
104
            desiredCount=desired_count,
105
            taskDefinition=task_definition
106
        )
107
108
    def run_task(self, cluster, task_definition, count, started_by, overrides,
109
                 launchtype='EC2', subnets=(), security_groups=(),
110
                 public_ip=False, platform_version=None):
111
112
        if launchtype == LAUNCH_TYPE_FARGATE:
113
            if not subnets or not security_groups:
114
                msg = 'At least one subnet (--subnet) and one security ' \
115
                      'group (--securitygroup) definition are required ' \
116
                      'for launch type FARGATE'
117
                raise TaskPlacementError(msg)
118
119
            network_configuration = {
120
                "awsvpcConfiguration": {
121
                    "subnets": subnets,
122
                    "securityGroups": security_groups,
123
                    "assignPublicIp": "ENABLED" if public_ip else "DISABLED"
124
                }
125
            }
126
127
            if platform_version is None:
128 1
                platform_version = 'LATEST'
129
130
            return self.boto.run_task(
131
                cluster=cluster,
132
                taskDefinition=task_definition,
133
                count=count,
134
                startedBy=started_by,
135
                overrides=overrides,
136 1
                launchType=launchtype,
137
                networkConfiguration=network_configuration,
138
                platformVersion=platform_version,
139
            )
140
141
        return self.boto.run_task(
142
            cluster=cluster,
143
            taskDefinition=task_definition,
144 1
            count=count,
145 1
            startedBy=started_by,
146 1
            overrides=overrides
147 1
        )
148
149 1
    def update_rule(self, cluster, rule, task_definition):
150 1
        target = self.events.list_targets_by_rule(Rule=rule)['Targets'][0]
151
        target['Arn'] = task_definition.arn.partition('task-definition')[0] + 'cluster/' + cluster
152 1
        target['EcsParameters']['TaskDefinitionArn'] = task_definition.arn
153
        self.events.put_targets(Rule=rule, Targets=[target])
154 1
        return target['Id']
155
156 1
157
class EcsService(dict):
158 1
    def __init__(self, cluster, service_definition=None, **kwargs):
159
        self._cluster = cluster
160 1
        super(EcsService, self).__init__(service_definition, **kwargs)
161
162 1
    def set_task_definition(self, task_definition):
163
        self[u'taskDefinition'] = task_definition.arn
164 1
165
    @property
166 1
    def cluster(self):
167
        return self._cluster
168 1
169
    @property
170 1
    def name(self):
171 1
        return self.get(u'serviceName')
172 1
173 1
    @property
174
    def task_definition(self):
175 1
        return self.get(u'taskDefinition')
176
177 1
    @property
178 1
    def desired_count(self):
179 1
        return self.get(u'desiredCount')
180 1
181
    @property
182 1
    def deployment_created_at(self):
183
        for deployment in self.get(u'deployments'):
184 1
            if deployment.get(u'status') == u'PRIMARY':
185
                return deployment.get(u'createdAt')
186
        return datetime.now()
187
188 1
    @property
189
    def deployment_updated_at(self):
190 1
        for deployment in self.get(u'deployments'):
191
            if deployment.get(u'status') == u'PRIMARY':
192
                return deployment.get(u'updatedAt')
193
        return datetime.now()
194
195 1
    @property
196 1
    def errors(self):
197 1
        return self.get_warnings(
198 1
            since=self.deployment_updated_at
199 1
        )
200 1
201 1
    @property
202 1
    def older_errors(self):
203 1
        return self.get_warnings(
204 1
            since=self.deployment_created_at,
205
            until=self.deployment_updated_at
206
        )
207 1
208 1
    def get_warnings(self, since=None, until=None):
209
        since = since or self.deployment_created_at
210
        until = until or datetime.now(tz=tzlocal())
211
        errors = {}
212 1
        for event in self.get(u'events'):
213 1
            if u'unable' not in event[u'message']:
214 1
                continue
215 1
            if since < event[u'createdAt'] < until:
216 1
                errors[event[u'createdAt']] = event[u'message']
217 1
        return errors
218 1
219 1
220 1
class EcsTaskDefinition(object):
221 1
    def __init__(self, containerDefinitions, volumes, family, revision,
222 1
                 status, taskDefinitionArn, requiresAttributes=None,
223 1
                 taskRoleArn=None, executionRoleArn=None, compatibilities=None,
224
                 tags=None, **kwargs):
225
        self.containers = containerDefinitions
226
        self.volumes = volumes
227
        self.family = family
228 1
        self.revision = revision
229
        self.status = status
230 1
        self.arn = taskDefinitionArn
231
        self.requires_attributes = requiresAttributes or {}
232 1
        self.role_arn = taskRoleArn or u''
233 1
        self.execution_role_arn = executionRoleArn or u''
234
        self.tags = tags
235 1
        self.additional_properties = kwargs
236
        self._diff = []
237 1
238
        # the compatibilities parameter is returned from the ECS API, when
239 1
        # describing a task, but may not be included, when registering a new
240
        # task definition. Just storing it for now.
241 1
        self.compatibilities = compatibilities
242
243 1
    @property
244 1
    def container_names(self):
245 1
        for container in self.containers:
246
            yield container[u'name']
247 1
248 1
    @property
249
    def family_revision(self):
250 1
        return '%s:%d' % (self.family, self.revision)
251 1
252
    @property
253 1
    def diff(self):
254 1
        return self._diff
255
256 1
    def diff_raw(self, task_b):
257 1
        containers_a = {c['name']: c for c in self.containers}
258
        containers_b = {c['name']: c for c in task_b.containers}
259 1
260 1
        requirements_a = sorted([r['name'] for r in self.requires_attributes])
261
        requirements_b = sorted([r['name'] for r in task_b.requires_attributes])
262 1
263
        for container in containers_a:
264
            containers_a[container]['environment'] = {e['name']: e['value'] for e in
265
                                                      containers_a[container].get('environment', {})}
266
267
        for container in containers_b:
268
            containers_b[container]['environment'] = {e['name']: e['value'] for e in
269
                                                      containers_b[container].get('environment', {})}
270
271
        for container in containers_a:
272 1
            containers_a[container]['secrets'] = {e['name']: e['valueFrom'] for e in
273
                                                  containers_a[container].get('secrets', {})}
274
275
        for container in containers_b:
276
            containers_b[container]['secrets'] = {e['name']: e['valueFrom'] for e in
277
                                                  containers_b[container].get('secrets', {})}
278
279
        composite_a = {
280
            'containers': containers_a,
281
            'volumes': self.volumes,
282 1
            'requires_attributes': requirements_a,
283
            'role_arn': self.role_arn,
284 1
            'execution_role_arn': self.execution_role_arn,
285 1
            'compatibilities': self.compatibilities,
286 1
            'additional_properties': self.additional_properties,
287 1
        }
288 1
289 1
        composite_b = {
290 1
            'containers': containers_b,
291 1
            'volumes': task_b.volumes,
292 1
            'requires_attributes': requirements_b,
293 1
            'role_arn': task_b.role_arn,
294 1
            'execution_role_arn': task_b.execution_role_arn,
295 1
            'compatibilities': task_b.compatibilities,
296 1
            'additional_properties': task_b.additional_properties,
297 1
        }
298
299 1
        return list(diff(composite_a, composite_b))
300
301 1
    def get_overrides(self):
302 1
        override = dict()
303 1
        overrides = []
304 1
        for diff in self.diff:
305 1
            if override.get('name') != diff.container:
306
                override = dict(name=diff.container)
307
                overrides.append(override)
308
            if diff.field == 'command':
309
                override['command'] = self.get_overrides_command(diff.value)
310 1
            elif diff.field == 'environment':
311
                override['environment'] = self.get_overrides_env(diff.value)
312 1
            elif diff.field == 'secrets':
313
                override['secrets'] = self.get_overrides_secrets(diff.value)
314 1
        return overrides
315
316 1
    @staticmethod
317
    def parse_command(command):
318 1
        if re.match(JSON_LIST_REGEX, command):
319
            try:
320 1
                return json.loads(command)
321
            except JSONDecodeError as e:
322 1
                raise EcsTaskDefinitionCommandError(
323
                    "command should be valid JSON list. Got following "
324 1
                    "command: {} resulting in error: {}"
325 1
                        .format(command, str(e)))
326 1
327 1
        return command.split()
328 1
329 1
    @staticmethod
330
    def get_overrides_command(command):
331
        return EcsTaskDefinition.parse_command(command)
332
333
    @staticmethod
334
    def get_overrides_env(env):
335 1
        return [{"name": e, "value": env[e]} for e in env]
336 1
337 1
    @staticmethod
338 1
    def get_overrides_secrets(secrets):
339 1
        return [{"name": s, "valueFrom": secrets[s]} for s in secrets]
340 1
341
    def set_images(self, tag=None, **images):
342
        self.validate_container_options(**images)
343
        for container in self.containers:
344
            if container[u'name'] in images:
345
                new_image = images[container[u'name']]
346 1
                diff = EcsTaskDefinitionDiff(
347 1
                    container=container[u'name'],
348
                    field=u'image',
349 1
                    value=new_image,
350 1
                    old_value=container[u'image']
351 1
                )
352 1
                self._diff.append(diff)
353 1
                container[u'image'] = new_image
354 1
            elif tag:
355
                image_definition = container[u'image'].rsplit(u':', 1)
356
                new_image = u'%s:%s' % (image_definition[0], tag.strip())
357
                diff = EcsTaskDefinitionDiff(
358
                    container=container[u'name'],
359
                    field=u'image',
360 1
                    value=new_image,
361 1
                    old_value=container[u'image']
362
                )
363 1
                self._diff.append(diff)
364 1
                container[u'image'] = new_image
365
366 1
    def set_commands(self, **commands):
367 1
        self.validate_container_options(**commands)
368 1
        for container in self.containers:
369
            if container[u'name'] in commands:
370 1
                new_command = commands[container[u'name']]
371 1
                diff = EcsTaskDefinitionDiff(
372 1
                    container=container[u'name'],
373 1
                    field=u'command',
374
                    value=new_command,
375
                    old_value=container.get(u'command')
376
                )
377
                self._diff.append(diff)
378 1
                container[u'command'] = self.parse_command(new_command)
379 1
380
    def set_environment(self, environment_list, exclusive=False, env_file=((None, None),)):
381
        environment = {}
382
        if None not in env_file[0]:
383
            for env in env_file:
384
                l = read_env_file(env[0], env[1])
385 1
                environment_list = l + environment_list
386 1
        for env in environment_list:
387 1
            environment.setdefault(env[0], {})
388
            environment[env[0]][env[1]] = env[2]
389 1
390 1
        self.validate_container_options(**environment)
391
        for container in self.containers:
392 1
            if container[u'name'] in environment:
393 1
                self.apply_container_environment(
394
                    container=container,
395 1
                    new_environment=environment[container[u'name']],
396 1
                    exclusive=exclusive,
397
                )
398 1
            elif exclusive is True:
399
                self.apply_container_environment(
400
                    container=container,
401
                    new_environment={},
402
                    exclusive=exclusive,
403
                )
404 1
405
    def apply_container_environment(self, container, new_environment, exclusive=False):
406 1
        environment = container.get('environment', {})
407
        old_environment = {env['name']: env['value'] for env in environment}
408
409
        if exclusive is True:
410 1
            merged = new_environment
411 1
        else:
412
            merged = old_environment.copy()
413 1
            merged.update(new_environment)
414 1
415 1
        if old_environment == merged:
416
            return
417 1
418 1
        diff = EcsTaskDefinitionDiff(
419 1
            container=container[u'name'],
420 1
            field=u'environment',
421
            value=merged,
422
            old_value=old_environment
423
        )
424
        self._diff.append(diff)
425 1
426 1
        container[u'environment'] = [
427
            {"name": e, "value": merged[e]} for e in merged
428
        ]
429
430
    def set_secrets(self, secrets_list, exclusive=False):
431
        secrets = {}
432 1
433 1
        for secret in secrets_list:
434 1
            secrets.setdefault(secret[0], {})
435
            secrets[secret[0]][secret[1]] = secret[2]
436 1
437 1
        self.validate_container_options(**secrets)
438
        for container in self.containers:
439 1
            if container[u'name'] in secrets:
440 1
                self.apply_container_secrets(
441
                    container=container,
442 1
                    new_secrets=secrets[container[u'name']],
443 1
                    exclusive=exclusive,
444
                )
445 1
            elif exclusive is True:
446
                self.apply_container_secrets(
447
                    container=container,
448
                    new_secrets={},
449
                    exclusive=exclusive,
450
                )
451 1
452
    def apply_container_secrets(self, container, new_secrets, exclusive=False):
453 1
        secrets = container.get('secrets', {})
454
        old_secrets = {secret['name']: secret['valueFrom'] for secret in secrets}
455
456
        if exclusive is True:
457 1
            merged = new_secrets
458 1
        else:
459 1
            merged = old_secrets.copy()
460 1
            merged.update(new_secrets)
461
462
        if old_secrets == merged:
463
            return
464 1
465 1
        diff = EcsTaskDefinitionDiff(
466 1
            container=container[u'name'],
467
            field=u'secrets',
468
            value=merged,
469
            old_value=old_secrets
470
        )
471
        self._diff.append(diff)
472 1
473 1
        container[u'secrets'] = [
474
            {"name": s, "valueFrom": merged[s]} for s in merged
475 1
        ]
476 1
477 1
    def validate_container_options(self, **container_options):
478
        for container_name in container_options:
479
            if container_name not in self.container_names:
480
                raise UnknownContainerError(
481
                    u'Unknown container: %s' % container_name
482
                )
483 1
484 1
    def set_role_arn(self, role_arn):
485
        if role_arn:
486
            diff = EcsTaskDefinitionDiff(
487 1
                container=None,
488 1
                field=u'role_arn',
489 1
                value=role_arn,
490 1
                old_value=self.role_arn
491 1
            )
492 1
            self.role_arn = role_arn
493
            self._diff.append(diff)
494 1
495 1
    def set_execution_role_arn(self, execution_role_arn):
496 1
        if execution_role_arn:
497
            diff = EcsTaskDefinitionDiff(
498
                container=None,
499
                field=u'execution_role_arn',
500
                value=execution_role_arn,
501 1
                old_value=self.execution_role_arn
502 1
            )
503
            self.execution_role_arn = execution_role_arn
504
            self._diff.append(diff)
505
506
507 1
class EcsTaskDefinitionDiff(object):
508 1
    def __init__(self, container, field, value, old_value):
509
        self.container = container
510
        self.field = field
511
        self.value = value
512
        self.old_value = old_value
513
514
    def __repr__(self):
515 1
        if self.field == u'environment':
516
            return '\n'.join(self._get_environment_diffs(
517
                self.container,
518
                self.value,
519
                self.old_value,
520
            ))
521 1
        elif self.field == u'secrets':
522
            return '\n'.join(self._get_secrets_diffs(
523 1
                self.container,
524 1
                self.value,
525 1
                self.old_value,
526 1
            ))
527 1
        elif self.container:
528 1
            return u'Changed %s of container "%s" to: "%s" (was: "%s")' % (
529 1
                self.field,
530 1
                self.container,
531 1
                self.value,
532 1
                self.old_value
533 1
            )
534 1
        else:
535 1
            return u'Changed %s to: "%s" (was: "%s")' % (
536
                self.field,
537 1
                self.value,
538
                self.old_value
539 1
            )
540 1
541 1
    @staticmethod
542 1
    def _get_environment_diffs(container, env, old_env):
543 1
        msg = u'Changed environment "%s" of container "%s" to: "%s"'
544 1
        msg_removed = u'Removed environment "%s" of container "%s"'
545 1
        diffs = []
546 1
        for name, value in env.items():
547 1
            old_value = old_env.get(name)
548 1
            if value != old_value or value and not old_value:
549 1
                message = msg % (name, container, value)
550 1
                diffs.append(message)
551 1
        for old_name in old_env.keys():
552
            if old_name not in env.keys():
553
                message = msg_removed % (old_name, container)
554 1
                diffs.append(message)
555 1
        return diffs
556 1
557 1
    @staticmethod
558 1
    def _get_secrets_diffs(container, secrets, old_secrets):
559
        msg = u'Changed secret "%s" of container "%s" to: "%s"'
560 1
        msg_removed = u'Removed secret "%s" of container "%s"'
561 1
        diffs = []
562 1
        for name, value in secrets.items():
563 1
            old_value = old_secrets.get(name)
564 1
            if value != old_value or not old_value:
565
                message = msg % (name, container, value)
566
                diffs.append(message)
567
        for old_name in old_secrets.keys():
568 1
            if old_name not in secrets.keys():
569 1
                message = msg_removed % (old_name, container)
570 1
                diffs.append(message)
571 1
        return diffs
572
573
574
class EcsAction(object):
575
    def __init__(self, client, cluster_name, service_name):
576 1
        self._client = client
577 1
        self._cluster_name = cluster_name
578
        self._service_name = service_name
579
580
        try:
581 1
            if service_name:
582
                self._service = self.get_service()
583
        except IndexError:
584
            raise EcsConnectionError(
585
                u'An error occurred when calling the DescribeServices '
586 1
                u'operation: Service not found.'
587 1
            )
588
        except ClientError as e:
589 1
            raise EcsConnectionError(str(e))
590 1
        except NoCredentialsError:
591
            raise EcsConnectionError(
592
                u'Unable to locate credentials. Configure credentials '
593
                u'by running "aws configure".'
594 1
            )
595
596
    def get_service(self):
597
        services_definition = self._client.describe_services(
598 1
            cluster_name=self._cluster_name,
599
            service_name=self._service_name
600 1
        )
601 1
        return EcsService(
602
            cluster=self._cluster_name,
603
            service_definition=services_definition[u'services'][0]
604
        )
605
606
    def get_current_task_definition(self, service):
607
        return self.get_task_definition(service.task_definition)
608
609
    def get_task_definition(self, task_definition):
610 1
        task_definition_payload = self._client.describe_task_definition(
611 1
            task_definition_arn=task_definition
612
        )
613 1
614 1
        task_definition = EcsTaskDefinition(
615
            tags=task_definition_payload.get('tags', None),
616 1
            **task_definition_payload[u'taskDefinition']
617 1
        )
618
        return task_definition
619
620
    def update_task_definition(self, task_definition):
621
        response = self._client.register_task_definition(
622
            family=task_definition.family,
623 1
            containers=task_definition.containers,
624
            volumes=task_definition.volumes,
625 1
            role_arn=task_definition.role_arn,
626 1
            execution_role_arn=task_definition.execution_role_arn,
627 1
            tags=task_definition.tags,
628 1
            additional_properties=task_definition.additional_properties
629
        )
630
        new_task_definition = EcsTaskDefinition(**response[u'taskDefinition'])
631
        return new_task_definition
632 1
633 1
    def deregister_task_definition(self, task_definition):
634 1
        self._client.deregister_task_definition(task_definition.arn)
635
636
    def update_service(self, service, desired_count=None):
637
        response = self._client.update_service(
638 1
            cluster=service.cluster,
639
            service=service.name,
640 1
            desired_count=desired_count,
641 1
            task_definition=service.task_definition
642 1
        )
643
        return EcsService(self._cluster_name, response[u'service'])
644
645
    def is_deployed(self, service):
646 1
        if len(service[u'deployments']) != 1:
647 1
            return False
648 1
        running_tasks = self._client.list_tasks(
649 1
            cluster_name=service.cluster,
650 1
            service_name=service.name
651 1
        )
652
        if not running_tasks[u'taskArns']:
653 1
            return service.desired_count == 0
654
        running_count = self.get_running_tasks_count(
655 1
            service=service,
656
            task_arns=running_tasks[u'taskArns']
657 1
        )
658
        return service.desired_count == running_count
659 1
660
    def get_running_tasks_count(self, service, task_arns):
661 1
        running_count = 0
662
        tasks_details = self._client.describe_tasks(
663 1
            cluster_name=self._cluster_name,
664
            task_arns=task_arns
665 1
        )
666
        for task in tasks_details[u'tasks']:
667 1
            arn = task[u'taskDefinitionArn']
668
            status = task[u'lastStatus']
669
            if arn == service.task_definition and status == u'RUNNING':
670 1
                running_count += 1
671 1
        return running_count
672 1
673 1
    @property
674 1
    def client(self):
675 1
        return self._client
676 1
677
    @property
678
    def service(self):
679 1
        return self._service
680 1
681 1
    @property
682 1
    def cluster_name(self):
683 1
        return self._cluster_name
684 1
685
    @property
686
    def service_name(self):
687 1
        return self._service_name
688 1
689 1
690 1
class DeployAction(EcsAction):
691 1
    def deploy(self, task_definition):
692 1
        try:
693
            self._service.set_task_definition(task_definition)
694 1
            return self.update_service(self._service)
695
        except ClientError as e:
696 1
            raise EcsError(str(e))
697 1
698
699
class ScaleAction(EcsAction):
700
    def scale(self, desired_count):
701
        try:
702
            return self.update_service(self._service, desired_count)
703
        except ClientError as e:
704
            raise EcsError(str(e))
705
706
707
class RunAction(EcsAction):
708
    def __init__(self, client, cluster_name):
709 1
        super(RunAction, self).__init__(client, cluster_name, None)
710 1
        self._client = client
711 1
        self._cluster_name = cluster_name
712 1
        self.started_tasks = []
713
714
    def run(self, task_definition, count, started_by, launchtype, subnets,
715 1
            security_groups, public_ip, platform_version):
716 1
        try:
717 1
            result = self._client.run_task(
718
                cluster=self._cluster_name,
719
                task_definition=task_definition.family_revision,
720 1
                count=count,
721 1
                started_by=started_by,
722 1
                overrides=dict(containerOverrides=task_definition.get_overrides()),
723
                launchtype=launchtype,
724
                subnets=subnets,
725 1
                security_groups=security_groups,
726 1
                public_ip=public_ip,
727
                platform_version=platform_version,
728
            )
729 1
            self.started_tasks = result['tasks']
730 1
            return True
731
        except ClientError as e:
732
            raise EcsError(str(e))
733 1
734 1
735
class UpdateAction(EcsAction):
736
    def __init__(self, client):
737 1
        super(UpdateAction, self).__init__(client, None, None)
738 1
739
740
class DiffAction(EcsAction):
741 1
    def __init__(self, client):
742 1
        super(DiffAction, self).__init__(client, None, None)
743
744
745 1
class EcsError(Exception):
746 1
    pass
747
748
749
class EcsConnectionError(EcsError):
750
    pass
751
752
753
class UnknownContainerError(EcsError):
754
    pass
755
756
757
class TaskPlacementError(EcsError):
758
    pass
759
760
761
class UnknownTaskDefinitionError(EcsError):
762
    pass
763
764
765
class EcsTaskDefinitionCommandError(EcsError):
766
    pass
767