Merge "Linux OVS-dpdk and multiqueue support"
diff --git a/.kitchen.yml b/.kitchen.yml
new file mode 100644
index 0000000..2c11074
--- /dev/null
+++ b/.kitchen.yml
@@ -0,0 +1,66 @@
+---
+driver:
+  name: docker
+  hostname: linux.ci.local
+  use_sudo: sudo
+
+provisioner:
+  name: salt_solo
+  salt_install: bootstrap
+  salt_bootstrap_url: https://bootstrap.saltstack.com
+  salt_version: latest
+  require_chef: false
+  log_level: error
+  formula: linux
+  grains:
+    noservices: true
+  state_top:
+    base:
+      "*":
+        - linux
+  pillars:
+    top.sls:
+      base:
+        "*":
+          - linux
+
+verifier:
+  name: inspec
+  sudo: true
+
+platforms:
+  - name: <%=ENV['PLATFORM'] || 'ubuntu-xenial'%>
+    driver_config:
+      image: <%=ENV['PLATFORM'] || 'trevorj/salty-whales:xenial'%>
+      platform: ubuntu
+
+
+suites:
+
+  - name: network
+    provisioner:
+      pillars-from-files:
+        linux.sls: tests/pillar/network.sls
+
+  #- name: storage
+    #provisioner:
+      #pillars-from-files:
+        #linux.sls: tests/pillar/storage.sls
+      #init_environment: |
+        #sudo mkdir -p /tmp/node
+        #sudo dd if=/dev/zero of=/tmp/loop_dev0 bs=1024 count=$((30*1024));
+        #sudo dd if=/dev/zero of=/tmp/loop_dev1 bs=1024 count=$((30*1024));
+        #sudo dd if=/dev/zero of=/tmp/loop_dev2 bs=1024 count=$((30*1024));
+        #sudo dd if=/dev/zero of=/tmp/loop_dev3 bs=1024 count=$((30*1024));
+        #sudo dd if=/dev/zero of=/tmp/loop_dev4 bs=1024 count=$((30*1024));
+        #sudo mkfs.ext4 /tmp/loop_dev1
+        #sudo mkswap /tmp/loop_dev2
+        #sudo chown root /tmp/loop_dev*;
+        #sudo chmod 0600 /tmp/loop_dev*;
+
+  - name: system
+    provisioner:
+      pillars-from-files:
+        linux.sls: tests/pillar/system.sls
+
+# vim: ft=yaml sw=2 ts=2 sts=2 tw=125
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2a33688
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,30 @@
+sudo: required
+services:
+  - docker
+
+install:
+  - pip install PyYAML
+  - pip install virtualenv
+  - |
+    test -e Gemfile || cat <<EOF > Gemfile
+    source 'https://rubygems.org'
+    gem 'rake'
+    gem 'test-kitchen'
+    gem 'kitchen-docker'
+    gem 'kitchen-inspec'
+    gem 'inspec'
+    gem 'kitchen-salt', :git => 'https://github.com/epcim/kitchen-salt.git', :branch => 'dependencis-pkg-repo2'
+    #Waiting for PR#78
+    #gem 'kitchen-salt', '>=0.2.25'
+  - bundle install
+
+env:
+  - PLATFORM=trevorj/salty-whales:trusty
+  - PLATFORM=trevorj/salty-whales:xenial
+
+before_script:
+  - make test | tail
+
+script:
+  - test ! -e .kitchen.yml || bundle exec kitchen converge || true
+  - test ! -e .kitchen.yml || bundle exec kitchen verify -t tests/integration
diff --git a/README.rst b/README.rst
index 39893c1..c1b8cc7 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,3 @@
-
 =====
 Linux
 =====
@@ -31,7 +30,7 @@
         timezone: 'Europe/Prague'
         utc: true
 
-Linux with system users, sowe with password set
+Linux with system users, some with password set
 
 .. code-block:: yaml
 
@@ -54,6 +53,115 @@
             home: '/home/jsmith'
             password: userpassword
 
