Repository: claranet/ssha

Issue:
Exclude instances
The `discover` and `bastion` blocks allow filters on tags and such. It could be useful to exclude instances with certain tags too.

Comments:

Patch:
diff --git a/README.md b/README.md
index ca01c66..5031e43 100644
--- a/README.md
+++ b/README.md
@@ -130,7 +130,13 @@ discover {
       name of the config that was selected by the user.
       */
       Environment = "${config.name}"
-      Service     = "bastion"
+    }
+
+    /*
+    TagsNotEqual can be used to exclude instances with matching tags.
+    */
+    TagsNotEqual {
+      Service = "k8s"
     }
   }
 
diff --git a/ssha/ec2.py b/ssha/ec2.py
index 8c51e56..d60c327 100644
--- a/ssha/ec2.py
+++ b/ssha/ec2.py
@@ -1,5 +1,7 @@
 from __future__ import print_function
 
+import operator
+
 from . import aws, config, errors, ssm
 
 
@@ -42,19 +44,27 @@ def _instance_sort_key(instance):
     return result
 
 
-def _rules_pass(obj, rules):
+def _rules_pass(obj, rules, compare=operator.eq):
 
     for key, expected_value in rules.items():
 
-        if key not in obj:
-            return False
-
         if isinstance(expected_value, dict):
-            nested_rules = expected_value
-            if not _rules_pass(obj[key], nested_rules):
+
+            if key.endswith('NotEqual'):
+                nested_compare = operator.ne
+                key = key[:-len('NotEqual')]
+            else:
+                nested_compare = compare
+
+            nested_rules_passed = _rules_pass(
+                obj=obj.get(key) or {},
+                rules=expected_value,
+                compare=nested_compare,
+            )
+            if not nested_rules_passed:
                 return False
 
-        elif obj[key] != expected_value:
+        elif not compare(obj.get(key), expected_value):
             return False
 
     return True


Test patch:
diff --git a/tests/test_ec2.py b/tests/test_ec2.py
new file mode 100644
index 0000000..d44e071
--- /dev/null
+++ b/tests/test_ec2.py
@@ -0,0 +1,49 @@
+import unittest
+
+from ssha import ec2
+
+
+class TestEC2(unittest.TestCase):
+
+    def test_rules_pass(self):
+
+        bastion_instance = {
+            'State': {
+                'Name': 'Running',
+            },
+            'Tags': {
+                'Service': 'bastion',
+            },
+        }
+
+        web_instance = {
+            'State': {
+                'Name': 'Running',
+            },
+            'Tags': {
+                'Service': 'web',
+            },
+        }
+
+        is_bastion = {
+            'State': {
+                'Name': 'Running',
+            },
+            'Tags': {
+                'Service': 'bastion',
+            }
+        }
+
+        is_not_bastion = {
+            'State': {
+                'Name': 'Running',
+            },
+            'TagsNotEqual': {
+                'Service': 'bastion',
+            }
+        }
+
+        self.assertTrue(ec2._rules_pass(bastion_instance, is_bastion))
+        self.assertTrue(ec2._rules_pass(web_instance, is_not_bastion))
+        self.assertFalse(ec2._rules_pass(web_instance, is_bastion))
+        self.assertFalse(ec2._rules_pass(bastion_instance, is_not_bastion))