Skip to content

Commit 3960a4b

Browse files
authored
Merge pull request #1 from moee/0.0.1
0.0.1
2 parents 90ab079 + 5134552 commit 3960a4b

File tree

6 files changed

+211
-0
lines changed

6 files changed

+211
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,5 @@ ENV/
8787

8888
# Rope project settings
8989
.ropeproject
90+
91+
*.swp

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,45 @@
11
# ecs_task_runner
22
Helper tools to run a task on AWS ECS and follow the logs
3+
4+
# Why?
5+
The use case why I created this script is that we want to run ECS tasks in Jenkins and directly see the output of the tasks that are being run. This is very cumbersome to achieve using the `aws cli`. Hence this library.
6+
7+
# Installation
8+
Until this package is distributed as pip package, you have to install it directly from this repository:
9+
10+
```
11+
pip install git+https://github.com/moee/[email protected]
12+
```
13+
14+
# Usage
15+
## Example 1: Jenkins Integration
16+
17+
```sh
18+
#!/bin/sh
19+
pip install git+https://github.com/moee/[email protected]
20+
21+
python << END
22+
import ecstaskrunner, sys, logging
23+
24+
logging.basicConfig()
25+
logging.getLogger('ecstaskrunner').setLevel(logging.INFO)
26+
27+
sys.exit(
28+
ecstaskrunner.run_task(
29+
cluster="YOUR-CLUSTER-NAME",
30+
taskDefinition='YOUR-TASK-DEFINITION',
31+
)
32+
)
33+
END
34+
```
35+
This runs the task named `YOUR-TASK-DEFINITION` on the cluster `YOUR-CLUSTER-NAME`, displays all the output (Note: this only works if the container definition uses the awslogs driver) and waits for the task to stop. Only if all containers have stopped and exited with `0` the job will be marked as success.
36+
37+
## Example 2: Get the log output of a task
38+
39+
```python
40+
import ecstaskrunner
41+
task = ecstaskrunner.task.Task(cluster='YOUR-CLUSTER-NAME', taskId='YOUR-TASK-ID')
42+
for container in task.containers:
43+
for line in task.containers[container].get_log_events():
44+
print "%s: %s" % (container, line)
45+
```

ecstaskrunner/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import boto3
2+
import sys
3+
import time
4+
import logging
5+
import botocore.errorfactory
6+
import ecstaskrunner
7+
import ecstaskrunner.task
8+
9+
def run_task(**kwargs):
10+
client = boto3.client('ecs')
11+
12+
response = client.run_task(**kwargs)
13+
14+
logger = logging.getLogger('ecstaskrunner')
15+
16+
taskResponse = response['tasks'][0]
17+
18+
taskId = taskResponse['taskArn'][taskResponse['taskArn'].rfind("/")+1:]
19+
logger.debug(taskId)
20+
21+
task = ecstaskrunner.task.Task(taskResponse['clusterArn'], taskId)
22+
23+
logger.debug("task is pending")
24+
while task.isPending():
25+
time.sleep(1)
26+
27+
while task.isRunning():
28+
for container in task.containers:
29+
for log_event in task.containers[container].get_log_events():
30+
logger.info("%s: %s" % (container, log_event))
31+
logger.debug("task status: %s" % task.getLastStatus())
32+
33+
exitCode = 0
34+
35+
for container in task.describeTask()['containers']:
36+
if 'reason' in container:
37+
logger.warn("%s failed: %s" % (container['name'], container['reason']))
38+
exitCode = 1
39+
continue
40+
41+
logger.info("%s exited with code %d" % (container['name'], container['exitCode']))
42+
if container['exitCode'] != 0:
43+
exitCode = 2
44+
45+
return exitCode

ecstaskrunner/container.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import boto3
2+
import logging
3+
import datetime
4+
5+
class Container:
6+
def __init__(self, taskId, config, definition):
7+
self.taskId = taskId
8+
self.config = config
9+
self.definition = definition
10+
self.name = config['name']
11+
self.startTime = None
12+
self.logger = logging.getLogger("Container")
13+
14+
def get_log_events(self):
15+
logConfig = self.definition['logConfiguration']
16+
tasklogger = logging.getLogger(self.name)
17+
18+
logs = boto3.client('logs')
19+
20+
logStreamName = '%s/%s/%s' % (
21+
logConfig['options']['awslogs-stream-prefix'],
22+
self.name,
23+
self.taskId
24+
)
25+
nextToken = False
26+
while True:
27+
a = {
28+
'logGroupName': logConfig['options']['awslogs-group'],
29+
'logStreamName': logStreamName,
30+
'startFromHead': True,
31+
'limit': 2
32+
}
33+
34+
if nextToken:
35+
a['nextToken'] = nextToken
36+
else:
37+
if self.startTime:
38+
a['startTime'] = self.startTime
39+
40+
try:
41+
response = logs.get_log_events(**a)
42+
except Exception as e:
43+
# todo not sure why i cannot check for the class directly
44+
if e.__class__.__name__ == 'ResourceNotFoundException':
45+
self.logger.warn(e)
46+
return
47+
raise e
48+
for event in response['events']:
49+
yield "[%s] %s" % (
50+
datetime.datetime.fromtimestamp(
51+
event['timestamp']/1000
52+
),
53+
event['message'])
54+
self.startTime = event['timestamp']+1
55+
56+
if len(response['events']) != a['limit']:
57+
self.logger.debug("[EOS]")
58+
break
59+
60+
nextToken = response['nextForwardToken']
61+

ecstaskrunner/task.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import boto3
2+
import logging
3+
from ecstaskrunner.container import Container
4+
5+
class Task:
6+
def __init__(self, cluster, taskId):
7+
self.cluster = cluster
8+
self.taskId = taskId
9+
self.containers = {}
10+
self.client = boto3.client('ecs')
11+
self.logger = logging.getLogger('Task')
12+
13+
task = self.describeTask()
14+
15+
if not task:
16+
self.logger.warn("Task with id %s does not exist" % taskId)
17+
return
18+
19+
self.taskDefinitionArn = task['taskDefinitionArn']
20+
containers = task['containers']
21+
response = self.client.describe_task_definition(taskDefinition=self.taskDefinitionArn)
22+
containerDefinitions = response['taskDefinition']['containerDefinitions']
23+
24+
self.logger.debug("task definition arn: %s" % self.taskDefinitionArn)
25+
26+
for container in self.describeTask()['containers']:
27+
self.containers[container['name']] = Container(
28+
self.taskId,
29+
container,
30+
[x for x in containerDefinitions if x['name'] == container['name']][0]
31+
)
32+
33+
def describeTask(self):
34+
response = self.client.describe_tasks(cluster=self.cluster, tasks=[self.taskId])
35+
if len(response['tasks']) != 1:
36+
return None
37+
return response['tasks'][0]
38+
39+
def getLastStatus(self):
40+
return self.describeTask()['lastStatus']
41+
42+
def isRunning(self):
43+
return self.getLastStatus() == "RUNNING"
44+
45+
def isPending(self):
46+
return self.getLastStatus() == "PENDING"

setup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
3+
from distutils.core import setup
4+
5+
setup(name='ecstaskrunner',
6+
version='0.0.1',
7+
description='Helper tools to run a task on AWS ECS and follow the logs',
8+
author='Michael Osl',
9+
author_email='[email protected]',
10+
url='https://github.com/moee/ecstaskrunner',
11+
packages=['ecstaskrunner'],
12+
install_requires=['boto3']
13+
)
14+

0 commit comments

Comments
 (0)