+Configure sudo for users and groups under ``/etc/sudoers.d/``.
+This ways ``linux.system.sudo`` pillar map to actual sudo attributes:
+
+.. code-block:: jinja
+   # simplified template:
+   Cmds_Alias {{ alias }}={{ commands }}
+   {{ user }}   {{ hosts }}=({{ runas }}) NOPASSWD: {{ commands }}
+   %{{ group }} {{ hosts }}=({{ runas }}) NOPASSWD: {{ commands }}
+
+   # when rendered:
+   saltuser1 ALL=(ALL) NOPASSWD: ALL
+
+
+.. code-block:: yaml
+  linux:
+    system:
+      sudo:
+        enabled: true
+        alias:
+          host:
+            LOCAL:
+            - localhost
+            PRODUCTION:
+            - db1
+            - db2
+          runas:
+            DBA:
+            - postgres
+            - mysql
+            SALT:
+            - root
+          command:
+            # Note: This is not 100% safe when ALL keyword is used, user still may modify configs and hide his actions.
+            #       Best practice is to specify full list of commands user is allowed to run.
+            SUPPORT_RESTRICTED:
+            - /bin/vi /etc/sudoers*
+            - /bin/vim /etc/sudoers*
+            - /bin/nano /etc/sudoers*
+            - /bin/emacs /etc/sudoers*
+            - /bin/su - root
+            - /bin/su -
+            - /bin/su
+            - /usr/sbin/visudo
+            SUPPORT_SHELLS:
+            - /bin/sh
+            - /bin/ksh
+            - /bin/bash
+            - /bin/rbash
+            - /bin/dash
+            - /bin/zsh
+            - /bin/csh
+            - /bin/fish
+            - /bin/tcsh
+            - /usr/bin/login
+            - /usr/bin/su
+            - /usr/su
+            ALL_SALT_SAFE:
+            - /usr/bin/salt state*
+            - /usr/bin/salt service*
+            - /usr/bin/salt pillar*
+            - /usr/bin/salt grains*
+            - /usr/bin/salt saltutil*
+            - /usr/bin/salt-call state*
+            - /usr/bin/salt-call service*
+            - /usr/bin/salt-call pillar*
+            - /usr/bin/salt-call grains*
+            - /usr/bin/salt-call saltutil*
+            SALT_TRUSTED:
+            - /usr/bin/salt*
+        users:
+          # saltuser1 with default values: saltuser1 ALL=(ALL) NOPASSWD: ALL
+          saltuser1: {}
+          saltuser2:
+            hosts:
+            - LOCAL
+          # User Alias DBA
+          DBA:
+            hosts:
+            - ALL
+            commands:
+            - ALL_SALT_SAFE
+        groups:
+          db-ops:
+            hosts:
+            - ALL
+            - '!PRODUCTION'
+            runas:
+            - DBA
+            commands:
+            - /bin/cat *
+            - /bin/less *
+            - /bin/ls *
+          salt-ops:
+            hosts:
+            - 'ALL'
+            runas:
+            - SALT
+            commands:
+            - SUPPORT_SHELLS
+          salt-ops-2nd:
+            name: salt-ops
+            nopasswd: false
+            runas:
+            - DBA
+            commands:
+            - ALL
+            - '!SUPPORT_SHELLS'
+            - '!SUPPORT_RESTRICTED'
+
 Linux with package, latest version
 
 .. code-block:: yaml
diff --git a/linux/files/sudoer b/linux/files/sudoer
index 3b682af..549d5f9 100644
--- a/linux/files/sudoer
+++ b/linux/files/sudoer
@@ -1,5 +1,6 @@
-# managed by salt
-
+# sudoer file managed by salt-minion
+# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+#
 # user {{ user_name }} is system administrator.
 # It has passwordless sudo functionality.
 {{ user_name }} ALL=(ALL) NOPASSWD:ALL
