Merge "Extend system.file with file.decode module"
diff --git a/.kitchen.yml b/.kitchen.yml
index 9901242..eeaf317 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -33,16 +33,10 @@
   sudo: true
 
 docker_images:
-  - &xenial-20163 <%=ENV['IMAGE_XENIAL_20163'] || 'docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19'%>
   - &xenial-20177 <%=ENV['IMAGE_XENIAL_20177'] || 'docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2017.7/salt:2018_11_19'%>
   - &xenial-stable <%=ENV['IMAGE_XENIAL_STABLE'] || 'docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19'%>
 
 platforms:
-  - name: xenial-2016.3
-    driver_config:
-      image: *xenial-20163
-      platform: ubuntu
-
   - name: xenial-2017.7
     driver_config:
       image: *xenial-20177
diff --git a/_states/aptkey.py b/_states/aptkey.py
new file mode 100644
index 0000000..8d12829
--- /dev/null
+++ b/_states/aptkey.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# _state/aptkey.py
+"""
+Manage apt keys
+"""
+from __future__ import absolute_import, print_function, unicode_literals
+
+from base64 import b64encode, b64decode
+from binascii import Error as binasciiError
+
+def __virtual__():
+    """Only load if 'pkg' (aptpkg) module has necessary functions"""
+
+    if not 'pkg.add_repo_key' in __salt__ or not 'pkg.get_repo_keys' in __salt__:
+        return False, "'pkg.add_repo_key' and 'pkg.get_repo_keys' functions are required"
+    return True
+
+def _get_fingerprints(input_text):
+    """Get fingerprint(s) for given text
+
+    :param str input_text: text to get fingerprint(s) from it
+
+    :return:
+        A list of found fingerprint(s)
+    :rtype list:
+
+    """
+    fingerprints = []
+    cmd = 'apt-key adv --with-fingerprint --with-colons --dry-run -'
+    out = __salt__['cmd.run_stdout'](cmd, stdin=input_text, python_shell=False)
+    for line in out.split('\n'):
+        line = line.split(':')
+        if line[0] == 'fpr':
+            fingerprints.append(line[-2])
+    return fingerprints
+
+
+def added(name, key_text=None, key_url=None):
+    """Ensure that given key added to APT's key storage
+
+    :param str name:
+        Just an ID, it does not used at all.
+    :param str key_text:
+        Key to add as a plain text or base64-encoded.
+    :param str key_url:
+        URL from which key should be fetched. Supported URLs: salt://, http://,
+        https:// and file://.
+
+    Examples:
+
+    .. code-block:: yaml
+
+        # Base64 encoded key
+        {% set repo_key = salt['hashutil.base64_b64encode'](repo.key) %}
+        linux_repo_ubuntu_key:
+          aptkey.added:
+            - key_text: {{ repo_key }}
+
+    .. code-block:: yaml
+
+        # Plaintext key
+        linux_repo_ubuntu_key:
+          aptkey.added:
+            - key_text: '{{ repo.key | replace("\n", "\\n") }}'
+
+    .. code-block:: yaml
+
+        linux_repo_ubuntu_key:
+          aptkey.added:
+            - key_url: 'https://example.com/key.asc'
+    """
+    ret = {'name': name,
+           'result': None if __opts__['test'] else True,
+           'changes': {},
+           'comment': ''}
+
+    if not key_text and not key_url:
+        ret['result'] = False,
+        ret['comment'] = 'No key to add provided'
+        return ret
+    if key_text and key_url:
+        ret['result'] = False
+        ret['comment'] = 'Only one of key_text or key_url is permitted'
+        return ret
+
+    # If key_url provided fetch it before proceeding
+    if key_url:
+        # This only supports salt://, http://, https:// and file:// URLs
+        key_text = __salt__['cp.get_url'](key_url, dest=None)
+
+    # Try to apply base64 decoding to key_text, just in case...
+    try:
+        # Decode key_text if it is base64 encoded string
+        decoded_key_text = b64decode(key_text)
+        # the simplest available check that given string was base64 encoded
+        if b64encode(decoded_key_text) == key_text:
+            key_text = decoded_key_text
+    except TypeError, binasciiError: # the first is for py2, the second for py3
+        pass
+
+    # Get apt's keys and their fingerprints
+    apt_keys = __salt__['pkg.get_repo_keys']()
+    apt_fingerprints = [key.get('fingerprint') for _, key in apt_keys.items()]
+
+    key_fingerprints = _get_fingerprints(key_text)
+    # If any of given fingerprints does not present in apt keys
+    # then add all of them
+    if not set(key_fingerprints).issubset(set(apt_fingerprints)):
+        if __opts__['test']:
+            ret['result'] = None
+            ret['changes'] = {'key': key_text}
+            return ret
+
+        success = __salt__['pkg.add_repo_key'](text=key_text)
+        if success:
+            ret['result'] = True
+            ret['changes'] = {'key': key_text}
+        else:
+            ret['result'] = False
+            ret['comment'] = 'Key was not added because of errors'
+    return ret
diff --git a/linux/system/repo.sls b/linux/system/repo.sls
index 73bb33d..3322f07 100644
--- a/linux/system/repo.sls
+++ b/linux/system/repo.sls
@@ -80,11 +80,11 @@
     - name: /etc/apt/preferences.d/{{ name }}
       {%- endif %}
 
-      {%- if repo.get('key') %}
+      {%- if repo.get('key') and grains['saltversioninfo'] < [2018, 3] %}
 linux_repo_{{ name }}_key:
         {% set repo_key = salt['hashutil.base64_b64encode'](repo.key) %}
-  cmd.run:
-    - name: "echo '{{ repo_key }}' | base64 -d | apt-key add -"
+    aptkey.added:
+    - key_text: {{ repo_key }}
     - require_in:
         {%- if repo.get('default', False) %}
       - file: default_repo_list
@@ -105,8 +105,8 @@
 #}
       {%- elif repo.key_url|default(False) and grains['saltversioninfo'] < [2017, 7] and not repo.key_url.startswith('salt://') %}
 linux_repo_{{ name }}_key:
-  cmd.run:
-    - name: "curl -sL {{ repo.key_url }} | apt-key add -"
+    aptkey.added:
+    - key_url: {{ repo.key_url }}
     - require_in:
         {%- if repo.get('default', False) %}
       - file: default_repo_list
@@ -144,6 +144,9 @@
             {%- if repo.key_url is defined and (grains['saltversioninfo'] >= [2017, 7] or repo.key_url.startswith('salt://')) %}
   - key_url: {{ repo.key_url }}
             {%- endif %}
+            {%- if repo.key is defined and grains['saltversioninfo'] >= [2018, 3] %}
+  - key_text: '{{ repo.key | replace("\n", "\\n") }}'
+            {%- endif %}
   - consolidate: {{ repo.get('consolidate', False) }}
   - require:
     - file: /etc/apt/apt.conf.d/99proxies-salt-{{ name }}