Skip to content

Commit

Permalink
Update to 1.0
Browse files Browse the repository at this point in the history
* Closes #35
* Closes #33
* Adds support for compose v2
  • Loading branch information
micahhausler committed Feb 8, 2016
1 parent 162bc51 commit 28f4bc7
Show file tree
Hide file tree
Showing 19 changed files with 352 additions and 31 deletions.
5 changes: 4 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
.. image:: https://coveralls.io/repos/micahhausler/container-transform/badge.png?branch=master
:target: https://coveralls.io/r/micahhausler/container-transform?branch=master

.. image:: https://readthedocs.org/projects/container-transform/badge/?version=latest
:target: http://container-transform.readthedocs.org/en/latest/?badge=latest
:alt: Documentation Status


container-transform
Expand All @@ -12,7 +15,7 @@ container-transform is a small utility to transform various docker container
formats to one another.

Currently, container-transform can parse and convert ECS task definitions and
docker-compose configuration files. It can also output Systemd unit files. Any
docker-compose configuration files (v1 and v2). It can also output Systemd unit files. Any
missing required parameters are printed to STDERR.

Quickstart
Expand Down
59 changes: 56 additions & 3 deletions container_transform/compose.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from functools import reduce

import yaml

Expand All @@ -7,7 +8,7 @@

class ComposeTransformer(BaseTransformer):
"""
A transformer for docker-compose
A transformer for docker-compose v1 and v2
To use this class:
Expand All @@ -17,6 +18,27 @@ class ComposeTransformer(BaseTransformer):
normalized_keys = transformer.ingest_containers()
"""
def __init__(self, filename=None):
"""
We override ``.__init__()`` on purpose, we need to get the volume,
version, network, and possibly other data.
:param filename: The file to be loaded
:type filename: str
"""
if filename:
self._filename = filename
stream = self._read_file(filename)
self.stream_version = int(stream.get('version', '1'))

if self.stream_version > 1:
self.stream = stream.get('services')
self.volumes = stream.get('volumes', None)
self.networks = stream.get('networks', None)
else:
self.stream = stream
else:
self.stream = None

def _read_stream(self, stream):
return yaml.safe_load(stream=stream)
Expand All @@ -38,14 +60,19 @@ def ingest_containers(self, containers=None):

def emit_containers(self, containers, verbose=True):

output = {}
services = {}
for container in containers:
name_in_container = container.get('name')
if not name_in_container:
name = str(uuid.uuid4())
else:
name = container.pop('name')
output[name] = container
services[name] = container

output = {
'services': services,
'version': '2',
}

noalias_dumper = yaml.dumper.SafeDumper
noalias_dumper.ignore_aliases = lambda self, data: True
Expand Down Expand Up @@ -243,3 +270,29 @@ def emit_volumes(self, volumes):
in volumes
if len(self._emit_volume(volume))
]

@staticmethod
def _parse_label_string(label):
eq = label.find('=')
if eq == -1:
return {label: None}
else:
return {label[:eq]: label[eq+1:]}

def ingest_labels(self, labels):
if isinstance(labels, list):
return reduce(
lambda a, b: a.update(b) or a,
map(self._parse_label_string, labels),
{}
)
return labels

def emit_labels(self, labels):
return labels

def ingest_logging(self, logging):
return logging

def emit_logging(self, logging):
return logging
22 changes: 22 additions & 0 deletions container_transform/ecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,15 @@ def add_volume(self, volume):

def emit_containers(self, containers, verbose=True):
"""
Emits the task definition and sorts containers by name
:param containers: List of the container definitions
:type containers: list of dict
:param verbose: Print out newlines and indented JSON
:type verbose: bool
:return: The text output
:rtype: str
"""
containers = sorted(containers, key=lambda c: c.get('name'))
task_definition = {
'family': self.family,
'containerDefinitions': containers,
Expand Down Expand Up @@ -253,3 +255,23 @@ def emit_volumes(self, volumes):
in volumes
if self._build_mountpoint(volume) is not None
]

def ingest_labels(self, labels):
return labels

def emit_labels(self, labels):
return labels

def ingest_logging(self, logging):
data = logging
if data.get('logDriver'): # pragma: no cover
data['driver'] = data.get('logDriver')
del data['logDriver']
return logging

def emit_logging(self, logging):
data = logging
if data.get('driver'): # pragma: no cover
data['logDriver'] = data.get('driver')
del data['driver']
return logging
29 changes: 28 additions & 1 deletion container_transform/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,5 +289,32 @@ class OutputTransformationTypes(Enum):
'required': False
},
},