diff --git a/linux/files/sudoer-aliases b/linux/files/sudoer-aliases
new file mode 100644
index 0000000..9e44886
--- /dev/null
+++ b/linux/files/sudoer-aliases
@@ -0,0 +1,19 @@
+# sudoer aliases, file managed by salt-minion
+# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+
+{%- for alias,commands in aliases.get('command',{}).iteritems() %}
+Cmnd_Alias {{ alias }}={{ commands|join(', ') }}
+{%- endfor %}
+
+{%- for alias,users in aliases.get('user',{}).iteritems() %}
+User_Alias {{ alias }}={{ users|join(', ') }}
+{%- endfor %}
+
+{%- for alias,users in aliases.get('runas',{}).iteritems() %}
+Runas_Alias {{ alias }}={{ users|join(', ') }}
+{%- endfor %}
+
+{%- for alias,hosts in aliases.get('host',{}).iteritems() %}
+Host_Alias {{ alias }}={{ hosts|join(', ') }}
+{%- endfor %}
+
diff --git a/linux/files/sudoer-groups b/linux/files/sudoer-groups
new file mode 100644
index 0000000..b0c1fdf
--- /dev/null
+++ b/linux/files/sudoer-groups
@@ -0,0 +1,7 @@
+# sudoer groups, file managed by salt-minion
+# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+
+{%- for group,spec in groups.iteritems() %}
+%{{ spec.name|default(group) }} {{ spec.get('hosts', ['ALL'])|join(',') }}=({{ spec.get('runas', ['ALL'])|join(', ') }}) {% if spec.get('nopasswd', True) %}NOPASSWD: {% endif %}{{ spec.get('commands', ['ALL'])|join(', ') }}
+{%- endfor %}
+
diff --git a/linux/files/sudoer-users b/linux/files/sudoer-users
new file mode 100644
index 0000000..4e05269
--- /dev/null
+++ b/linux/files/sudoer-users
@@ -0,0 +1,7 @@
+# sudoer users, file managed by salt-minion
+# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+
+{%- for user,spec in users.iteritems() %}
+{{ spec.name|default(user) }} {{ spec.get('hosts', ['ALL'])|join(',') }}=({{ spec.get('runas', ['ALL'])|join(', ') }}) {% if spec.get('nopasswd', True) %}NOPASSWD: {% endif %}{{ spec.get('commands', ['ALL'])|join(', ') }}
+{%- endfor %}
+
diff --git a/linux/network/interface.sls b/linux/network/interface.sls
index 0b1f0ee..a72f472 100644
--- a/linux/network/interface.sls
+++ b/linux/network/interface.sls
@@ -202,7 +202,7 @@
   {%- endif %}
   - nozeroconf: True
   - nisdomain: {{ system.domain }}
-  - require_reboot: False
+  - require_reboot: True
 
 {%- endif %}
 
diff --git a/linux/system/group.sls b/linux/system/group.sls
index 4963829..2c1c769 100644
--- a/linux/system/group.sls
+++ b/linux/system/group.sls
@@ -26,3 +26,4 @@
 {%- endfor %}
 
 {%- endif %}
+
diff --git a/linux/system/init.sls b/linux/system/init.sls
index 1ce8100..9d4d4f0 100644
--- a/linux/system/init.sls
+++ b/linux/system/init.sls
@@ -75,3 +75,6 @@
 {%- if system.config is defined %}
 - linux.system.config
 {%- endif %}
+{%- if system.sudo is defined %}
+- linux.system.sudo
+{%- endif %}
diff --git a/linux/system/repo.sls b/linux/system/repo.sls
index 04e7070..555abd1 100644
--- a/linux/system/repo.sls
+++ b/linux/system/repo.sls
@@ -58,6 +58,8 @@
   {%- if repo.key_url is defined %}
   - key_url: {{ repo.key_url }}
   {%- endif %}
+  - require:
+    - pkg: linux_packages
 
 {%- endif %}
 
@@ -80,6 +82,8 @@
   {%- if repo.gpgkey is defined %}
   - gpgkey: {{ repo.gpgkey }}
   {%- endif %}
+  - require:
+    - pkg: linux_packages
 
 {%- endif %}
 
@@ -99,6 +103,8 @@
     - mode: 0644
     - defaults:
         default_repos: {{ default_repos }}
+    - require:
+      - pkg: linux_packages
 
 {%- endif %}
 
