Repository: warpnet/salt-lint (python)

Issue:
Feature Request: Check state deprecation
**Is your feature request related to a problem? Please describe.**

It would be nice to notify user about deprecated states in SaltStack.

**Describe the solution you'd like**

Add the possibility to check for deprecation within used states.

Example(s) of an **improperly** configured state:
```code
mytestindex:
  elasticsearch_index.present
```

Example(s) of **properly** configured state:
```code
mytestindex:
  elasticsearch.index_present
```

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

**Additional context**

This issues is a replacement of:
- #42
- #38 
- #39 
- #41 
- #43 
- #44


Pull request description:
Add new deprecation functionality
- Add DeprecationRule function
- Add new deprecation rules
- ~Unit tests still need to be written~

Fixes #204 

Patch:
diff --git a/saltlint/linter/rule.py b/saltlint/linter/rule.py
index becf140..e006699 100644
--- a/saltlint/linter/rule.py
+++ b/saltlint/linter/rule.py
@@ -7,6 +7,7 @@ import six
 
 from saltlint.utils import get_rule_skips_from_line, get_file_type
 from saltlint.linter.match import Match
+from saltlint.utils import LANGUAGE_SLS
 
 
 class Rule(object):
@@ -86,3 +87,32 @@ class Rule(object):
             matches.append(Match(line, section, file['path'], self, message))
 
         return matches
+
+
+class DeprecationRule(Rule):
+    id = None
+    state = None
+    deprecated_since = None
+
+    severity = 'HIGH'
+    languages = [LANGUAGE_SLS]
+    tags = ['deprecation']
+
+    @property
+    def shortdesc(self):
+        return "State '{}' is deprecated since SaltStack version '{}'".format(
+            self.state, self.deprecated_since
+        )
+
+    @property
+    def description(self):
+        return self.shortdesc
+
+    @property
+    def regex(self):
+        return re.compile(
+            r"^\s{2}" + self.state.replace(".", r"\.") + "(?=:|$)"
+        )
+
+    def match(self, file, line):
+        return self.regex.search(line)
diff --git a/saltlint/rules/CmdWaitRecommendRule.py b/saltlint/rules/CmdWaitRecommendRule.py
index 2fd7405..9a8ce90 100644
--- a/saltlint/rules/CmdWaitRecommendRule.py
+++ b/saltlint/rules/CmdWaitRecommendRule.py
@@ -2,11 +2,11 @@
 # Copyright (c) 2020 Warpnet B.V.
 
 import re
-from saltlint.linter.rule import Rule
+from saltlint.linter.rule import DeprecationRule
 from saltlint.utils import LANGUAGE_SLS
 
 
-class CmdWaitRecommendRule(Rule):
+class CmdWaitRecommendRule(DeprecationRule):
     id = '213'
     shortdesc = 'SaltStack recommends using cmd.run together with onchanges, rather than cmd.wait'
     description = 'SaltStack recommends using cmd.run together with onchanges, rather than cmd.wait'
diff --git a/saltlint/rules/SLSFileNameRule.py b/saltlint/rules/SLSFileNameRule.py
index f251b50..e4b6db4 100644
--- a/saltlint/rules/SLSFileNameRule.py
+++ b/saltlint/rules/SLSFileNameRule.py
@@ -3,6 +3,7 @@
 
 import os
 from saltlint.linter.rule import Rule
+from saltlint.utils import LANGUAGE_SLS
 
 
 class SLSFileNameRule(Rule):
@@ -12,6 +13,7 @@ class SLSFileNameRule(Rule):
     description = ('SLS file with a period in the name (besides the suffix period) can not be '
                    'referenced')
     severity = 'HIGH'
+    languages = [LANGUAGE_SLS]
     tags = ['formatting']
     version_added = 'develop'
 
