Add sosreport tool

Sosreport is data/log collection tool for operating systems, which
will be used as single point of report create.

Change-Id: I747506de5280fb1e08b48161c9470d5b2cae9b0b
Related-Prod: PROD-34100
diff --git a/README.rst b/README.rst
index a994498..6ee52c4 100644
--- a/README.rst
+++ b/README.rst
@@ -1452,6 +1452,71 @@
             syslog: true
             syslog_error: true
 
+Linux Sosreport
+^^^^^^^^^^^^^^^
+
+Sosreport is an extensible, portable, support data collection tool
+primarily aimed at Linux distributions and other UNIX-like operating systems,
+which allows to create diagnostic snapshot of system.
+
+Works out of box and additional pillars are not needed by default:
+
+.. code-block:: bash
+
+    salt-call state.sls linux.system.sosreport.report
+
+or from Salt Master:
+
+.. code-block:: bash
+
+    salt -C '<target>' state.sls linux.system.sosreport.report
+
+Sosreport configuration may be extended with next pillar data:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        sosreport:
+          cmd_options:
+            tmp-dir: /root/reportdir
+            no_arg_opts: [ '-q' ]
+          config_options:
+            general:
+              all-logs: true
+            plugins:
+              disabled: [ docker ]
+            tunables:
+              apache.log: true
+
+Where is ``cmd_options`` additional provided arguments for cli cmd call,
+``general`` desribes parameters for sos.conf ``general`` section,
+``plugins`` desribes which plugins should be ``enabled`` or ``disabled``
+and ``tunables`` has custom plugin options which can be additionally set.
+
+Also it is possible to pass cmd_options through pillar override:
+
+.. code-block:: bash
+
+    salt -C '<target>' state.sls linux.system.sosreport.report pillar='{ "sosreport" : { "ticket-number": 12345, "tmp-dir": "/root/reportdir2" } }'
+
+Run ``sosreport --help`` to get full list of possible options.
+
+Once state ``linux.system.sosreport.report`` is executed on targets, it is
+possible to collect all reports by using next command on Salt Master:
+
+.. code-block:: bash
+
+    salt -C 'I@salt:master' state.sls linux.system.sosreport.collect pillar='{ "sosreport_collect" : { "target": "<target>", "archiveName": "sosreport_<env_name>_<customer>_<ticket>" } }'
+
+This will generate one common archive for all ``<target>`` nodes with name
+``sosreport_<env_name>_<customer>_<ticket>.tar.gz``. It is required to specify
+target nodes through model (``linux.system.sosreport.collect``) or pillar
+override. Also possible options are: ``nodeIp`` which allows you to use IP from another
+interface on node (should be available from minions), ``port`` for NetCat if
+you see that default port is busy, ``archiveName`` for your archive and
+``reportWorkDir`` directory to keeping all reports for current case.
+
 RHEL / CentOS
 ^^^^^^^^^^^^^
 Currently, ``update-motd`` is not available