diff --git a/linux/system/sudo.sls b/linux/system/sudo.sls
new file mode 100644
index 0000000..5cee4b3
--- /dev/null
+++ b/linux/system/sudo.sls
@@ -0,0 +1,73 @@
+{%- from "linux/map.jinja" import system with context %}
+{%- if system.enabled %}
+
+{%- if system.get('sudo', {}).get('enabled', False) %}
+
+{%- if system.get('sudo', {}).get('aliases', False) is mapping %}
+/etc/sudoers.d/90-salt-sudo-aliases:
+  file.managed:
+  - source: salt://linux/files/sudoer-aliases
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 440
+  - defaults:
+      aliases: {{ system.sudo.aliases|yaml }}
+  - check_cmd: /usr/sbin/visudo -c -f
+{%- else %}
+/etc/sudoers.d/90-salt-sudo-aliases:
+  file.absent:
+  - name: /etc/sudoers.d/90-salt-sudo-aliases
+{%- endif %}
+
+
+{%- if system.get('sudo', {}).get('users', False) is mapping %}
+/etc/sudoers.d/91-salt-sudo-users:
+  file.managed:
+  - source: salt://linux/files/sudoer-users
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 440
+  - defaults:
+      users: {{ system.sudo.users|yaml }}
+  - check_cmd: /usr/sbin/visudo -c -f
+{%- else %}
+/etc/sudoers.d/91-salt-sudo-users:
+  file.absent:
+  - name: /etc/sudoers.d/91-salt-sudo-users
+{%- endif %}
+
+{%- if system.get('sudo', {}).get('groups', False) is mapping %}
+/etc/sudoers.d/91-salt-sudo-groups:
+  file.managed:
+  - source: salt://linux/files/sudoer-groups
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 440
+  - defaults:
+      groups: {{ system.sudo.groups|yaml }}
+  - check_cmd: /usr/sbin/visudo -c -f
+{%- else %}
+/etc/sudoers.d/91-salt-sudo-groups:
+  file.absent:
+  - name: /etc/sudoers.d/91-salt-sudo-groups
+{%- endif %}
+
+{%- else %}
+
+/etc/sudoers.d/90-salt-sudo-aliases:
+  file.absent:
+  - name: /etc/sudoers.d/90-salt-sudo-aliases
+
+/etc/sudoers.d/91-salt-sudo-users:
+  file.absent:
+  - name: /etc/sudoers.d/91-salt-sudo-users
+
+/etc/sudoers.d/91-salt-sudo-groups:
+  file.absent:
+  - name: /etc/sudoers.d/91-salt-sudo-groups
+
+{%- endif %}
+{%- endif %}
diff --git a/linux/system/user.sls b/linux/system/user.sls
index 4232e65..997f6fa 100644
--- a/linux/system/user.sls
+++ b/linux/system/user.sls
@@ -48,6 +48,7 @@
     user_name: {{ name }}
   - require:
     - user: system_user_{{ name }}
+  - check_cmd: /usr/sbin/visudo -c -f
 
 {%- endif %}
 
diff --git a/tests/integration/system/sudoer_spec.rb b/tests/integration/system/sudoer_spec.rb
new file mode 100644
index 0000000..21163cf
--- /dev/null
+++ b/tests/integration/system/sudoer_spec.rb
@@ -0,0 +1,8 @@
+describe command('grep "" /etc/sudoers.d/*') do
+    its('stdout') { should_not match /sudogroup0/  }
+    its('stdout') { should match /salt-ops ALL=\(DBA\) NOPASSWD/  }
+    its('stdout') { should match /sudogroup2.*localhost=/  }
+    its('stdout') { should match /db-ops.*less/  }
+    its('stdout') { should_not match /sudogroup0/  }
+    its('stdout') { should_not match /sudogroup1 .* !SUDO_RESTRICTED_SU/  }
+end
diff --git a/tests/pillar/network.sls b/tests/pillar/network.sls
index a8dfee6..bf8b176 100644
--- a/tests/pillar/network.sls
+++ b/tests/pillar/network.sls
@@ -1,25 +1,26 @@
 linux:
   system:
     enabled: true
-    domain: local
+    domain: ci.local
+    name: linux
   network:
     enabled: true
