Support for docker swarm mode
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 4a51162..7884960 100644
--- a/README.rst
+++ b/README.rst
@@ -26,6 +26,43 @@
         insecure_registries:
           - 127.0.0.1
 
+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
 ------
 
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/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 %}