Merge pull request #5 from tcpcloud/logs

docker logs
diff --git a/Makefile b/Makefile
index fc83783..e4c5a0e 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@
 	cp -a $(FORMULANAME) $(DESTDIR)/$(SALTENVDIR)/
 	[ ! -d _modules ] || cp -a _modules $(DESTDIR)/$(SALTENVDIR)/
 	[ ! -d _states ] || cp -a _states $(DESTDIR)/$(SALTENVDIR)/ || true
+	[ ! -d _grains ] || cp -a _grains $(DESTDIR)/$(SALTENVDIR)/ || true
 	# Metadata
 	[ -d $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME) ] || mkdir -p $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME)
 	cp -a metadata/service/* $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME)
diff --git a/README.rst b/README.rst
index 31dff43..5cea699 100644
--- a/README.rst
+++ b/README.rst
@@ -29,6 +29,43 @@
           engine: json-file
           size: 50m
 
+Swarm
+-----
+
+Role can be master, manager or worker. Where master is the first manager that
+will initialize the swarm.
+
+Metadata for manager (first node):
+
+.. code-block:: yaml
+
+    docker:
+      host:
+        enabled: true
+      swarm:
+        role: manager
+        advertise_addr: 192.168.1.5
+        bind:
+          address: 192.168.1.5
+          port: 2377
+
+Metadata for worker:
+
+.. code-block:: yaml
+
+    docker:
+      host:
+        enabled: true
+      swarm:
+        role: worker
+        master:
+          host: 192.168.1.5
+          port: 2377
+
+Token to join to master node is obtained from grains using salt.mine.  In case
+of any ``join_token undefined`` issues, ensure you have ``docker_swarm_``
+grains available.
+
 Client
 ------
 
@@ -93,6 +130,33 @@
                 depends_on:
                   - db
 
+Service
+-------
+
+To deploy service in Swarm mode, you can use ``docker.client.service``:
+
+.. code-block:: yaml
+
+    parameters:
+      docker:
+        client:
+          service:
+            postgresql:
+              environment:
+                POSTGRES_USER: user
+                POSTGRES_PASSWORD: password
+                POSTGRES_DB: mydb
+              restart:
+                condition: on-failure
+              image: "postgres:9.5"
+              ports:
+                - 5432:5432
+              volume:
+                data:
+                  type: bind
+                  source: /srv/volumes/postgresql/maas
+                  destination: /var/lib/postgresql/data
+
 
 Registry
 --------
diff --git a/_grains/docker_swarm.py b/_grains/docker_swarm.py
new file mode 100644
index 0000000..8b8ecb7
--- /dev/null
+++ b/_grains/docker_swarm.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+import os
+import yaml
+import subprocess
+
+
+def main():
+    output = {}
+    if os.path.exists('/var/lib/docker/swarm/control.sock'):
+        try:
+            output["docker_swarm_tokens"] = {
+                'worker': subprocess.check_output(["docker", "swarm", "join-token", "-q", "worker"]).strip(),
+                'manager': subprocess.check_output(["docker", "swarm", "join-token", "-q", "manager"]).strip()
+            }
+        except subprocess.CalledProcessError:
+            pass
+
+    if os.path.exists('/var/lib/docker/swarm/state.json'):
+        with open('/var/lib/docker/swarm/state.json') as fh:
+            state = yaml.load(fh)
+            for key, value in state[0].iteritems():
+                output["docker_swarm_%s" % key] = value
+
+    if os.path.exists('/var/lib/docker/swarm/docker-state.json'):
+        with open('/var/lib/docker/swarm/docker-state.json') as fh:
+            state = yaml.load(fh)
+            for key, value in state.iteritems():
+                output["docker_swarm_%s" % key] = value
+
+    if output:
+        return output
+    else:
+        return None
diff --git a/docker/client.sls b/docker/client.sls
index 0ed5638..6de632b 100644
--- a/docker/client.sls
+++ b/docker/client.sls
@@ -6,6 +6,9 @@
   {%- if client.compose is defined %}
   - docker.client.compose
   {%- endif %}
+  {%- if client.service is defined %}
+  - docker.client.service
+  {%- endif %}
 
 docker_python:
   pkg.installed:
diff --git a/docker/client/service.sls b/docker/client/service.sls
new file mode 100644
index 0000000..272ba71
--- /dev/null
+++ b/docker/client/service.sls
@@ -0,0 +1,64 @@
+{% from "docker/map.jinja" import client with context %}
+
+include:
+  - docker.client
+
+{%- for name, service in client.get('service', {}).iteritems() %}
+{%- if service.get('enabled', True) %}
+
+docker_service_{{ name }}_create:
+  cmd.run:
+    - name: >
+        docker service create
+        --name {{ name }}
+        --with-registry-auth
+        {%- for env, value in service.get('environment', {}).iteritems() %} -e {{ env }}="{{ value }}"{%- endfor %}
+        {%- for port in service.get('ports', []) %} -p {{ port }}{%- endfor %}
+        {%- for label, value in service.get('label', {}).iteritems() %} -l {{ label }}="{{ value }}"{%- endfor %}
+        {%- if service.network is defined %} --network {{ service.network }}{%- endif %}
+        {%- if service.replicas is defined %} --replicas {{ service.replicas }}{%- endif %}
+        {%- if service.user is defined %} --user {{ service.user }}{%- endif %}
+        {%- if service.workdir is defined %} --workdir {{ service.workdir }}{%- endif %}
+        {%- if service.mode is defined %} --mode {{ service.mode }}{%- endif %}
+        {%- if service.endpoint is defined %} --endpoint-mode {{ service.endpoint }}{%- endif %}
+        {%- if service.constraint is defined %} --constraint {{ service.constraint }}{%- endif %}
+        {%- for name, volume in service.get('volume', {}).iteritems() %} --mount {% for key, value in volume.iteritems() %}{{ key }}={{ value }}{% if not loop.last %},{% endif %}{% endfor %}{%- endfor %}
+        {%- for param, value in service.get('restart', {}).iteritems() %} --restart-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('update', {}).iteritems() %} --update-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('log', {}).iteritems() %} --log-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('limit', {}).iteritems() %} --limit-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('reserve', {}).iteritems() %} --reserve-{{ param }} {{ value }}{%- endfor %}
+        {{ service.image }}
+    - unless: "docker service ls | grep {{ name }}"
+
+{%- if service.get('update_service', False) %}
+docker_service_{{ name }}_update:
+  cmd.run:
+    - name: >
+        docker service update
+        --name {{ name }}
+        --with-registry-auth
+        {%- if service.replicas is defined %} --replicas {{ service.replicas }}{%- endif %}
+        {%- if service.user is defined %} --user {{ service.user }}{%- endif %}
+        {%- if service.workdir is defined %} --workdir {{ service.workdir }}{%- endif %}
+        {%- if service.endpoint is defined %} --endpoint-mode {{ service.endpoint }}{%- endif %}
+        {%- for param, value in service.get('restart', {}).iteritems() %} --restart-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('update', {}).iteritems() %} --update-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('log', {}).iteritems() %} --log-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('limit', {}).iteritems() %} --limit-{{ param }} {{ value }}{%- endfor %}
+        {%- for param, value in service.get('reserve', {}).iteritems() %} --reserve-{{ param }} {{ value }}{%- endfor %}
+        --image {{ service.image }}
+        {{ name }}
+    - require:
+      - cmd: docker_service_{{ name }}_create
+{%- endif %}
+
+{%- else %}
+
+docker_service_{{ name }}_rm:
+  cmd.run:
+    - name: docker service rm {{ name }}
+    - onlyif: "docker service ls | grep {{ name }}"
+
+{%- endif %}
+{%- endfor %}
diff --git a/docker/init.sls b/docker/init.sls
index 6256940..ac6eeca 100644
--- a/docker/init.sls
+++ b/docker/init.sls
@@ -3,6 +3,9 @@
 {%- if pillar.docker.host is defined %}
 - docker.host
 {%- endif %}
+{%- if pillar.docker.swarm is defined %}
+- docker.swarm
+{%- endif %}
 {%- if pillar.docker.client is defined %}
 - docker.client
 {%- endif %}
diff --git a/docker/map.jinja b/docker/map.jinja
index d475371..8644de5 100644
--- a/docker/map.jinja
+++ b/docker/map.jinja
@@ -18,6 +18,14 @@
     },
 }, grain='os', merge=salt['pillar.get']('docker:host')) %}
 
+{% set swarm = salt['grains.filter_by']({
+    'default': {
+        'master': {
+            'port': 2377
+        }
+    }
+}, grain='os', merge=salt['pillar.get']('docker:swarm')) %}
+
 {% set client = salt['grains.filter_by']({
     'default': {
         'pkgs': ['python-docker'],
diff --git a/docker/swarm.sls b/docker/swarm.sls
new file mode 100644
index 0000000..314c183
--- /dev/null
+++ b/docker/swarm.sls
@@ -0,0 +1,48 @@
+{% from "docker/map.jinja" import swarm with context %}
+{%- if swarm.enabled|default(True) %}
+
+include:
+  - docker.host
+
+{%- if swarm.role == 'master' %}
+
+docker_swarm_init:
+  cmd.run:
+    - name: >
+        docker swarm init
+        {%- if swarm.advertise_addr is defined %} --advertise-addr {{ swarm.advertise_addr }}{%- endif %}
+        {%- if swarm.get('bind', {}).get('address', None) %} --listen-addr {{ swarm.bind.address }}{% if swarm.bind.port is defined %}:{{ swarm.bind.port }}{% endif %}{%- endif %}
+    - unless: "test -e /var/lib/docker/swarm/control.sock"
+    - require:
+      - service: docker_service
+
+docker_swarm_grains_publish:
+  module.run:
+  - name: mine.update
+  - watch:
+    - cmd: docker_swarm_init
+
+{%- else %}
+
+{%- for node_name, node_grains in salt['mine.get']('*', 'grains.items').iteritems() %}
+{%- if node_grains.get("docker_swarm_AdvertiseAddr", None) == swarm.master.host+":"+swarm.master.port|string %}
+{%- set join_token = node_grains.get('docker_swarm_tokens').get(swarm.role, "unknown") %}
+
+docker_swarm_join:
+  cmd.run:
+    - name: >
+        docker swarm join
+        --token {{ join_token }}
+        {%- if swarm.advertise_addr is defined %} --advertise-addr {{ swarm.advertise_addr }}{%- endif %}
+        {%- if swarm.get('bind', {}).get('address', None) %} --listen-addr {{ swarm.bind.address }}{% if swarm.bind.port is defined %}:{{ swarm.bind.port }}{% endif %}{%- endif %}
+        {{ swarm.master.host }}:{{ swarm.master.port }}
+    - unless: "test -e /var/lib/docker/swarm/control.sock"
+    - require:
+      - service: docker_service
+
+{%- endif %}
+{%- endfor %}
+
+{%- endif %}
+
+{%- endif %}