Odyssey
deploy.py
1 """
2 HomeCU tool for deploying containers and updating ECS.
3 """
4 
5 import boto3
6 import json
7 import os
8 import random
9 from . import s3, base
10 from . import REGISTRIES
11 from . import get_client_oauth_data
12 from .ecr import push_odyssey_images
13 from concurrent import futures
14 
15 
17  """ Deploy an Odyssey stack.
18 
19  The basic delivery for a deploy is a set of docker containers. A
20  docker registry acts as the gateway between build and execution. The
21  role of this script is to deliver new images to the registry and then
22  update ECS task definitions to commence a rolling update. """
23 
24  name = 'deploy'
25  min_version = "0.4"
26  max_version = '0.7.1'
27  allowed_states = {'ready', 'active', 'partial_deploy'}
28 
29  def setup_args(self, parser):
30  self.add_stack_argument()
31  self.add_argument('--verbose', '-v', action='store_true')
32  self.add_argument('--passphrase', env='ODYSSEY_STACK_PASSPHRASE',
33  help='To avoid being prompted for a passphrase on '
34  'protected stacks, you can set it on the command '
35  'line or via the env.')
36 
37  def run(self, args):
38  stack = self.get_stack(args.stack)
39  if stack is None:
40  raise SystemExit('Stack not found: %s' % args.stack)
41  stack.check_passphrase(args.passphrase)
42  self.check_requirements(stack)
43  stack.update_state('deploying')
44  try:
45  self.deploy(args, stack)
46  except:
47  stack.update_state('partial_deploy')
48  stack._save('Deploy failure of %s from %s' % (
49  os.environ['GIT_REV'], os.environ['PROJECT_NAME']))
50  raise
51  else:
52  stack.update_state('active')
53  stack._save('Deployed %s from %s' % (os.environ['GIT_REV'],
54  os.environ['PROJECT_NAME']))
55 
56  def deploy(self, args, stack):
57  registry = REGISTRIES[stack.region]
58  oauth_client = get_client_oauth_data(stack.dns[0])
59  push_odyssey_images(args.stack, stack.region, args.verbose)
60  template_vars = {
61  "stack": args.stack,
62  "stack_url": 'https://%s' % stack.dns[0],
63  "stack_domain": stack.dns[0],
64  "registry": registry,
65  "db_host": stack.db['host'],
66  "db_port": str(stack.db['port']),
67  "db_name": stack.db['name'],
68  "db_user": stack.db['user'],
69  "db_password": stack.db['password'],
70  "oauth2_client_id": oauth_client[0],
71  "oauth2_client_secret": oauth_client[1],
72  }
73  for i, n in enumerate(random.sample(range(12000, 60000), 10)):
74  template_vars['randport%d' % i] = n
75 
76  client = boto3.client('ecs', region_name=stack.region)
77  service_tasks = {}
78  for task in os.scandir('tasks'):
79  if not task.name.endswith('.json'):
80  continue
81  with open(task.path) as f:
82  data = json.loads(f.read() % template_vars)
83  taskdef = data['definition']
84  service = data['service']
85  if stack.env:
86  for cdef in taskdef['containerDefinitions']:
87  env = dict((x['name'], x['value'])
88  for x in cdef.get('environment', []))
89  env.update(stack.env)
90  cdef['environment'] = [{
91  "name": key,
92  "value": value
93  } for key, value in env.items()]
94  if not stack.has_resource('ecs-task-definition',
95  taskdef['family']):
96  print("Adding new task definition:", taskdef['family'])
97  stack.add_resource('ecs-task-definition', taskdef['family'],
98  family=taskdef['family'])
99  else:
100  print("Updating existing task definition:", taskdef['family'])
101  task = client.register_task_definition(**taskdef)['taskDefinition']
102  if service:
103  print("Updating Service: %s -> v%d" % (service,
104  task['revision']))
105  client.update_service(**{
106  "cluster": stack.cluster,
107  "service": service,
108  "taskDefinition": taskdef['family']
109  })
110  service_tasks[service] = task
111 
112  count = stack.deploy.get('count', 0)
113  stack.deploy = {
114  "count": count + 1,
115  "updated": base.localnow().isoformat(),
116  "project": os.environ['PROJECT_NAME'],
117  "build_ident": os.environ['OPERATOR_IDENT'],
118  "git_rev": os.environ['GIT_REV'],
119  "git_branch": os.environ['GIT_BRANCH'],
120  "git_repo": os.environ['GIT_REPO'],
121  "docker_version": os.environ['DOCKER_VERSION'],
122  "service_tasks": service_tasks
123  }
def deploy(self, args, stack)
Definition: deploy.py:56
def get_client_oauth_data(domain)
Definition: __init__.py:45
def add_stack_argument(self, *args, env=DEFAULT_STACK_ENV, help=None, metavar='STACK_NAME', **kwargs)
Definition: base.py:56
def get_stack(self, name)
Definition: s3.py:136
def check_requirements(self, stack)
Definition: base.py:65