Merge "Adding environment variables for salt minion"
diff --git a/.kitchen.yml b/.kitchen.yml
index ebfa3b9..888f83f 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -8,7 +8,7 @@
   name: salt_solo
   salt_install: bootstrap
   salt_bootstrap_url: https://bootstrap.saltstack.com
-  salt_version: latest
+  salt_version: <%=ENV['SALT_VERSION'] || 'latest'%>
   salt_minion_id: salt.ci.local
   require_chef: false
   log_level: error
@@ -150,6 +150,7 @@
         control_cloud_digitalocean.sls: tests/pillar/control_cloud_digitalocean.sls
         control_cloud_openstack.sls: tests/pillar/control_cloud_openstack.sls
         control_virt.sls: tests/pillar/control_virt.sls
+        control_virt_custom.sls: tests/pillar/control_virt_custom.sls
 
   - name: minion_multi_master_failover
     provisioner:
diff --git a/.travis.yml b/.travis.yml
index eed1da2..cdba2b5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,16 +17,29 @@
   - bundle install
 
 env:
-  - PLATFORM=trevorj/salty-whales:trusty SUITE=minion-default
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=minion-default
-  - PLATFORM=trevorj/salty-whales:trusty SUITE=master-default
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=master-default
-  - PLATFORM=trevorj/salty-whales:trusty SUITE=minion-default
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=minion-default
+
+  - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=control-default
+  - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=master-default
+  - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=minion-default
+  - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=minion-multi-master-failover
+  - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=control-default
+  - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=master-default
+  - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=minion-default
+  - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=minion-multi-master-failover
+# TODO, once 18.04 LTS Bionic Beaver is released, rename trusty to bionic
+  - PLATFORM=trevorj/salty-whales:bionic-2017.7 SUITE=master-default
+  - PLATFORM=trevorj/salty-whales:bionic-2017.7 SUITE=minion-default
   - PLATFORM=trevorj/salty-whales:trusty SUITE=control-default
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=control-default
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=master-default
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=minion-default
   - PLATFORM=trevorj/salty-whales:trusty SUITE=minion-multi-master-failover
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=control-default
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=master-default
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=minion-default
   - PLATFORM=trevorj/salty-whales:xenial SUITE=minion-multi-master-failover
+# TODO, once 18.04 LTS Bionic Beaver is released, rename trusty to bionic
+  - PLATFORM=trevorj/salty-whales:bionic SUITE=master-default
+  - PLATFORM=trevorj/salty-whales:bionic SUITE=minion-default
 
 before_script:
   - set -o pipefail
diff --git a/README.rst b/README.rst
index d8d8674..60c5c7b 100644
--- a/README.rst
+++ b/README.rst
@@ -28,6 +28,21 @@
 .. literalinclude:: tests/pillar/master_single_reclass.sls
    :language: yaml
 
+Salt master with Architect ENC metadata backend
+
+.. code-block:: yaml
+
+    salt:
+      master:
+        enabled: true
+        pillar:
+          engine: architect
+          project: project-name
+          host: architect-api
+          port: 8181
+          username: salt
+          password: password
+
 Salt master with multiple ext_pillars
 
 .. literalinclude:: tests/pillar/master_single_extpillars.sls
@@ -150,7 +165,6 @@
               host: 127.0.0.1
               port: 9999
 
-
 Salt engine definition for saltgraph metadata collector
 
 .. code-block:: yaml
@@ -166,6 +180,21 @@
             password: salt
             database: salt
 
+Salt engine definition for Architect service
+
+.. code-block:: yaml
+
+    salt:
+      master:
+        engine:
+          architect:
+            engine: architect
+            project: project-name
+            host: architect-api
+            port: 8181
+            username: salt
+            password: password
+
 Salt engine definition for sending events from docker events
 
 .. code-block:: yaml
@@ -476,6 +505,21 @@
    :language: yaml
 
 
+Common salt config options
+--------------------------
+
+Pass pillar render error to minion log.
+
+.. Note: When set to `False` this option is great for debuging. However it is not recomended for
+         any production environment as it may contain templating data as passwords, etc...,
+         that minion should not have.
+
+.. code-block:: yaml
+
+    salt:
+      master:
+        pillar_safe_render_error: False
+
 Salt minion
 -----------
 