diff --git a/saltlint/rules/StateDeprecationDockerAbsent.py b/saltlint/rules/StateDeprecationDockerAbsent.py
new file mode 100644
index 0000000..50955be
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerAbsent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerAbsent(DeprecationRule):
+    id = '906'
+    state = 'docker.absent'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerImageAbsent.py b/saltlint/rules/StateDeprecationDockerImageAbsent.py
new file mode 100644
index 0000000..74e65fc
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerImageAbsent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerImageAbsent(DeprecationRule):
+    id = '907'
+    state = 'docker.image_absent'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerImagePresent.py b/saltlint/rules/StateDeprecationDockerImagePresent.py
new file mode 100644
index 0000000..cae5946
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerImagePresent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerImagePresent(DeprecationRule):
+    id = '908'
+    state = 'docker.image_present'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerModWatch.py b/saltlint/rules/StateDeprecationDockerModWatch.py
new file mode 100644
index 0000000..34f618a
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerModWatch.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerModWatch(DeprecationRule):
+    id = '909'
+    state = 'docker.mod_watch'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerNetworkAbsent.py b/saltlint/rules/StateDeprecationDockerNetworkAbsent.py
new file mode 100644
index 0000000..95a20b9
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerNetworkAbsent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerNetworkAbsent(DeprecationRule):
+    id = '910'
+    state = 'docker.network_absent'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerNetworkPresent.py b/saltlint/rules/StateDeprecationDockerNetworkPresent.py
new file mode 100644
index 0000000..2d0bc6d
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerNetworkPresent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerNetworkPresent(DeprecationRule):
+    id = '911'
+    state = 'docker.network_present'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerRunning.py b/saltlint/rules/StateDeprecationDockerRunning.py
new file mode 100644
index 0000000..e88b375
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerRunning.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerRunning(DeprecationRule):
+    id = '912'
+    state = 'docker.running'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerStopped.py b/saltlint/rules/StateDeprecationDockerStopped.py
new file mode 100644
index 0000000..2995919
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerStopped.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerStopped(DeprecationRule):
+    id = '913'
+    state = 'docker.stopped'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerVolumeAbsent.py b/saltlint/rules/StateDeprecationDockerVolumeAbsent.py
new file mode 100644
index 0000000..f506d69
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerVolumeAbsent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerVolumeAbsent(DeprecationRule):
+    id = '914'
+    state = 'docker.volume_absent'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationDockerVolumePresent.py b/saltlint/rules/StateDeprecationDockerVolumePresent.py
new file mode 100644
index 0000000..36d4563
--- /dev/null
+++ b/saltlint/rules/StateDeprecationDockerVolumePresent.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationDockerVolumePresent(DeprecationRule):
+    id = '915'
+    state = 'docker.volume_present'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationElasticsearchIndexRule.py b/saltlint/rules/StateDeprecationElasticsearchIndexRule.py
new file mode 100644
index 0000000..e12a7c3
--- /dev/null
+++ b/saltlint/rules/StateDeprecationElasticsearchIndexRule.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationElasticsearchIndexRule(DeprecationRule):
+    id = '902'
+    state = 'elasticsearch_index.absent'
+    deprecated_since = '2017.7.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationVirtRevertedRule.py b/saltlint/rules/StateDeprecationVirtRevertedRule.py
new file mode 100644
index 0000000..89bc21a
--- /dev/null
+++ b/saltlint/rules/StateDeprecationVirtRevertedRule.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationVirtRevertedRule(DeprecationRule):
+    id = '903'
+    state = 'virt.reverted'
+    deprecated_since = '2016.3.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationVirtSavedRule.py b/saltlint/rules/StateDeprecationVirtSavedRule.py
new file mode 100644
index 0000000..17cafed
--- /dev/null
+++ b/saltlint/rules/StateDeprecationVirtSavedRule.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationVirtSavedRule(DeprecationRule):
+    id = '904'
+    state = 'virt.saved'
+    deprecated_since = '2016.3.0'
+    version_added = 'develop'
diff --git a/saltlint/rules/StateDeprecationVirtUnpoweredRule.py b/saltlint/rules/StateDeprecationVirtUnpoweredRule.py
new file mode 100644
index 0000000..b153eef
--- /dev/null
+++ b/saltlint/rules/StateDeprecationVirtUnpoweredRule.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+from saltlint.linter.rule import DeprecationRule
+
+
+class StateDeprecationVirtUnpoweredRule(DeprecationRule):
+    id = '905'
+    state = 'virt.unpowered'
+    deprecated_since = '2016.3.0'
+    version_added = 'develop'


Test patch:
diff --git a/tests/unit/TestDeprecationRule.py b/tests/unit/TestDeprecationRule.py
new file mode 100644
index 0000000..b52c632
--- /dev/null
+++ b/tests/unit/TestDeprecationRule.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Warpnet B.V.
+
+import unittest
+
+from saltlint.linter.collection import RulesCollection
+from saltlint.rules.StateDeprecationDockerAbsent import StateDeprecationDockerAbsent
+from tests import RunFromText
+
+
+GOOD_STATES_LINE = '''
+example:
+  docker_container.absent:
+    - name: example
+
+example:
+  docker_container.absent
+'''
+
+BAD_STATES_LINE = '''
+example:
+  docker.absent:
+    - name: example
+
+example:
+  docker.absent
+'''
+
+class TestFileModeLeadingZeroRule(unittest.TestCase):
+    collection = RulesCollection()
+
+    def setUp(self):
+        self.collection.register(StateDeprecationDockerAbsent())
+
+    def test_statement_positive(self):
+        runner = RunFromText(self.collection)
+        results = runner.run_state(GOOD_STATES_LINE)
+        self.assertEqual(0, len(results))
+
+    def test_statement_negative(self):
+        runner = RunFromText(self.collection)
+        results = runner.run_state(BAD_STATES_LINE)
+        self.assertEqual(2, len(results))
diff --git a/tests/unit/TestSLSFileNameRule.py b/tests/unit/TestSLSFileNameRule.py
index 770070a..924306e 100644
--- a/tests/unit/TestSLSFileNameRule.py
+++ b/tests/unit/TestSLSFileNameRule.py
@@ -24,8 +24,11 @@ class TestSLSFileNameRule(unittest.TestCase):
         runner = Runner(self.collection, path, Configuration())
         self.assertEqual([], runner.run())
 
+        path = 'tests/test-extension-failure.extra.jinja'
+        runner = Runner(self.collection, path, Configuration())
+        self.assertEqual([], runner.run())
+
     def test_file_negative(self):
         path = 'tests/test-extension-failure.extra.sls'
         runner = Runner(self.collection, path, Configuration())
-        errors = runner.run()
-        self.assertEqual(1, len(errors))
+        self.assertEqual(1, len(runner.run()))