Merge "Configure /etc/default/useradd through 'defaults'"
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/README.rst b/README.rst
index 9c66374..2993b55 100644
--- a/README.rst
+++ b/README.rst
@@ -561,6 +561,19 @@
               foo: 1
               bar: 'bar'
 
+Ensure presence of file to be decoded through file.decode module (see:
+https://docs.saltstack.com/en/latest/ref/states/all/salt.states.file.html#salt.states.file.decode):
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        file:
+          /tmp/test4.txt:
+            decode: True
+            encoded_data: |
+              dGVzdDQK
+
 Kernel
 ~~~~~~
 
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/auth.sls b/linux/system/auth.sls
index 690ec04..5cb3798 100644
--- a/linux/system/auth.sls
+++ b/linux/system/auth.sls
@@ -170,6 +170,7 @@
     - pkg: linux_auth_ldap_packages
   - watch_in:
     - service: linux_auth_nslcd_service
+    - service: linux_auth_nscd_service
 
 linux_auth_ldap_packages:
   pkg.installed:
@@ -185,12 +186,18 @@
     - pkg: linux_auth_ldap_packages
   - watch_in:
     - service: linux_auth_nslcd_service
+    - service: linux_auth_nscd_service
 
 linux_auth_nslcd_service:
   service.running:
   - enable: true
   - name: nslcd
 
+linux_auth_nscd_service:
+  service.running:
+  - enable: true
+  - name: nscd
+
     {%- endif %}
   {%- endif %}
 {%- endif %}
diff --git a/linux/system/file.sls b/linux/system/file.sls
index e8a6d52..1ae9906 100644
--- a/linux/system/file.sls
+++ b/linux/system/file.sls
@@ -3,16 +3,44 @@
 
 {%- for file_name, file in system.file.items() %}
 
+  {%- if file.decode is defined %}
+    {%- if file.name is defined %}
+      {%- set dirname = salt['file.dirname'](file.name) %}
+    {%- else %}
+      {%- set dirname = salt['file.dirname'](file_name) %}
+    {%- endif %}
+linux_directory_{{ dirname }}:
+  file.directory:
+    - name: {{ dirname }}
+    - makedirs: {{ file.get('makedirs', 'True') }}
+    - user: {{ file.get('user', 'root') }}
+    - group: {{ file.get('group', 'root') }}
+
 linux_file_{{ file_name }}:
-{%- if file.serialize is defined %}
+  file.decode:
+    {%- if file.name is defined %}
+    - name: {{ file.name }}
+    {%- else %}
+    - name: {{ file_name }}
+    {%- endif %}
+    - encoding_type: base64
+    {%- if file.contents_pillar is defined %}
+    - contents_pillar: {{ file.contents_pillar }}
+    {%- elif file.encoded_data is defined %}
+    - encoded_data: |
+        {{ file.encoded_data | indent(8) }}
+    {%- endif %}
+  {%- else %}
+linux_file_{{ file_name }}:
+    {%- if file.serialize is defined %}
   file.serialize:
     - formatter: {{ file.serialize }}
-  {%- if file.contents is defined  %}
+      {%- if file.contents is defined  %}
     - dataset: {{ file.contents|yaml }}
-  {%- elif file.contents_pillar is defined %}
+      {%- elif file.contents_pillar is defined %}
     - dataset_pillar: {{ file.contents_pillar }}
-  {%- endif %}
-{%- else %}
+      {%- endif %}
+    {%- else %}
   file.managed:
     {%- if file.source is defined %}
     - source: {{ file.source }}
@@ -31,8 +59,7 @@
     {%- elif file.contents_grains is defined %}
     - contents_grains: {{ file.contents_grains }}
     {%- endif %}
-
-{%- endif %}
+  {%- endif %}
     {%- if file.name is defined %}
     - name: {{ file.name }}
     {%- else %}
@@ -50,6 +77,7 @@
     {%- if file.encoding is defined %}
     - encoding: {{ file.encoding }}
     {%- endif %}
+{%- endif %}
 
 {%- endfor %}
 
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 }}
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index 73448aa..d5a953e 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -34,6 +34,10 @@
         name: /tmp/test3.txt
         source: salt://linux/files/test/file_template.jinja
         template: jinja
+      test4:
+        decode: True
+        name: /tmp/test4.txt
+        encoded_data: dGVzdDQK
     apt:
       preferences:
         enabled: true