diff --git a/linux/files/sos.conf b/linux/files/sos.conf
new file mode 100644
index 0000000..b74454e
--- /dev/null
+++ b/linux/files/sos.conf
@@ -0,0 +1,25 @@
+{%- from "linux/map.jinja" import system with context -%}
+# general parameters like debug, verbose and so on. batch is enabled for salt by default
+[general]
+batch = yes
+{%- if system.sosreport.config_options.general is defined %}
+  {%- for key, value in system.sosreport.config_options.general.items() %}
+{{ key }} = {{ value }}
+  {%- endfor %}
+{%- endif %}
+
+# enable/disable specified plugins
+[plugins]
+{%- if system.sosreport.config_options.plugins is defined %}
+  {%- for status, plugins in system.sosreport.config_options.plugins.items() %}
+{{ status }} = {{ plugins|join(',') }}
+  {%- endfor %}
+{%- endif %}
+
+# custom options for enabled plugins
+[tunables]
+{%- if system.sosreport.config_options.tunables is defined %}
+  {%- for key, value in system.sosreport.config_options.tunables.items() %}
+{{ key }} = {{ value }}
+  {%- endfor %}
+{%- endif -%}
\ No newline at end of file
diff --git a/linux/files/sosreport_collect.sh b/linux/files/sosreport_collect.sh
new file mode 100644
index 0000000..0c8fa13
--- /dev/null
+++ b/linux/files/sosreport_collect.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+nodeIp="{{ nodeIp }}"
+port="{{ port }}"
+target="{{ target }}"
+reportWorkDir="{{ reportWorkDir }}"
+archiveName="{{ archiveName }}"
+archiveNameFull="${archiveName}.tar.gz"
+errMsg=""
+
+if [ -z "${target}" ]; then
+  echo "Target is not set."
+  exit 1
+fi
+
+if [ -z "${nodeIp}" ]; then
+  echo "Node Ip is not set."
+  exit 1
+fi
+
+if [ "$(ss -l src ":${port}" | wc -l)" -ne "1" ]; then
+  echo "Port ${port} in use, please set another port."
+  exit 1
+fi
+
+nc_listen() {
+    local node=${1}
+    nc -q 1 -lp ${port} | tar -x || errMsg="${errMsg}Failed to collect report for ${node}\n"
+}
+
+cd ${reportWorkDir}
+for node in $(salt -C "${target}" --out txt test.ping | cut -f 1 -d ':'); do
+  nc_listen ${node} &
+  sleep 2 # wait until socket initialized
+  salt -C "${node}" cmd.run "bash /var/tmp/sosreport_send.sh ${nodeIp} ${port}"
+  sleep 2 # wait until socket released
+done
+
+if [ -z "${errMsg}" ]; then
+  tar -czf "${archiveName}.tar.gz" sosreport-*
+  echo "Report created: ${reportWorkDir}/${archiveNameFull}"
+else
+  echo -e "${errMsg}"
+  echo "Report can't be created."
+  exit 1
+fi
diff --git a/linux/map.jinja b/linux/map.jinja
index 28994cb..fa4e73b 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -26,6 +26,7 @@
              'logpath': '/var/log/atop',
              'outfile': '/var/log/atop/daily.log'
          },
+         'sosreport': {},
     },
     'Debian': {
         'pkgs': ['python-apt', 'apt-transport-https', 'libmnl0'],
@@ -55,6 +56,7 @@
              'logpath': '/var/log/atop',
              'outfile': '/var/log/atop/daily.log'
          },
+         'sosreport': {}
     },
     'RedHat': {
         'pkgs': ['policycoreutils', 'policycoreutils-python', 'telnet', 'wget'],
@@ -83,6 +85,7 @@
              'logpath': '/var/log/atop',
              'outfile': '/var/log/atop/daily.log'
          },
+         'sosreport': {}
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:system')) %}
 