@@ -568,6 +612,11 @@
 .. literalinclude:: tests/pillar/control_virt.sls
    :language: yaml
 
+salt virt with custom destination for image file
+
+.. literalinclude:: tests/pillar/control_virt_custom.sls
+   :language: yaml
+
 
 Usage
 =====
diff --git a/_engines/architect.py b/_engines/architect.py
new file mode 100644
index 0000000..4096a73
--- /dev/null
+++ b/_engines/architect.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+"""
+Salt engine for intercepting state jobs and forwarding to the Architect
+service.
+"""
+
+# Import python libs
+from __future__ import absolute_import
+import json
+import logging
+
+# Import salt libs
+import salt.utils.event
+import salt.utils.http
+
+log = logging.getLogger(__name__)
+
+
+def start(project='default',
+          host='127.0.0.1',
+          port=8181,
+          username=None,
+          password=None):
+    '''
+    Listen to state jobs events and forward Salt states
+    '''
+    url = "{}://{}:{}/salt/{}/event/{}".format('http',
+                                               host,
+                                               port,
+                                               'v1',
+                                               project)
+    target_functions = ['state.sls', 'state.apply', 'state.highstate']
+
+    if __opts__['__role'] == 'master':
+        event_bus = salt.utils.event.get_master_event(__opts__,
+                                                      __opts__['sock_dir'],
+                                                      listen=True)
+    else:
+        event_bus = salt.utils.event.get_event(
+            'minion',
+            transport=__opts__['transport'],
+            opts=__opts__,
+            sock_dir=__opts__['sock_dir'],
+            listen=True)
+
+    log.info('Salt Architect engine initialised')
+
+    while True:
+        event = event_bus.get_event()
+        if event and event.get('fun', None) in target_functions:
+            is_test_run = 'test=true' in [arg.lower() for arg in event.get('fun_args', [])]
+            if not is_test_run:
+                data = salt.utils.http.query(url=url,
+                                             method='POST',
+                                             decode=False,
+                                             data=json.dumps(event))
+                if 'OK' in data.get('body', ''):
+                    log.info("Architect Engine request to '{}'"
+                             " was successful".format(url))
+                else:
+                    log.warning("Problem with Architect Engine"
+                                " request to '{}' ({})".format(url, data))
diff --git a/_modules/modelschema.py b/_modules/modelschema.py
index cb45153..5e319ae 100644
--- a/_modules/modelschema.py
+++ b/_modules/modelschema.py
@@ -132,7 +132,7 @@
         data = 'Schema is valid'
     except SchemaError as exc:
         LOG.error("SchemaError:{}".format(exc))
-        data = repr(exc)
+        raise Exception("SchemaError")
     return {'{}-{}'.format(service, role): data}
 
 
@@ -154,10 +154,15 @@
         data = 'Model is valid'
     except SchemaError as exc:
         LOG.error("SchemaError:{}".format(exc))