-    hostname: test01
-    fqdn: test01.local
+    hostname: linux
+    fqdn: linux.ci.local
     network_manager: false
-    interface:
-      eth0:
-        enabled: true
-        type: eth
-        address: 192.168.0.102
-        netmask: 255.255.255.0
-        gateway: 192.168.0.1
-        name_servers:
-        - 8.8.8.8
-        - 8.8.4.4
-        mtu: 1500
-      vlan69:
-        enabled: true
-        type: vlan
-        use_interfaces:
-        - interface: ${linux:interface:eth0}
+    #interface:
+      #eth0:
+        #enabled: true
+        #type: eth
+        #address: 192.168.0.102
+        #netmask: 255.255.255.0
+        #gateway: 192.168.0.1
+        #name_servers:
+        #- 8.8.8.8
+        #- 8.8.4.4
+        #mtu: 1500
+      #vlan69:
+        #enabled: true
+        #type: vlan
+        #use_interfaces:
+        #- interface: ${linux:interface:eth0}
diff --git a/tests/pillar/storage.sls b/tests/pillar/storage.sls
index af9e2fd..af63dbe 100644
--- a/tests/pillar/storage.sls
+++ b/tests/pillar/storage.sls
@@ -5,23 +5,42 @@
       file:
         enabled: true
         engine: file
-        device: /swapfile
-        size: 512
+        device: /tmp/loop_dev2
+        size: 5
+    mount:
+      # NOTE: simple dummy loop devices, use for test purposes only
+      dev0:
+        enabled: false
+        device: /tmp/loop_dev0
+        path: /tmp/node/dev0
+        file_system: xfs
+        opts: noatime,nobarrier,logbufs=8,nobootwait,nobarrier
+        user: root
+        group: root
+        mode: 755
+      dev1:
+        enabled: true
+        device: /tmp/loop_dev1
+        path: /mnt
+        file_system: ext4
+        #opts: noatime,nobarrier,logbufs=8,nobootwait,nobarrier
+        user: root
+        group: root
     lvm:
       vg0:
         name: vg0-dummy
         enabled: true
         devices:
-          - /dev/vdb
+          - /tmp/loop_dev3
         volume:
           lv01:
-            size: 512M
+            size: 5M
             mount:
-              path: /srv
+              path: /mnt
     disk1:
       enabled: true
-      device: /dev/dummy
-      path: /srv/dummy
+      device: /dev/loop_dev4
+      path: /tmp/dummy
       file_system: xfs
       options: "noatime,nobarrier,logbufs=8"
       user: nobody
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index 30968e2..d92dc8e 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -2,32 +2,18 @@
   system:
     enabled: true
     cluster: default
-    name: test01
-    timezone: Europe/Prague
+    name: linux
     domain: local
     environment: prd
+    hostname: system.pillar.local
     apparmor:
       enabled: false
     haveged:
       enabled: true
-    console:
-      tty0:
-        autologin: root
-      ttyS0:
-        autologin: root
-        rate: 115200
-        term: xterm
     prompt:
-      default: "test01.local$"
+      default: "linux.ci.local$"
     kernel:
-      sriov: True
       isolcpu: 1,2,3,4
-      hugepages:
-        large:
-          default: true
-          size: 1G
-          count: 210
-          mount_point: /mnt/hugepages_1GB
     motd:
       - warning: |
           #!/bin/sh
@@ -55,12 +41,43 @@
         uid: 9999
         full_name: Test User
         home: /home/test
+        groups:
+          - root
+      salt_user1:
+        enabled: true
+        name: saltuser1
+        sudo: false
+        uid: 9991
+        full_name: Salt User1
+        home: /home/saltuser1
+      salt_user2:
+        enabled: true
+        name: saltuser2
+        sudo: false
+        uid: 9992
+        full_name: Salt Sudo User2
+        home: /home/saltuser2
     group:
       test:
         enabled: true
         name: test
         gid: 9999
         system: true
