diff --git a/README.md b/README.md
index f17e1cb7..88eb2d35 100644
--- a/README.md
+++ b/README.md
@@ -209,10 +209,11 @@ Steps are the functional tests that is actually executing necessary task to vali
| WHEN | I `{math_formula}` them | `action`: `math_formula` |
| WHEN | its `{key}` is `{value}`
its `{key}` has `{value}`
its `{key}` contains `{value}`
its `{key}` includes `{value}` | `key`: any property that resource have (e.g. name, address, etc. ) `address` will give the terraform object name
`value`: any string or numeric value that the property has.
_Found resources from previous step will be filtered based on these values._ |
| WHEN | its `{key}` is not `{value}`
its `{key}` has not `{value}`
its `{key}` does not contain `{value}`
its `{key}` does not include `{value}` | `key`: any property that resource have (e.g. name, address, etc. ) `address` will give the terraform object name
`value`: any string, bool or numeric value that the property has.
_Found resources from previous step will be filtered based on these values._ |
-| THEN | I expect the result is `{operator}` than `{number}`
Its value must be `{operator}` than `{number}` | `operator`: `more`, `more and equal`, `less`, `less and equal`
`number`: an integer |
+| THEN | I expect the result is `{operator}` than/to `{number}`
Its value must be `{operator}` than/to `{number}` | `operator`: `more`, `more and equal`, `less`, `less and equal`, `equal`
`number`: an integer |
| WHEN
THEN | it contain `{something}`
it contains `{something}`
it must contain `{something}` | `something`: any property within terraform resoruce/provider/etc. (e.g. `access_key`, `ingress` ) |
| THEN | `{property}` is enabled
`{property}` must be enabled | `property`: can be either a generic property from your terraform configuration or templated ones like below for some resources;
* `encryption at rest`
* `encrytion in flight`|
| THEN | its value `{condition}` match the "`{search_regex}`" regex | `condition`: `must` or `must not`
`search_regex`: the regular expression of the searching value |
+| THEN | its value `{condition}` be `{value}` | `condition`: `must` or `must not`
`value`: the matching value |
| WHEN
THEN | its value must be set by a variable | |
| THEN | it must `{condition}` have `{proto}` protocol and port `{port}` for `{cidr}` | `{condition}`: only,not
`proto`: tcp, udp
`port`: integer port number (or a port range by using `-` delimeter between port ranges [e.g. 80-84])
`cidr`: IP/Cidr |
| THEN | the scenario fails
the scenario should fail
it fails
it should fail
it must fail | None |
diff --git a/example/example_01/aws/data.example.feature b/example/example_01/aws/data.example.feature
index 0b1037c5..aba9a3de 100644
--- a/example/example_01/aws/data.example.feature
+++ b/example/example_01/aws/data.example.feature
@@ -5,3 +5,9 @@ Feature: Data example feature
When it contains zone_ids
And I count them
Then I expect the result is greater than 2
+
+ Scenario: Subnet Count
+ Given I have aws_availability_zones data defined
+ When it contains zone_ids
+ And I count them
+ Then I expect the result is equal to 3
diff --git a/example/example_01/aws/resource_filtering.feature b/example/example_01/aws/resource_filtering.feature
index 39deecf3..9e995508 100644
--- a/example/example_01/aws/resource_filtering.feature
+++ b/example/example_01/aws/resource_filtering.feature
@@ -5,10 +5,9 @@ Feature: Resource Filtering example
When its address is aws_s3_bucket.s3_bucket_prod
And it contains tags
Then it must contain
- And its value must match the "" regex
+ And its value must be
Examples:
- | tag_keys | pattern |
- | Data Classification | ^PRIVATE$ |
- | Data Residency | ^EU$ |
-
\ No newline at end of file
+ | tag_keys | string |
+ | Data Classification | PRIVATE |
+ | Data Residency | EU |
diff --git a/terraform_compliance/main.py b/terraform_compliance/main.py
index 12942b08..a9eab0b1 100644
--- a/terraform_compliance/main.py
+++ b/terraform_compliance/main.py
@@ -7,7 +7,7 @@
from radish.main import main as call_radish
__app_name__ = "terraform-compliance"
-__version__ = "1.0.36"
+__version__ = "1.0.37"
print('{} v{} initiated\n'.format(__app_name__, __version__))
diff --git a/terraform_compliance/steps/steps.py b/terraform_compliance/steps/steps.py
index ed77d473..d97fc1f1 100644
--- a/terraform_compliance/steps/steps.py
+++ b/terraform_compliance/steps/steps.py
@@ -379,6 +379,8 @@ def i_action_them(_step_obj, action_type):
@then(u'Its value must be {operator:ANY} than {number:d}')
@then(u'I expect the result is {operator:ANY} than {number:d}')
+@then(u'Its value must be {operator:ANY} to {number:d}')
+@then(u'I expect the result is {operator:ANY} to {number:d}')
def i_expect_the_result_is_operator_than_number(_step_obj, operator, number, _stash=EmptyStash):
values = _step_obj.context.stash if _stash is EmptyStash else _stash
@@ -399,12 +401,15 @@ def i_expect_the_result_is_operator_than_number(_step_obj, operator, number, _st
assert values < number, "{} is not less than {}".format(values, number)
elif operator in ("less and equal", "lesser and equal", "smaller and equal"):
assert values <= number, "{} is not less and equal than {}".format(values, number)
+ elif operator in ("equal",):
+ assert values == number, "{} is not equal to {}".format(values, number)
else:
raise TerraformComplianceNotImplemented('Invalid operator: {}'.format(operator))
elif type(values) is Null:
raise TerraformComplianceNotImplemented('Null/Empty value found on {}'.format(_step_obj.context.type))
+
@step(u'its value {condition:ANY} match the "{search_regex}" regex')
def its_value_condition_match_the_search_regex_regex(_step_obj, condition, search_regex, _stash=EmptyStash):
def fail(condition, name=None):
@@ -444,6 +449,12 @@ def fail(condition, name=None):
for key, value in values.items():
its_value_condition_match_the_search_regex_regex(_step_obj, condition, search_regex, value)
+
+@step(u'its value {condition:ANY} be {match:ANY}')
+def its_value_condition_equal(_step_obj, condition, match, _stash=EmptyStash):
+ its_value_condition_match_the_search_regex_regex(_step_obj, condition, "^" + re.escape(match) + "$", _stash)
+
+
@then(u'the scenario fails')
@then(u'the scenario should fail')
@then(u'it fails')
diff --git a/tests/terraform_compliance/steps/test_main_steps.py b/tests/terraform_compliance/steps/test_main_steps.py
index 22e1b2ff..e8d0f142 100644
--- a/tests/terraform_compliance/steps/test_main_steps.py
+++ b/tests/terraform_compliance/steps/test_main_steps.py
@@ -8,7 +8,8 @@
its_value_condition_match_the_search_regex_regex,
it_condition_have_proto_protocol_and_port_port_for_cidr,
it_fails,
- its_key_is_value, its_key_is_not_value
+ its_key_is_value, its_key_is_not_value,
+ its_value_condition_equal
)
from terraform_compliance.common.exceptions import TerraformComplianceNotImplemented, Failure, TerraformComplianceInternalFailure
from tests.mocks import MockedStep, MockedWorld, MockedTerraformPropertyList, MockedTerraformResourceList, MockedTerraformResource
@@ -311,6 +312,14 @@ def test_i_expect_the_result_is_operator_than_number_less_and_equal(self):
i_expect_the_result_is_operator_than_number(step, 'less and equal', 41)
self.assertEqual(str(err.exception), '42 is not less and equal than 41')
+ def test_i_expect_the_result_is_operator_than_number_equal(self):
+ step = MockedStep()
+ step.context.stash = {'values': 42}
+ self.assertIsNone(i_expect_the_result_is_operator_than_number(step, 'equal', 42))
+ with self.assertRaises(AssertionError) as err:
+ i_expect_the_result_is_operator_than_number(step, 'equal', 41)
+ self.assertEqual(str(err.exception), '42 is not equal to 41')
+
def test_i_expect_the_result_is_more_than_number_failure(self):
step = MockedStep()
step.context.stash = dict(values=3)
@@ -478,6 +487,8 @@ def test_its_value_condition_match_the_search_regex_regex_null_value_is_parsed(s
with self.assertRaises(Failure):
its_value_condition_match_the_search_regex_regex(step, 'must', 'something')
+ with self.assertRaises(Failure):
+ its_value_condition_equal(step, 'must', 'something')
def test_its_value_condition_match_the_search_regex_regex_success(self):
step = MockedStep()
@@ -509,6 +520,31 @@ def test_its_value_condition_match_the_search_regex_regex_success(self):
self.assertEqual(its_value_condition_match_the_search_regex_regex(step, 'must not', 'some_.*'), None)
self.assertEqual(its_value_condition_match_the_search_regex_regex(step, 'must not', 'some_other.*'), None)
+ def test_its_value_condition_equals(self):
+ step = MockedStep()
+ expected_value = r"https://www.stackoverflow.com[as](.*)\s\t+$"
+ step.context.stash = [
+ {
+ 'address': 'some_resource.id',
+ 'type': 'some_resource_type',
+ 'name': 'some_name',
+ 'values': {
+ 'some_key': expected_value
+ }
+ }
+ ]
+ step.context.type = 'resource'
+ step.context.name = 'some_name'
+ step.context.property_name = 'tags'
+ step.context_sensitive_sentence = 'must'
+
+ self.assertEqual(its_value_condition_equal(step, 'must', expected_value), None)
+ self.assertEqual(its_value_condition_equal(step, 'must not', expected_value * 2), None)
+
+ with self.assertRaises(Failure):
+ self.assertEqual(its_value_condition_equal(step, 'must', expected_value + ' '), None)
+ self.assertEqual(its_value_condition_equal(step, 'must not', expected_value), None)
+
def test_its_key_is_not_value_exist_in_values_bool(self):
step = MockedStep()
step.context.stash = [
@@ -617,4 +653,4 @@ def test_its_key_is_not_value_exist_in_values_int(self):
]
its_key_is_not_value(step, 'storage_encrypted', 1)
self.assertTrue(type(step.context.stash) is list)
- self.assertEqual(step.context.stash[0]['some_key'], 'some_other_value')
\ No newline at end of file
+ self.assertEqual(step.context.stash[0]['some_key'], 'some_other_value')