From d706f75d2dfc91c3a6ee2c551b5e123426863100 Mon Sep 17 00:00:00 2001 From: Peijun Ma Date: Fri, 9 Aug 2019 11:55:06 -0400 Subject: [PATCH 1/3] Add `equal` operator --- README.md | 2 +- example/example_01/aws/data.example.feature | 6 ++++++ terraform_compliance/steps/steps.py | 4 ++++ tests/terraform_compliance/steps/test_main_steps.py | 10 +++++++++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32f3bc92..5b3b912a 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ 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 | 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/terraform_compliance/steps/steps.py b/terraform_compliance/steps/steps.py index 5d1b408b..dd1e49be 100644 --- a/terraform_compliance/steps/steps.py +++ b/terraform_compliance/steps/steps.py @@ -376,6 +376,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 @@ -396,6 +398,8 @@ 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)) diff --git a/tests/terraform_compliance/steps/test_main_steps.py b/tests/terraform_compliance/steps/test_main_steps.py index 22e1b2ff..df920243 100644 --- a/tests/terraform_compliance/steps/test_main_steps.py +++ b/tests/terraform_compliance/steps/test_main_steps.py @@ -311,6 +311,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) @@ -617,4 +625,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') From 6ec0293712431bae65de3c0592384df051b28b5c Mon Sep 17 00:00:00 2001 From: Peijun Ma Date: Fri, 9 Aug 2019 13:35:55 -0400 Subject: [PATCH 2/3] Implement exact value matching step --- README.md | 1 + .../example_01/aws/resource_filtering.feature | 9 +++--- terraform_compliance/steps/steps.py | 7 +++++ .../steps/test_main_steps.py | 30 ++++++++++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5b3b912a..deda853d 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ Steps are the functional tests that is actually executing necessary task to vali | 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/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/steps/steps.py b/terraform_compliance/steps/steps.py index dd1e49be..87226ec6 100644 --- a/terraform_compliance/steps/steps.py +++ b/terraform_compliance/steps/steps.py @@ -406,6 +406,7 @@ def i_expect_the_result_is_operator_than_number(_step_obj, operator, number, _st 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): @@ -445,6 +446,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 df920243..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 @@ -486,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() @@ -517,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 = [ From 6ab230203de1164b86b8191ceec6277863747708 Mon Sep 17 00:00:00 2001 From: Peijun Ma Date: Fri, 9 Aug 2019 14:06:54 -0400 Subject: [PATCH 3/3] Bump patch version --- terraform_compliance/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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__))