Repository: warpnet/salt-lint (python)

Issue:
Feature Request: Check cmd state deprecation
### Preface
Kindly use the [check for state.cmd deprecation](https://github.com/roaldnefs/salt-lint/pull/37) as an example of adding a check. #37 has to be merged first.

### Description

Add the possibility to check for deprecation within the [cmd state](https://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html)

The following options have been deprecated:
* `cmd.run:quiet` (since _version 2014.1.0_)

The following options should be used:
* `cmd.run:output_loglevel: quiet`

Example(s) of an **improperly** configured state:
```code
getpip:
  cmd.run:
    - name: /usr/bin/python /usr/local/sbin/get-pip.py
    - unless: which pip
    - require:
      - pkg: python
      - file: /usr/local/sbin/get-pip.py
    - reload_modules: True
    - quiet
```

An example of **properly** configured state:
```code
getpip:
  cmd.run:
    - name: /usr/bin/python /usr/local/sbin/get-pip.py
    - unless: which pip
    - require:
      - pkg: python
      - file: /usr/local/sbin/get-pip.py
    - reload_modules: True
    - output_loglevel: quiet
```

### Fix requirements
A fix for this issue should (at least):
* Notify the user of using a deprecated option
* Notify the user since what version the option will be or has been deprecated
* A suggestion on what option to replace it with
* Contain unit tests for the check

Pull request description:
Warn on using quiet with cmd.run
- Warn on using the quiet argument with cmd.run
- Fixes #40 

Patch:
diff --git a/README.md b/README.md
index 5a02f7e..fbc3364 100644
--- a/README.md
+++ b/README.md
@@ -164,6 +164,10 @@ Optionally override the default file selection as follows:
 
 ## List of rules
 
+### Formatting
+
+Disable formatting checks using `-x formatting`
+
 Rule | Description
 :-:|:--
 [201](https://github.com/warpnet/salt-lint/wiki/201) | Trailing whitespace
@@ -180,6 +184,25 @@ Rule | Description
 [212](https://github.com/warpnet/salt-lint/wiki/212) | Most files should not contain irregular spaces
 [213](https://github.com/warpnet/salt-lint/wiki/213) | SaltStack recommends using `cmd.run` together with `onchanges`, rather than `cmd.wait`
 
+### Jinja
+
+Disable jinja checks using `-x jinja`
+
+Rule | Description
+:-:|:--
+[202](https://github.com/warpnet/salt-lint/wiki/202) | Jinja statement should have spaces before and after: `{% statement %}`
+[206](https://github.com/warpnet/salt-lint/wiki/206) | Jinja variables should have spaces before and after `{{ var_name }}`
+[209](https://github.com/warpnet/salt-lint/wiki/209) | Jinja comment should have spaces before and after: `{# comment #}`
+[211](https://github.com/warpnet/salt-lint/wiki/211) | `pillar.get` or `grains.get` should be formatted differently
+
+### Deprecations
+
+Disable deprecation checks using `-x deprecation`
+
+Rule | Description
+:-:|:--
+[901](https://github.com/warpnet/salt-lint/wiki/901) | Using the `quiet` argument with `cmd.run` is deprecated. Use `output_loglevel: quiet`
+
 ## False Positives: Skipping Rules
 
 Some rules are bit of a rule of thumb. To skip a specific rule for a specific task, inside your state add `# noqa [rule_id]` at the end of the line. You can skip multiple rules via a space-separated list. Example:
diff --git a/saltlint/rules/CmdRunQuietRule.py b/saltlint/rules/CmdRunQuietRule.py
new file mode 100644
index 0000000..5dd68ce
--- /dev/null
+++ b/saltlint/rules/CmdRunQuietRule.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+import re
+from saltlint.linter.rule import Rule
+from saltlint.utils import get_rule_skips_from_text
+
+class CmdRunQuietRule(Rule):
+    id = '901'
+    shortdesc = 'Using the quiet argument with cmd.run is deprecated. Use output_loglevel: quiet'
+    description = 'Using the quiet argument with cmd.run is deprecated. Use output_loglevel: quiet'
+
+    severity = 'HIGH'
+    tags = ['deprecation']
+    version_added = 'develop'
+
+    regex = re.compile(r"^.+\n^\s{2}cmd\.run:(?:\n.+)+\n^\s{4}- quiet\s?.*", re.MULTILINE)
+
+    def matchtext(self, file, text):
+        results = []
+
+        for match in re.finditer(self.regex, text):
+            # Get the location of the regex match
+            start = match.start()
+            end = match.end()
+
+            # Get the line number of the last character
+            lines = text[:end].splitlines()
+            line_no = len(lines)
+
+            # Skip result if noqa for this rule ID is found in section
+            section = text[start:end]
+            if self.id in get_rule_skips_from_text(section):
+                continue
+
+            # Append the match to the results
+            results.append((line_no, lines[-1], self.shortdesc))
+
+        return results
diff --git a/saltlint/utils.py b/saltlint/utils.py
index a0334b2..dbcdf23 100644
--- a/saltlint/utils.py
+++ b/saltlint/utils.py
@@ -31,3 +31,12 @@ def get_rule_skips_from_line(line):
         noqa_text = line.split('# noqa')[1]
         rule_id_list = noqa_text.split()
     return rule_id_list
+
+
+def get_rule_skips_from_text(text):
+    rule_id_list = []
+    for line in text.splitlines():
+        rule_id_list.extend(get_rule_skips_from_line(line))
+
+    # Return a list of unique ids
+    return list(set(rule_id_list))


Test patch:
diff --git a/tests/unit/TestCmdRunQuietRule.py b/tests/unit/TestCmdRunQuietRule.py
new file mode 100644
index 0000000..ffb4c55
--- /dev/null
+++ b/tests/unit/TestCmdRunQuietRule.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+import unittest
+
+from saltlint.linter.collection import RulesCollection
+from saltlint.rules.CmdRunQuietRule import CmdRunQuietRule
+from tests import RunFromText
+
+
+GOOD_QUIET_STATE = '''
+getpip:
+  cmd.run:
+    - name: /usr/bin/python /usr/local/sbin/get-pip.py
+    - unless: which pip
+    - require:
+      - pkg: python
+      - file: /usr/local/sbin/get-pip.py
+    - output_loglevel: quiet
+'''
+
+BAD_QUIET_STATE = '''
+getpip:
+  cmd.run:
+    - name: /usr/bin/python /usr/local/sbin/get-pip.py
+    - unless: which pip
+    - require:
+      - pkg: python
+      - file: /usr/local/sbin/get-pip.py
+    - quiet  # This is the ninth line
+
+getpip2:
+  cmd.run:
+    - name: /usr/bin/python /usr/local/sbin/get-pip.py
+    - quiet
+
+getpip3:
+  cmd.run:
+    - name: /usr/bin/python /usr/local/sbin/get-pip.py
+    - quiet # noqa: 901
+'''
+
+class TestCmdRunQuietRule(unittest.TestCase):
+    collection = RulesCollection()
+
+    def setUp(self):
+        self.collection.register(CmdRunQuietRule())
+
+    def test_statement_positive(self):
+        runner = RunFromText(self.collection)
+        results = runner.run_state(GOOD_QUIET_STATE)
+        self.assertEqual(0, len(results))
+
+    def test_statement_negative(self):
+        runner = RunFromText(self.collection)
+        results = runner.run_state(BAD_QUIET_STATE)
+        self.assertEqual(2, len(results))
+
+        # Check line numbers of the results
+        self.assertEqual(9, results[0].linenumber)
+        self.assertEqual(14, results[1].linenumber)