'labels': {
TransformationTypes.ECS.value: {
'name': 'dockerLabels',
'required': False
},
TransformationTypes.COMPOSE.value: {
'name': 'labels',
'required': False
},
TransformationTypes.SYSTEMD.value: {
'name': 'labels',
'required': False
},
},
'logging': {
TransformationTypes.ECS.value: {
'name': 'logConfiguration',
'required': False
},
TransformationTypes.COMPOSE.value: {
'name': 'logging',
'required': False
},
TransformationTypes.SYSTEMD.value: {
'name': 'logging',
'required': False
},
}
}
19 changes: 19 additions & 0 deletions container_transform/systemd.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@
--net {{ net }} \\{% endif -%}
{% for volume in volumes %}
-v {{ volume }} \\{% endfor -%}
{%- if logging %}
{% if logging.driver -%}
--log-driver={{ logging.driver }} \\{% endif -%}
{% if logging.options %}{% for opt in logging.options|dictsort %}
--log-opt {{ opt[0] }}={{ opt[1] }} \\{% endfor -%}{% endif %}{% endif -%}
{% if environment %}{% for env in environment|dictsort %}
-e "{{ env[0] }}={{ env[1] }}" \\{% endfor -%}{% endif -%}
{% if labels %}{% for label in labels|dictsort %}
--label {{ label[0] }}="{{ label[1] }}" \\{% endfor -%}{% endif -%}
{% for link in links %}
--link {{ link }} \\{% endfor -%}
{% for vf in volumes_from %}
Expand Down Expand Up @@ -162,3 +169,15 @@ def emit_volumes(self, volumes):
in volumes
if len(self._emit_volume(volume))
]

def ingest_labels(self, labels):
pass

def emit_labels(self, labels):
return labels

def ingest_logging(self, logging):
pass

def emit_logging(self, logging):
return logging
8 changes: 5 additions & 3 deletions container_transform/tests/compose_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ def test_emit_containers_no_name(self, mock_uuid):

self.assertEqual(
(
'{mock_uuid}:\n'
' cpu: 200\n'
' image: postgres:9.3\n'
'services:\n'
' {mock_uuid}:\n'
' cpu: 200\n'
' image: postgres:9.3\n'
'version: \'2\'\n'
).format(mock_uuid=mock_uuid.return_value),
output
)
Expand Down
17 changes: 17 additions & 0 deletions container_transform/tests/composev2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '2'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
labels:
com.example.description: "Accounting webapp"
com.example.department: "Finance"
com.example.label-with-empty-value: ""
logging:
driver: gelf
options:
tag: web
gelf-address: "udp://127.0.0.1:12900"
23 changes: 23 additions & 0 deletions container_transform/tests/composev2_extended.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: '2'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
labels:
com.example.name: "web"
com.example.emptyvalue: ""
logging:
driver: gelf
options:
tag: web
gelf-address: "udp://127.0.0.1:12900"
redis:
image: redis
ports:
- "6379"
labels:
- io.redis.name=redis
- io.redis.novalue
54 changes: 54 additions & 0 deletions container_transform/tests/composev2_extended_output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"containerDefinitions": [
{
"dockerLabels": {
"io.redis.name": "redis",
"io.redis.novalue": null
},
"essential": true,
"image": "redis",
"name": "redis",
"portMappings": [
{
"containerPort": 6379
}
]
},
{
"dockerLabels": {
"com.example.emptyvalue": "",
"com.example.name": "web"
},
"essential": true,
"logConfiguration": {
"logDriver": "gelf",
"options": {
"gelf-address": "udp://127.0.0.1:12900",
"tag": "web"
}
},
"mountPoints": [
{
"containerPath": "/code",
"sourceVolume": "_"
}
],
"name": "web",
"portMappings": [
{
"containerPort": 5000,
"hostPort": 5000
}
]
}
],
"family": "",
"volumes": [
{
"host": {
"sourcePath": "."
},
"name": "_"
}
]
}
41 changes: 41 additions & 0 deletions container_transform/tests/composev2_output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"containerDefinitions": [
{
"dockerLabels": {
"com.example.department": "Finance",
"com.example.description": "Accounting webapp",
"com.example.label-with-empty-value": ""
},
"essential": true,
"logConfiguration": {
"logDriver": "gelf",
"options": {
"gelf-address": "udp://127.0.0.1:12900",
"tag": "web"
}
},
"mountPoints": [
{
"containerPath": "/code",
"sourceVolume": "_"
}
],
"name": "web",
"portMappings": [
{
"containerPort": 5000,
"hostPort": 5000
}
]
}
],
"family": "",
"volumes": [
{
"host": {
"sourcePath": "."
},
"name": "_"
}
]
}
22 changes: 22 additions & 0 deletions container_transform/tests/composev2_output.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# web.service #######################################################################
[Unit]
Description=Web
After=docker.service
Requires=docker.service

[Service]
ExecStartPre=-/usr/bin/docker kill web
ExecStartPre=-/usr/bin/docker rm web
ExecStartPre=/usr/bin/docker pull <image>
ExecStart=/usr/bin/docker run \
--name web \
-p 5000:5000 \
-v .:/code \
--log-driver=gelf \
--log-opt gelf-address=udp://127.0.0.1:12900 \
--log-opt tag=web \
--label com.example.department="Finance" \
--label com.example.description="Accounting webapp" \
--label com.example.label-with-empty-value="" \
<image>
ExecStop=/usr/bin/docker stop web

0 comments on commit 28f4bc7

Please sign in to comment.