+      db-ops:
+        enabled: true
+        name: testgroup
+      salt-ops:
+        enabled: true
+        name: sudogroup0
+      sudogroup1:
+        enabled: true
+        name: sudogroup1
+      sudogroup2:
+        enabled: true
+        name: sudogroup2
+      sudogroup3:
+        enabled: false
+        name: sudogroup3
     job:
       test:
         enabled: true
@@ -75,11 +92,6 @@
       opencontrail:
         source: "deb http://ppa.launchpad.net/tcpcloud/contrail-2.20/ubuntu trusty main"
         architectures: amd64
-    policyrcd:
-      - package: cassandra
-        action: exit 101
-      - package: '*'
-        action: switch
     locale:
       en_US.UTF-8:
         enabled: true
@@ -88,3 +100,103 @@
         enabled: true
     autoupdates:
       enabled: true
+    sudo:
+      enabled: true
+      alias:
+        runas:
+          DBA:
+          - postgres
+          - mysql
+          SALT:
+          - root
+        host:
+          LOCAL:
+          - localhost
+          PRODUCTION:
+          - db1
+          - db2
+        command:
+          SUDO_RESTRICTED_SU:
+          - /bin/vi /etc/sudoers
+          - /bin/su - root
+          - /bin/su -
+          - /bin/su
+          - /usr/sbin/visudo
+          SUDO_SHELLS:
+          - /bin/sh
+          - /bin/ksh
+          - /bin/bash
+          - /bin/rbash
+          - /bin/dash
+          - /bin/zsh
+          - /bin/csh
+          - /bin/fish
+          - /bin/tcsh
+          - /usr/bin/login
+          - /usr/bin/su
+          - /usr/su
+          SUDO_SALT_SAFE:
+          - /usr/bin/salt state*
+          - /usr/bin/salt service*
+          - /usr/bin/salt pillar*
+          - /usr/bin/salt grains*
+          - /usr/bin/salt saltutil*
+          - /usr/bin/salt-call state*
+          - /usr/bin/salt-call service*
+          - /usr/bin/salt-call pillar*
+          - /usr/bin/salt-call grains*
+          - /usr/bin/salt-call saltutil*
+          SUDO_SALT_TRUSTED:
+          - /usr/bin/salt*
+      users:
+        saltuser1: {}
+        saltuser2:
+          hosts:
+          - LOCAL
+        # User Alias:
+        DBA:
+          hosts:
+          - ALL
+          commands:
+          - SUDO_SALT_SAFE
+      groups:
+        db-ops:
+          hosts:
+          - ALL
+          - '!PRODUCTION'
+          runas:
+          - DBA
+          commands:
+          - /bin/cat *
+          - /bin/less *
+          - /bin/ls *
+          - SUDO_SALT_SAFE
+          - '!SUDO_SHELLS'
+          - '!SUDO_RESTRICTED_SU'
+        salt-ops:
+          hosts:
+          - 'ALL'
+          runas:
+          - SALT
+          commands:
+          - SUDO_SALT_TRUSTED
+        salt-ops2:
+          name: salt-ops
+          runas:
+          - DBA
+          commands:
+          - SUDO_SHELLS
+        sudogroup1:
+          commands:
+            - ALL
+        sudogroup2:
+          commands:
+            - ALL
+          hosts:
+            - localhost
+          users:
+            - test
+          nopasswd: false
+        sudogroup3:
+          commands:
+            - ALL
diff --git a/tests/pillar/system_extra.sls b/tests/pillar/system_extra.sls
new file mode 100644
index 0000000..801c628
--- /dev/null
+++ b/tests/pillar/system_extra.sls
@@ -0,0 +1,28 @@
+
+linux:
+  system:
+    enabled: true
+    cluster: default
+    name: linux
+    timezone: Europe/Prague
+    console:
+      tty0:
+        autologin: root
+      ttyS0:
+        autologin: root
+        rate: 115200
+        term: xterm
+    kernel:
+      sriov: True
+      isolcpu: 1,2,3,4
+      hugepages:
+        large:
+          default: true
+          size: 1G
+          count: 210
+          mount_point: /mnt/hugepages_1GB
+    policyrcd:
+      - package: cassandra
+        action: exit 101
+      - package: '*'
+        action: switch