Skip to content

Support multiple images update #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ To run the script::
-r | --region AWS Region Name. May also be set as environment variable AWS_DEFAULT_REGION
-p | --profile AWS Profile to use - If you set this aws-access-key, aws-secret-key and region are needed
-c | --cluster Name of ECS cluster
-i | --image Name of Docker image to run, ex: repo/image:latest
Format: [domain][:port][/repo][/][image][:tag]
Examples: mariadb, mariadb:latest, silintl/mariadb, silintl/mariadb:latest, private.registry.com:8000/repo/image:tag
-l |--container-image CONTAINER_IMAGE
Name of the container with the name of Docker image to
run Format: [container_name]=[domain][:port][/repo][/]
[image][:tag] Examples: db=mariadb,
database=mariadb:latest, db=silintl/mariadb:latest,
my_container|private.registry.com:8000/repo/image:tag.
You can use this argument multiple times to replace
Optional arguments:
-D | --desired-count The number of instantiations of the task to place and keep running in your service.
-m | --min minumumHealthyPercent: The lower limit on the number of running tasks during a deployment.
Expand Down
91 changes: 67 additions & 24 deletions ecs_deploy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
ecs-deploy.py
Expand Down Expand Up @@ -26,7 +27,9 @@ def __init__(self):
'aws_access_key_id')
credentials = self._arg_kwargs(credentials, 'aws_secret_key',
'aws_secret_access_key')
credentials = self._arg_kwargs(credentials, 'aws_region', 'region')
credentials = self._arg_kwargs(credentials, 'aws_region',
'region_name')

# init boto3 ecs client
self.client = boto3.client('ecs', **credentials)
except ClientError as err:
Expand Down Expand Up @@ -77,7 +80,7 @@ def _init_parser(self):

parser.add_argument(
'-r',
'--region',
'--aws-region',
help='AWS Region Name. May also be set as environment variable \
AWS_DEFAULT_REGION')

Expand All @@ -103,13 +106,17 @@ def _init_parser(self):
help='Name of ECS cluster')

parser.add_argument(
'-i',
'--image',
'-l',
'--container-image',
action='append',
required=True,
help='Name of Docker image to run, ex: repo/image:latest\nFormat: \
[domain][:port][/repo][/][image][:tag]\nExamples: mariadb, \
mariadb:latest, silintl/mariadb,\nsilintl/mariadb:latest, \
private.registry.com:8000/repo/image:tag')
help='Name of the container with the name of Docker image to run\
\nFormat: \
[container_name]=[domain][:port][/repo][/][image][:tag]\
\nExamples: db=mariadb, database=mariadb:latest, \
db=silintl/mariadb:latest, \
my_container|private.registry.com:8000/repo/image:tag. \
You can use this argument multiple times to replace images.')

# OPTIONAL ARGUMENTS

Expand Down Expand Up @@ -166,32 +173,51 @@ def _run_parser(self):
self.task_definition_name = self._task_definition_name()
self.service_name = self._service_name()

self.task_definition = \
self.client_fn('describe_task_definition')['taskDefinition']
self.new_task_definition = \
self.client_fn('register_task_definition')['taskDefinition']
self.task_definition = self.client_fn(
'describe_task_definition')['taskDefinition']
print('Current task definition: %s' %
self.task_definition['taskDefinitionArn'])

self.new_task_definition = self.client_fn(
'register_task_definition')['taskDefinition']
print('New task definition: %s' %
self.new_task_definition['taskDefinitionArn'])

if self.task_definition:
if not self.client_fn('update_service'):
sys.exit(1)

# loop for desired timeout
timeout = self.args.get('timeout') or time.time() + 90
timeout = self._int_or_none(self.args.get('timeout')) or 90
started_at = time.time()
while True:
updated = False
running_tasks = self.client_fn('describe_tasks')['tasks']
for task in running_tasks:
self.running_tasks = self.client_fn('list_tasks')['taskArns']

if len(self.running_tasks) > 0:
described_tasks = self.client_fn('describe_tasks')['tasks']
else:
described_tasks = []

for task in described_tasks:
if task['taskDefinitionArn'] == \
self.new_task_definition['taskDefinitionArn']:
print('SUCCESS')
updated = True
if updated or time.time() > timeout:
print('Service updated successfully, '
'new task definition running.')
return
if time.time() > started_at + timeout:
print('ERROR: New task definition not running'
'within %d seconds' % timeout)
sys.exit(1)
time.sleep(1)

else:
sys.exit(1)

def _int_or_none(self, value):
if type(value) == str and value.isdigit():
return int(value)
return None

def _task_definition_name(self):
if self.args.get('task_definition'):
return self.args.get('task_definition')
Expand Down Expand Up @@ -235,10 +261,22 @@ def client_kwargs(self, fn):
kwargs['family'] = self.task_definition['family']
kwargs['containerDefinitions'] = \
self.task_definition['containerDefinitions']

# optional kwargs from args
if self.args.get('image'):
kwargs['containerDefinitions'][0]['image'] = \
self.args.get('image')
for ci in self.args.get('container_image'):
ci_container_name, ci_image = ci.split('=')
container_found = False

for cd in kwargs['containerDefinitions']:
if cd['name'] == ci_container_name:
cd['image'] = ci_image
container_found = True

if not container_found:
print('Container %s not found in the task definition %s' %
(ci_container_name,
self.task_definition['taskDefinitionArn']))
sys.exit(1)

elif fn == 'update_service':
kwargs['cluster'] = self.cluster
Expand All @@ -260,7 +298,7 @@ def client_kwargs(self, fn):

elif fn == 'describe_tasks':
kwargs['cluster'] = self.cluster
kwargs['tasks'] = self.client_fn('list_tasks')['taskArns']
kwargs['tasks'] = self.running_tasks

return kwargs

Expand All @@ -277,5 +315,10 @@ def client_fn(self, fn):
print('Exception: %s' % e)
sys.exit(1)

if __name__ == '__main__':

def main():
CLI()


if __name__ == '__main__':
main()
22 changes: 11 additions & 11 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ def run_tests(self):

setup(
name='ecs-deploy-py',
version='0.1.3',
version='0.1.4',
url='http://github.com/cuttlesoft/ecs-deploy.py',
download_url='https://github.com/cuttlesoft/ecs-deploy.py/tarball/0.1.3',
download_url='https://github.com/cuttlesoft/ecs-deploy.py/tarball/0.1.4',
license='MIT',
author='Cuttlesoft, LLC',
author_email='[email protected]',
Expand All @@ -74,14 +74,14 @@ def run_tests(self):
zip_safe=False,
platforms='any',
install_requires=[
'boto3==1.4.0',
'botocore==1.4.56',
'docutils==0.12',
'futures==3.0.5',
'jmespath==0.9.0',
'python-dateutil==2.5.3',
's3transfer==0.1.4',
'six==1.10.0'
'boto3>=1.4.0',
'botocore>=1.4.56',
'docutils>=0.12',
'futures>=3.0.5',
'jmespath>=0.9.0',
'python-dateutil>=2.5.3',
's3transfer>=0.1.4',
'six>=1.10.0'
],
cmdclass={'test': PyTest},
classifiers=[
Expand All @@ -95,7 +95,7 @@ def run_tests(self):
'Topic :: Software Development :: Libraries :: Python Modules'],
entry_points={
'console_scripts': [
'ecs-deploy-py = ecs_deploy:CLI'
'ecs-deploy-py = ecs_deploy:main'
]
}
)
27 changes: 10 additions & 17 deletions tests/test_script.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest
import mock
import boto3
from moto import mock_ecs
Expand Down Expand Up @@ -126,7 +125,7 @@ def setUp(self):
'cluster': mock_cluster['cluster']['clusterName'],
'task_definition': mock_task['taskDefinition']['family'],
'service_name': mock_service['service']['serviceName'],
'image': 'mock_image'
'container_image': ['string=mock_image']
}

self.mock_cli.cluster = mock_cluster['cluster']
Expand Down Expand Up @@ -231,7 +230,7 @@ def test_client_kwargs_with_register_task_definition(self):
assert 'family' in mock_kwargs
assert 'containerDefinitions' in mock_kwargs
assert mock_kwargs['containerDefinitions'][0]['image'] == \
mock_cli.args['image']
mock_cli.args['container_image'][0].split('=')[1]
assert mock_kwargs['family'] == mock_cli.task_definition['family']

def test_client_kwargs_with_update_service(self):
Expand Down Expand Up @@ -343,23 +342,17 @@ def test_client_kwargs_with_list_tasks(self):
def test_client_kwargs_with_describe_tasks(self):
mock_cli, client = self.setUp()
mock_cli.service_name = mock_cli.args['service_name']
mock_cli.running_tasks = [""]

with mock.patch.object(mock_cli, 'client_fn') as mock_fn:
mock_list_tasks_response = {
'taskArns': [
mock_cli.task_definition['taskDefinitionArn']
]
}
mock_fn.return_value = mock_list_tasks_response
mock_kwargs = mock_cli.client_kwargs('describe_tasks')
mock_kwargs = mock_cli.client_kwargs('describe_tasks')

assert 'cluster' in mock_kwargs
assert 'tasks' in mock_kwargs
assert mock_kwargs['cluster']['clusterName'] == \
mock_cli.args['cluster']
assert mock_kwargs['tasks'] == mock_list_tasks_response['taskArns']
assert 'cluster' in mock_kwargs
assert 'tasks' in mock_kwargs
assert mock_kwargs['cluster']['clusterName'] == \
mock_cli.args['cluster']
assert mock_kwargs['tasks'] == mock_cli.running_tasks

def test_client_kwargs_with_describe_tasks(self):
def test_client_kwargs_with_list_services_once(self):
mock_cli, client = self.setUp()
mock_cli.client = client

Expand Down