diff --git a/linux/system/sosreport/collect.sls b/linux/system/sosreport/collect.sls
new file mode 100644
index 0000000..4a8b40f
--- /dev/null
+++ b/linux/system/sosreport/collect.sls
@@ -0,0 +1,53 @@
+{%- from "linux/map.jinja" import system with context %}
+
+{%- if system.sosreport.collect is defined or pillar['sosreport_collect'] is defined %}
+{%- set collectDefaultOpts = {
+      'nodeIp': grains.get('fqdn_ip4')[0],
+      'port': '31337',
+      'archiveName': 'sosreport_{0}'.format(grains.get('domain')),
+      'reportWorkDir':  '/tmp/sosreport_collect_' + 9999|random_str } %}
+
+{%- if system.sosreport.collect is defined %}
+  {%- for key,value in system.sosreport.collect.items() %}
+    {%- do collectDefaultOpts.update({ key: value }) %}
+  {%- endfor %}
+{%- endif %}
+{%- if pillar['sosreport_collect'] is defined %}
+  {%- for key,value in pillar['sosreport_collect'].items() %}
+    {%- do collectDefaultOpts.update({ key: value }) %}
+  {%- endfor %}
+{%- endif %}
+
+sosreport_tmp_collect_dir:
+  file.directory:
+    - name: {{ collectDefaultOpts['reportWorkDir'] }}
+    - user: root
+
+sosreport_send_script:
+  file.managed:
+  - name: {{ collectDefaultOpts['reportWorkDir'] }}/sosreport_collect.sh
+  - source: salt://linux/files/sosreport_collect.sh
+  - makedirs: true
+  - user: root
+  - mode: 755
+  - template: jinja
+  - require:
+    - file: sosreport_tmp_collect_dir
+  - defaults: |
+    nodeIp: {{ collectDefaultOpts['nodeIp'] }}
+    port: {{ collectDefaultOpts['port'] }}
+    target: {{ collectDefaultOpts['target'] }}
+    reportWorkDir: {{ collectDefaultOpts['reportWorkDir'] }}
+    archiveName: {{ collectDefaultOpts['archiveName'] }}
+
+sosreport_run:
+  cmd.run:
+    - name: {{ collectDefaultOpts['reportWorkDir'] }}/sosreport_collect.sh
+    - require:
+      - file: sosreport_send_script
+
+{%- else %}
+echo_unset_data:
+  cmd.run:
+  - name: echo "You are trying to collect Sosreport, for this pillar data sosreport.collect or pillar override sosreport_collect should be set. See README for sosreport.collect usage. Skipping."
+{%- endif %}
diff --git a/linux/system/sosreport/report.sls b/linux/system/sosreport/report.sls
new file mode 100644
index 0000000..0e2ca82
--- /dev/null
+++ b/linux/system/sosreport/report.sls
@@ -0,0 +1,67 @@
+{%- from "linux/map.jinja" import system with context %}
+
+sosreport_packages:
+  pkg.installed:
+    - names: [ sosreport, netcat-openbsd ]
+
+{# Package has default config file, so if nothing provided through pillars use it #}
+{%- if system.sosreport.config_options is defined %}
+sosreport_config_file:
+  file.managed:
+  - name: /etc/sos.conf
+  - source: salt://linux/files/sos.conf
+  - user: root
+  - mode: 644
+  - template: jinja
+  - require:
+    - pkg: sosreport_packages
+{%- endif %}
+
+{%- set cmd_opts = { 'tmp-dir': '/tmp/sosreport_' + 9999|random_str } %}
+{%- if system.sosreport.cmd_options is defined %}
+  {%- for key,value in system.sosreport.cmd_options.items() %}
+    {%- do cmd_opts.update({ key: value }) %}
+  {%- endfor %}
+{%- endif %}
+{%- if pillar['sosreport'] is defined %}
+  {%- for key,value in pillar['sosreport'].items() %}
+    {%- do cmd_opts.update({ key: value }) %}
+  {%- endfor %}
+{%- endif %}
+{%- set opts_list = [] %}
+{%- for key,value in cmd_opts.items() %}
+  {%- if key == 'no_arg_opts' %}
+    {%- do opts_list.append(value|join(' ')) %}
+  {%- else %}
+    {%- do opts_list.append('--{0} {1}'.format(key, value)) %}
+  {%- endif %}
+{%- endfor %}
+
+sosreport_tmp_dir:
+  file.directory:
+    - name: {{ cmd_opts['tmp-dir'] }}
+    - user: root
+
+sosreport_run:
+  cmd.run:
+    - name: sosreport {{ opts_list|join(' ') }}
+    - require:
+      - pkg: sosreport_packages
+      - file: sosreport_tmp_dir
+{%- if system.sosreport.config_options is defined %}
+      - file: sosreport_config_file
+{%- endif %}
+
+sosreport_send_script:
+  file.managed:
+  - name: /var/tmp/sosreport_send.sh
+  - makedirs: true
+  - user: root
+  - mode: 755
+  - template: jinja
+  - contents: |
+      #!/bin/bash
+      masterHost=${1}
+      masterPort=${2}
+      cd {{ cmd_opts['tmp-dir'] }}
+      tar -c sosreport-* | nc ${masterHost} ${masterPort}