-        data = repr(exc)
+        raise Exception("SchemaError")
     except ValidationError as exc:
         LOG.error("ValidationError:{}\nInstance:{}\n"
-                  "SchemaPath:{}".format(exc.message, exc.instance,
+                  "Schema title:{}\n"
+                  "SchemaPath:{}".format(exc.message,
+                                         exc.instance,
+                                         exc.schema.get(
+                                             "title",
+                                             "Schema title not set!"),
                                          exc.schema_path))
         raise Exception("ValidationError")
     return {'{}-{}'.format(service, role): data}
@@ -176,10 +181,15 @@
         data = 'Model is valid'
     except SchemaError as exc:
         LOG.error("SchemaError:{}".format(exc))
-        data = str(exc)
+        raise Exception("SchemaError")
     except ValidationError as exc:
         LOG.error("ValidationError:{}\nInstance:{}\n"
-                  "SchemaPath:{}".format(exc.message, exc.instance,
+                  "Schema title:{}\n"
+                  "SchemaPath:{}".format(exc.message,
+                                         exc.instance,
+                                         exc.schema.get(
+                                             "title",
+                                             "Schema title not set!"),
                                          exc.schema_path))
         raise Exception("ValidationError")
     return data
@@ -214,9 +224,10 @@
             except Exception as exc:
                 LOG.error('{}: {}'.format(pillar, repr(exc)))
     if service not in raw_data.keys():
-        raise Exception(
-            "Could not find applicable  data "
-            "for:{}\n at:{}".format(service, _get_base_dir()))
+        LOG.error("Could not find applicable  data "
+                  "for:{}\n at:{}".format(service, _get_base_dir()))
+        raise Exception("DataError")
+
     data = raw_data[service]
     output = {}
     for role_name, role in data.items():
diff --git a/_modules/virtng.py b/_modules/virtng.py
index a79e5dd..304a6bb 100644
--- a/_modules/virtng.py
+++ b/_modules/virtng.py
@@ -313,7 +313,7 @@
     Detect information for the image at path
     '''
     ret = {}
-    out = __salt__['cmd.run']('qemu-img info {0}'.format(path))
+    out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
 
     match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
                  'format': r'file format: (\w+)'}
@@ -332,7 +332,7 @@
     '''
     Detect what driver needs to be used for the given image
     '''
-    out = __salt__['cmd.run']('qemu-img info {0}'.format(vda))
+    out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
     if 'file format: qcow2' in out:
         return 'qcow2'
     else:
@@ -354,7 +354,10 @@
     elif hypervisor in ['kvm', 'qemu']:
         ret['disktype'] = 'qcow2'
         ret['filename'] = '{0}{1}'.format(name, '.qcow2')
-        ret['pool'] = __salt__['config.option']('virt.images')
+        if 'img_dest' in kwargs:
+            ret['pool'] = kwargs['img_dest']
+        else:
+            ret['pool'] = __salt__['config.option']('virt.images')
     return ret
 
 
@@ -408,10 +411,11 @@
                    'pool': '[{0}] '.format(kwargs.get('pool', '0'))
                   }
     elif hypervisor in ['qemu', 'kvm']:
-        overlay = {'format': 'qcow2',
-                   'model': 'virtio',
-                   'pool': __salt__['config.option']('virt.images')
-                  }
+        if 'img_dest' in kwargs:
+            pool = kwargs['img_dest']
+        else:
+            pool = __salt__['config.option']('virt.images')
+        overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
     else:
         overlay = {}
 
@@ -589,7 +593,8 @@
                     xml = _gen_vol_xml(name,
                                        disk_name,
                                        args['size'],
-                                       hypervisor)
+                                       hypervisor,
+                                       **kwargs)
                     define_vol_xml_str(xml)
 
             elif hypervisor in ['qemu', 'kvm']:
@@ -599,7 +604,10 @@
                 # disk size TCP cloud
                 disk_size = args['size']
 
-                img_dir = __salt__['config.option']('virt.images')
+                if 'img_dest' in kwargs:
+                    img_dir = kwargs['img_dest']
+                else:
+                    img_dir = __salt__['config.option']('virt.images')
                 img_dest = os.path.join(
                     img_dir,
                     name,
@@ -1591,7 +1599,7 @@
     except IOError:
         # No /proc/modules? Are we on Windows? Or Solaris?
         return False
-    return 'libvirtd' in __salt__['cmd.run'](__grains__['ps'])
+    return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
 
 
 def is_xen_hyper():
@@ -1616,7 +1624,7 @@
     except IOError:
         # No /proc/modules? Are we on Windows? Or Solaris?
         return False
-    return 'libvirtd' in __salt__['cmd.run'](__grains__['ps'])
+    return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
 
 
 def is_hyper():
diff --git a/_states/httpng.py b/_states/httpng.py
new file mode 100644
index 0000000..a539ec0
--- /dev/null
+++ b/_states/httpng.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+'''
+HTTP monitoring states
+
+Perform an HTTP query and statefully return the result
+
+TODO: This is a copy of upstream state file: https://github.com/saltstack/salt/blob/2017.7.3/salt/states/http.py.
+It have to be removed when MCP Salt will be upgreaded up to 2017 or higher.
+
+.. versionadded:: 2015.5.0
+'''
+
+# Import python libs
+from __future__ import absolute_import
+import re
+import logging
+import time
+
+__monitor__ = [
+        'query',
+        ]
+
+log = logging.getLogger(__name__)
+
+
+def query(name, match=None, match_type='string', status=None, wait_for=None, **kwargs):
+    '''
+    Perform an HTTP query and statefully return the result
+
+    .. versionadded:: 2015.5.0
+
+    name
+        The name of the query.
+
+    match
+        Specifies a pattern to look for in the return text. By default, this will
+        perform a string comparison of looking for the value of match in the return
+        text.
+
+    match_type
+        Specifies the type of pattern matching to use. Default is ``string``, but
+        can also be set to ``pcre`` to use regular expression matching if a more
+        complex pattern matching is required.
+
+        .. note::
+
+            Despite the name of ``match_type`` for this argument, this setting
+            actually uses Python's ``re.search()`` function rather than Python's
+            ``re.match()`` function.
+
+    status
+        The status code for a URL for which to be checked. Can be used instead of
+        or in addition to the ``match`` setting.
+
+    If both ``match`` and ``status`` options are set, both settings will be checked.
+    However, note that if only one option is ``True`` and the other is ``False``,
+    then ``False`` will be returned. If this case is reached, the comments in the
+    return data will contain troubleshooting information.
+
+    For more information about the ``http.query`` state, refer to the
+    :ref:`HTTP Tutorial <tutorial-http>`.
+
+    .. code-block:: yaml
+
+        query_example:
+          http.query:
+            - name: 'http://example.com/'
+            - status: 200
+
+    '''
+    # Monitoring state, but changes may be made over HTTP
+    ret = {'name': name,
+           'result': None,
+           'comment': '',
+           'changes': {},
+           'data': {}}  # Data field for monitoring state
+
+    if match is None and status is None:
+        ret['result'] = False
+        ret['comment'] += (
+            ' Either match text (match) or a status code (status) is required.'
+        )
+        return ret
+
+    if 'decode' not in kwargs:
+        kwargs['decode'] = False
+    kwargs['text'] = True
+    kwargs['status'] = True
+    if __opts__['test']:
+        kwargs['test'] = True
+
+    if wait_for:
+        data = __salt__['http.wait_for_successful_query'](name, wait_for=wait_for, **kwargs)
+    else:
+        data = __salt__['http.query'](name, **kwargs)
+
+    if match is not None:
+        if match_type == 'string':
+            if match in data.get('text', ''):
+                ret['result'] = True
+                ret['comment'] += ' Match text "{0}" was found.'.format(match)
+            else:
+                ret['result'] = False
+                ret['comment'] += ' Match text "{0}" was not found.'.format(match)
+        elif match_type == 'pcre':
+            if re.search(match, data.get('text', '')):
+                ret['result'] = True
+                ret['comment'] += ' Match pattern "{0}" was found.'.format(match)
+            else:
+                ret['result'] = False
+                ret['comment'] += ' Match pattern "{0}" was not found.'.format(match)
+
+    if status is not None:
+        if data.get('status', '') == status:
+            ret['comment'] += 'Status {0} was found, as specified.'.format(status)
+            if ret['result'] is None:
+                ret['result'] = True
+        else:
+            ret['comment'] += 'Status {0} was not found, as specified.'.format(status)
+            ret['result'] = False
+
+    if __opts__['test'] is True:
+        ret['result'] = None
+        ret['comment'] += ' (TEST MODE'
+        if 'test_url' in kwargs:
+            ret['comment'] += ', TEST URL WAS: {0}'.format(kwargs['test_url'])
+        ret['comment'] += ')'
+
+    ret['data'] = data
+    return ret
+
+
+def wait_for_successful_query(name, wait_for=300, **kwargs):
+    '''
+    Like query but, repeat and wait until match/match_type or status is fulfilled. State returns result from last
+    query state in case of success or if no successful query was made within wait_for timeout.
+
+    name
+        The name of the query.
+
+    wait_for
+        Total time to wait for requests that succeed.
+
+    request_interval
+        Optional interval to delay requests by N seconds to reduce the number of requests sent.
+
+    .. note::
+
+        All other arguements are passed to the http.query state.
+    '''
+    starttime = time.time()
+
+    while True:
+        caught_exception = None
+        ret = None
+        try:
+            ret = query(name, **kwargs)
+            if ret['result']:
+                return ret
+        except Exception as exc:
+            caught_exception = exc
+
+        if time.time() > starttime + wait_for:
+            if not ret and caught_exception:
+                # workaround pylint bug https://www.logilab.org/ticket/3207
+                raise caught_exception  # pylint: disable=E0702
+            return ret
+        else:
+            # Space requests out by delaying for an interval
+            if 'request_interval' in kwargs:
+                log.debug("delaying query for {0} seconds.".format(kwargs['request_interval']))
+                time.sleep(kwargs['request_interval'])
diff --git a/debian/control b/debian/control
index 8c56def..9f78368 100644
--- a/debian/control
+++ b/debian/control
@@ -11,7 +11,7 @@
 
 Package: salt-formula-salt
 Architecture: all
-Depends: ${misc:Depends}, salt-master, reclass,
+Depends: ${misc:Depends},
  salt-formula-git, salt-formula-reclass
 Description: Salt salt formula
  Install and configure Salt masters and minions.
diff --git a/metadata/service/master/syndic.yml b/metadata/service/master/syndic.yml
new file mode 100644
index 0000000..470cbe0
--- /dev/null
+++ b/metadata/service/master/syndic.yml
@@ -0,0 +1,4 @@
+parameters:
+  salt:
+    master:
+      order_masters: true
diff --git a/metadata/service/syndic/cluster.yml b/metadata/service/syndic/cluster.yml
new file mode 100644
index 0000000..278b7ce
--- /dev/null
+++ b/metadata/service/syndic/cluster.yml
@@ -0,0 +1,7 @@
+parameters:
+  salt:
+    syndic:
+      enabled: true
+      masters:
+      - host: ${_param:salt_syndic_master_address_01}
+      - host: ${_param:salt_syndic_master_address_02}
diff --git a/metadata/service/syndic/single.yml b/metadata/service/syndic/single.yml
new file mode 100644
index 0000000..088cbdf
--- /dev/null
+++ b/metadata/service/syndic/single.yml
@@ -0,0 +1,6 @@
+parameters:
+  salt:
+    syndic:
+      enabled: true
+      master:
+        host: ${_param:salt_syndic_master_address}
diff --git a/salt/control/virt.sls b/salt/control/virt.sls
index b30f7af..84d9c12 100644
--- a/salt/control/virt.sls
+++ b/salt/control/virt.sls
@@ -30,6 +30,7 @@
 
 {%- set size = control.size.get(node.size) %}
 
+
 salt_control_virt_{{ cluster_name }}_{{ node_name }}:
   module.run:
   - name: virtng.init
@@ -44,6 +45,9 @@
       seed: True
       serial_type: pty
       console: True
+      {%- if node.img_dest is defined %}
+      img_dest: {{ node.img_dest }}
+      {%- endif %}
   - unless: virsh list --all --name| grep -E "^{{ node_name }}.{{ cluster.domain }}$"
 
 #salt_control_seed_{{ cluster_name }}_{{ node_name }}:
@@ -61,9 +65,9 @@
   - vm_: {{ node_name }}.{{ cluster.domain }}
   - state: true
   - unless: virsh list --autostart --name| grep -E "^{{ node_name }}.{{ cluster.domain }}$"
-  
+
 {%- endif %}
-  
+
 {%- endif %}
 
 {%- endfor %}
diff --git a/salt/files/architect.yml b/salt/files/architect.yml
new file mode 100644
index 0000000..dcba23f
--- /dev/null
+++ b/salt/files/architect.yml
@@ -0,0 +1,8 @@
+{% from "salt/map.jinja" import master with context %}
+project: {{ master.pillar.project }}
+host: {{ master.pillar.host }}
+port: {{ master.pillar.port }}
+{%- if master.pillar.username is defined %}
+username: {{ master.pillar.username }}
+password: {{ master.pillar.password }}
+{%- endif %}
diff --git a/salt/files/master.conf b/salt/files/master.conf
index d8952e9..9f9b00a 100644
--- a/salt/files/master.conf
+++ b/salt/files/master.conf
@@ -40,6 +40,8 @@
 
 pillar_opts: False
 
+pillar_safe_render_error: {{ master.pillar_safe_render_error }}
+
 {%- if master.accept_policy == 'open_mode' %}
 open_mode: True
 {%- endif %}
@@ -58,6 +60,14 @@
   - {{ master.pillar.get('salt', {}).get('path', '/srv/salt/pillar') }}
 {%- endif %}
 
+{%- if master.pillar.engine == 'architect' %}
+ext_pillar:
+  - cmd_yaml: 'architect-salt-pillar %s'
+
+master_tops:
+  ext_nodes: architect-salt-top
+{%- endif %}
+
 {%- if master.pillar.engine == 'reclass' or (master.pillar.engine == 'composite' and master.pillar.reclass is defined) %}
 
 reclass: &reclass
@@ -177,6 +187,10 @@
   {%- endif %}
 {%- endif %}
 
+file_recv: {{ master.get('file_recv', False) }}
+
+id: {{ system.name }}.{{ system.domain }}
+
 {#-
 vim: syntax=jinja
 -#}
diff --git a/salt/files/orchestrate.sls b/salt/files/orchestrate.sls
index dde20cb..378d570 100644
--- a/salt/files/orchestrate.sls
+++ b/salt/files/orchestrate.sls
@@ -30,7 +30,7 @@
 
         {%- if salt['file.directory_exists']('/srv/salt/env/'+environment_name+'/'+formula.0+'/orchestrate') and formula|length > 1 and salt['file.file_exists']('/srv/salt/env/'+environment_name+'/'+formula.0+'/orchestrate/'+formula.1+'.sls') %}
 
-{{ salt['cmd.run']('cat /srv/salt/env/'+environment_name+'/'+formula.0+'/orchestrate/'+formula.1+'.sls') }}
+{{ salt['cmd.shell']('cat /srv/salt/env/'+environment_name+'/'+formula.0+'/orchestrate/'+formula.1+'.sls') }}
 
         {%- else %}
           {%- if args[ state.0 ] is defined %}
diff --git a/salt/map.jinja b/salt/map.jinja
index 213e95f..68a7cf2 100644
--- a/salt/map.jinja
+++ b/salt/map.jinja
@@ -19,6 +19,7 @@
   base_environment: dev
   dir:
     files: /srv/salt/env
+  pillar_safe_render_error: true
   pillar:
     engine: salt
   max_event_size: 100000000
diff --git a/salt/master/pillar.sls b/salt/master/pillar.sls
index 806511d..709cc8c 100644
--- a/salt/master/pillar.sls
+++ b/salt/master/pillar.sls
@@ -31,6 +31,25 @@
 
 {%- endif %}
 
+{%- elif master.pillar.engine == 'architect' %}
+
+salt_pillar_architect_package:
+  pip.installed:
+    - name: architect-client
+
+salt_pillar_architect_package_config_dir:
+  file.directory:
+  - name: /etc/architect
+
+salt_pillar_architect_package_config_file:
+  file.managed:
+  - name: /etc/architect/client.yml
+  - source: salt://salt/files/architect.yml
+  - user: root
+  - template: jinja
+  - require:
+    - file: salt_pillar_architect_package_config_dir
+
 {%- elif master.pillar.engine == 'reclass' %}
 
 include:
diff --git a/salt/meta/sphinx.yml b/salt/meta/sphinx.yml
index 41df43c..2ae6452 100644
--- a/salt/meta/sphinx.yml
+++ b/salt/meta/sphinx.yml
@@ -8,7 +8,7 @@
       name: minion
       param:
         version:
-          value: {{ salt['cmd.run']('salt-minion --version')|replace('salt-minion ', '') }}
+          value: {{ salt['cmd.shell']('salt-minion --version')|replace('salt-minion ', '') }}
   {%- endif %}
   {%- if pillar.salt.master is defined %}
   {%- from "salt/map.jinja" import master with context %}
@@ -16,5 +16,5 @@
       name: master
       param:
         version:
-          value: {{ salt['cmd.run']('salt --version')|replace('salt ', '') }}
+          value: {{ salt['cmd.shell']('salt --version')|replace('salt ', '') }}
   {%- endif %}
diff --git a/salt/minion/cert.sls b/salt/minion/cert.sls
index 09ef9d1..720a695 100644
--- a/salt/minion/cert.sls
+++ b/salt/minion/cert.sls
@@ -69,6 +69,7 @@
     {% if cert.country is defined %}- C: {{ cert.country }}{%- endif %}
     {% if cert.locality is defined %}- L: {{ cert.locality }}{%- endif %}
     {% if cert.organization is defined %}- O: {{ cert.organization }}{%- endif %}
+    {% if cert.organization_name is defined %}- organizationName: {{ cert.organization_name }}{%- endif %}
     {% if cert.signing_private_key is defined and cert.signing_cert is defined %}
     - signing_private_key: "{{ cert.signing_private_key }}"
     - signing_cert: "{{ cert.signing_cert }}"
diff --git a/salt/minion/service.sls b/salt/minion/service.sls
index 36d8aff..2d03c62 100644
--- a/salt/minion/service.sls
+++ b/salt/minion/service.sls
@@ -59,6 +59,37 @@
       - cmd: salt_minion_service_restart
       {%- endfor %}
     {%- endif %}
+
+    {%- if support_yaml %}
+    {%- set dependency = support_yaml.get('dependency') %}
+    {%- if dependency %}
+      {%- if dependency.get('engine', 'pkg') == 'pkg' %}
+
+salt_minion_{{ service_name }}_dependencies:
+  pkg.installed:
+    - names: {{ dependency.get('pkgs') }}
+    - onchanges_in:
+      - cmd: salt_minion_service_restart
+      {%- elif dependency.get('engine', 'pkg') == 'pip' %}
+        {%- if dependency.get('pkgs') %}
+salt_minion_{{ service_name }}_dependencies:
+  pkg.installed:
+    - names: {{ dependency.get('pkgs') }}
+    - onchanges_in:
+      - cmd: salt_minion_service_restart
+    - require_in:
+      - pip: salt_minion_{{ service_name }}_dependencies_pip
+        {%- endif %}
+
+salt_minion_{{ service_name }}_dependencies_pip:
+  pip.installed:
+    - names: {{ dependency.get('python_pkgs') }}
+    - onchanges_in:
+      - cmd: salt_minion_service_restart
+
+      {%- endif %}
+    {%- endif %}
+    {%- endif %}
 {%- endfor %}
 
 salt_minion_service:
diff --git a/tests/pillar/control_virt_custom.sls b/tests/pillar/control_virt_custom.sls
new file mode 100644
index 0000000..7397494
--- /dev/null
+++ b/tests/pillar/control_virt_custom.sls
@@ -0,0 +1,55 @@
+virt:
+  disk:
+    three_disks:
+      - system:
+          size: 4096
+          image: ubuntu.qcow
+      - repository_snapshot:
+          size: 8192
+          image: snapshot.qcow
+      - cinder-volume:
+          size: 2048
+salt:
+  minion:
+    enabled: true
+    master:
+      host: config01.dc01.domain.com
+  control:
+    enabled: true
+    virt_enabled: true
+    size:
+      small:
+        cpu: 1
+        ram: 1
+      medium:
+        cpu: 2
+        ram: 4
+      large:
+        cpu: 4
+        ram: 8
+      medium_three_disks:
+        cpu: 2
+        ram: 4
+        disk_profile: three_disks
+    cluster:
+      vpc20_infra:
+        domain: neco.virt.domain.com
+        engine: virt
+        config:
+          engine: salt
+          host: master.domain.com
+        node:
+          ubuntu1:
+            provider: node01.domain.com
+            image: ubuntu.qcow
+            size: medium
+            img_dest: /var/lib/libvirt/ssdimages
+          ubuntu2:
+            provider: node02.domain.com
+            image: bubuntu.qcomw
+            size: small
+            img_dest: /var/lib/libvirt/hddimages
+          ubuntu3:
+            provider: node03.domain.com
+            image: meowbuntu.qcom2
+            size: medium_three_disks
diff --git a/tests/pillar/master_single_extpillars.sls b/tests/pillar/master_single_extpillars.sls
index 03521eb..d2d2422 100644
--- a/tests/pillar/master_single_extpillars.sls
+++ b/tests/pillar/master_single_extpillars.sls
@@ -17,6 +17,7 @@
     command_timeout: 5
     worker_threads: 2
     base_environment: prd
+    pillar_safe_render_error: False
     #environment:
     # prd:
     #   formula: