Skip to content

Commit

Permalink
Merge branch 'release/1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
fabfuel committed Oct 7, 2016
2 parents a8c9fa0 + 67a159a commit df77635
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 19 deletions.
48 changes: 46 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ scale
=====
Scale a service up or down and change the number of running tasks.

run
===
Run a one-off task based on an existing task-definition and optionally override command and/or environment variables.


Usage
-----
Expand Down Expand Up @@ -93,8 +97,8 @@ To change the image of a specific container, run the following command::
This will modify the **webserver** container only and change its image to "nginx:latest".


Deploy several new image
========================
Deploy several new images
=========================
The `-i` or `--image` option can also be passed several times::

$ ecs deploy my-cluster my-service -i webserver nginx:1.9 -i application django:latest
Expand Down Expand Up @@ -136,3 +140,43 @@ To change the number of running tasks and scale a service up and down, run this

$ ecs scale my-cluster my-service 4


Run a one-off task
==================
To run a one-off task, based on an existing task-definition, run this command::

$ ecs run my-cluster my-task

You can define just the task family (e.g. ``my-task``) or you can run a specific revision of the task-definition (e.g.
``my-task:123``). And optionally you can add or adjust environment variables like this::

$ ecs run my-cluster my-task:123 -e my-container MY_VARIABLE "my value"


Run a task with a custom command
================================

You can override the command definition via option ``-c`` or ``--command`` followed by the container name and the
command in a natural syntax, e.g. no conversion to comma-separation required::

$ ecs run my-cluster my-task -c my-container "python some-script.py param1 param2"

Monitoring
----------
With ECS deploy you can track your deployments automatically. Currently only New Relic is supported:

New Relic
=========
To record a deployment in New Relic, you can provide the the API Key (**Attention**: this is a specific REST API Key, not the license key) and the application id in two ways:

Via cli options::

$ ecs deploy my-cluster my-service --newrelic-apikey ABCDEFGHIJKLMN --newrelic-appid 1234567890
Or implicitly via environment variables ``NEW_RELIC_API_KEY`` and ``NEW_RELIC_APP_ID`` ::

$ export NEW_RELIC_API_KEY=ABCDEFGHIJKLMN
$ export NEW_RELIC_APP_ID=1234567890
$ ecs deploy my-cluster my-service

Optionally you can provide an additional comment to the deployment via ``--comment "New feature X"`` and the name of the user who deployed with ``--user john.doe``
52 changes: 49 additions & 3 deletions ecs_deploy/cli.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import print_function, absolute_import

from os import getenv
from time import sleep

import click
import getpass
from datetime import datetime, timedelta

from ecs_deploy.ecs import DeployAction, ScaleAction, EcsClient
from ecs_deploy.ecs import DeployAction, ScaleAction, RunAction, EcsClient
from ecs_deploy.newrelic import Deployment, NewRelicDeploymentException


Expand Down Expand Up @@ -107,6 +109,46 @@ def scale(cluster, service, desired_count, access_key_id, secret_access_key, reg
exit(1)


@click.command()
@click.argument('cluster')
@click.argument('task')
@click.argument('count', required=False, default=1)
@click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: <container> <command>')
@click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: <container> <name> <value>')
@click.option('--region', help='AWS region')
@click.option('--access-key-id', help='AWS access key id')
@click.option('--secret-access-key', help='AWS secret access yey')
@click.option('--profile', help='AWS configuration profile')
def run(cluster, task, count, command, env, region, access_key_id, secret_access_key, profile):
"""
Run a one-off task.
\b
CLUSTER is the name of your cluster (e.g. 'my-custer') within ECS.
TASK is the name of your task definintion (e.g. 'mytask') within ECS.
COMMAND is the number of tasks your service should run.
"""
try:
client = get_client(access_key_id, secret_access_key, region, profile)
action = RunAction(client, cluster)

task_definition = action.get_task_definition(task)
task_definition.set_commands(**{key: value for (key, value) in command})
task_definition.set_environment(env)
print_diff(task_definition, 'Using task definition: %s' % task)

action.run(task_definition, count, 'ECS Deploy')

click.secho('Successfully started %d instances of task: %s' % (len(action.started_tasks), task_definition.family_revision), fg='green')
for started_task in action.started_tasks:
click.secho('- %s' % started_task['taskArn'], fg='green')
click.secho(' ')

except Exception as e:
click.secho('%s\n' % str(e), fg='red', err=True)
exit(1)


def wait_for_finish(action, timeout, title, success_message, failure_message):
click.secho(title, nl=False)
waiting = True
Expand All @@ -126,6 +168,9 @@ def wait_for_finish(action, timeout, title, success_message, failure_message):


def record_deployment(revision, newrelic_apikey, newrelic_appid, comment, user):
newrelic_apikey = getenv('NEW_RELIC_API_KEY', newrelic_apikey)
newrelic_appid = getenv('NEW_RELIC_APP_ID', newrelic_appid)

if not revision or not newrelic_apikey or not newrelic_appid:
return False

Expand All @@ -141,9 +186,9 @@ def record_deployment(revision, newrelic_apikey, newrelic_appid, comment, user):
return True


def print_diff(task_definition):
def print_diff(task_definition, title='Updating task definition'):
if task_definition.diff:
click.secho('Updating task definition')
click.secho(title)
for diff in task_definition.diff:
click.secho(str(diff), fg='blue')
click.secho('')
Expand All @@ -166,6 +211,7 @@ def print_errors(service, was_timeout=False, message=''):

ecs.add_command(deploy)
ecs.add_command(scale)
ecs.add_command(run)

if __name__ == '__main__': # pragma: no cover
ecs()
59 changes: 57 additions & 2 deletions ecs_deploy/ecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ def update_service(self, cluster, service, desired_count, task_definition):
taskDefinition=task_definition
)

def run_task(self, cluster, task_definition, count, started_by, overrides):
return self.boto.run_task(
cluster=cluster,
taskDefinition=task_definition,
count=count,
startedBy=started_by,
overrides=overrides
)


class EcsService(dict):
def __init__(self, cluster, iterable=None, **kwargs):
Expand Down Expand Up @@ -129,10 +138,33 @@ def family(self):
def revision(self):
return self.get(u'revision')

@property
def family_revision(self):
return '%s:%d' % (self.get(u'family'), self.get(u'revision'))

@property
def diff(self):
return self._diff

def get_overrides(self):
override = dict()
overrides = []
for diff in self.diff:
if override.get('name') != diff.container:
override = dict(name=diff.container)
overrides.append(override)
if diff.field == 'command':
override['command'] = self.get_overrides_command(diff.value)
elif diff.field == 'environment':
override['environment'] = self.get_overrides_environment(diff.value)
return overrides

def get_overrides_command(self, command):
return command.split(' ')

def get_overrides_environment(self, environment_dict):
return [{"name": e, "value": environment_dict[e]} for e in environment_dict]

def set_images(self, tag=None, **images):
self.validate_container_options(**images)
for container in self.containers:
Expand Down Expand Up @@ -174,7 +206,7 @@ def apply_container_environment(self, container, new_environment):
merged_environment = old_environment.copy()
merged_environment.update(new_environment)

diff = EcsTaskDefinitionDiff(container[u'name'], u'environment', dumps(merged_environment), dumps(old_environment))
diff = EcsTaskDefinitionDiff(container[u'name'], u'environment', merged_environment, old_environment)
self._diff.append(diff)

container[u'environment'] = [{"name": e, "value": merged_environment[e]} for e in merged_environment]
Expand All @@ -194,7 +226,7 @@ def __init__(self, container, field, value, old_value):

def __repr__(self):
return u"Changed %s of container '%s' to: %s (was: %s)" % \
(self.field, self.container, self.value, self.old_value)
(self.field, self.container, dumps(self.value), dumps(self.old_value))


class EcsAction(object):
Expand All @@ -221,6 +253,11 @@ def get_current_task_definition(self, service):
task_definition = EcsTaskDefinition(task_definition_payload[u'taskDefinition'])
return task_definition

def get_task_definition(self, task_definition):
task_definition_payload = self._client.describe_task_definition(task_definition)
task_definition = EcsTaskDefinition(task_definition_payload[u'taskDefinition'])
return task_definition

def update_task_definition(self, task_definition):
response = self._client.register_task_definition(task_definition.family, task_definition.containers,
task_definition.volumes)
Expand Down Expand Up @@ -278,6 +315,24 @@ def scale(self, desired_count):
return self.update_service(self._service)


class RunAction(EcsAction):
def __init__(self, client, cluster_name):
self._client = client
self._cluster_name = cluster_name
self.started_tasks = []

def run(self, task_definition, count, started_by):
result = self._client.run_task(
cluster=self._cluster_name,
task_definition=task_definition.family_revision,
count=count,
started_by=started_by,
overrides=dict(containerOverrides=task_definition.get_overrides())
)
self.started_tasks = result['tasks']
return True


class EcsError(Exception):
pass

Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

setup(
name='ecs-deploy',
version='0.3.0',
version='1.0.0',
url='https://github.com/fabfuel/ecs-deploy',
download_url='https://github.com/fabfuel/ecs-deploy/archive/0.3.0.tar.gz',
download_url='https://github.com/fabfuel/ecs-deploy/archive/1.0.0.tar.gz',
license='BSD',
author='Fabian Fuelling',
author_email='fabian@fabfuel.de',
author_email='pypi@fabfuel.de',
description='Simplify Amazon ECS deployments',
long_description=__doc__,
packages=find_packages(exclude=['tests']),
Expand Down
Loading

0 comments on commit df77635

Please sign in to comment.