Skip to content

Commit

Permalink
Regex support on When its key has value (#608)
Browse files Browse the repository at this point in the history
* Added documentation about the new feature

* Enabled regex support on Match object

* Fixed an ambigiuous error shown where calling radish

* Added regex support for When its key is value

* Added step abstracts

* Changed the error string of an ambigious error

* Fixed `None` declarations where it was mistakenly declared as `Null`

* Fixed some parameter mis-definitions

* Implemented regex capability on metadata steps for `When` directive

* Updated the documentation about the new steps

* test_regex_on_when: Changed expected test outcome

* Changed the expected test outcome, again
  • Loading branch information
eerkunt authored Mar 22, 2022
1 parent c7ba95d commit 3a69925
Show file tree
Hide file tree
Showing 14 changed files with 343 additions and 16 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

# Unreleased
* Added a new step: `When its {key} is "{value}" regex` [#608](https://github.com/terraform-compliance/cli/issues/608)) [#602](https://github.com/terraform-compliance/cli/issues/602))
* Added a new step: `When its {key} is not "{value}" regex` [#608](https://github.com/terraform-compliance/cli/issues/608)) [#602](https://github.com/terraform-compliance/cli/issues/602))
* Added a new step: `When its {key} metadata has "{value}" regex` [#608](https://github.com/terraform-compliance/cli/issues/608)) [#602](https://github.com/terraform-compliance/cli/issues/602))
* Added a new step: `When its {key} metadata has not "{value}" regex` [#608](https://github.com/terraform-compliance/cli/issues/608)) [#602](https://github.com/terraform-compliance/cli/issues/602))
* Fixed a problem that was throwing an exception `IndexError: list index out of range` [#605](https://github.com/terraform-compliance/cli/issues/605))

# 1.3.31 (2021-12-09)
* Added support for terraform 1.1.x

Expand Down
206 changes: 206 additions & 0 deletions docs/pages/bdd-references/when.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,60 @@ is
| [key](#){: .p-1 .text-blue-100 .fw-700} | any dictionary key (allows spaces). | `my dictionary key` |
| [value](#){: .p-1 .text-blue-100 .fw-700} | any dictionary value (allows spaces). | `my dictionary value` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} has \"[something]\"(#){: .p-1 .text-blue-100 .fw-700} regex
This is a filtering function with regular expression support on values. Thus, found resources from previous step will be filtered based on these values.

> __Possible sentences :__
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
is "[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
has
"[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
includes
"[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
contains
"[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[dictionary](#){: .p-1 .text-green-200 .fw-700}
includes an entry where
"
[key](#){: .p-1 .text-blue-100 .fw-700}
"
is
is "[value](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
| key | Description | Examples |
|:---:|:----------|:-|
| [property](#){: .p-1 .text-green-200 .fw-700} | any property that resources have. Using `type` will give the Terraform resource type; `address` is the name you have given it | `address` `name` `size` |
| [dictionary](#){: .p-1 .text-green-200 .fw-700} | a dictionary property that resource has. | `tags` |
| [something](#){: .p-1 .text-blue-100 .fw-700} | Any regular expression | `aws_s3_.*"` |
| [some string with spaces](#){: .p-1 .text-blue-100 .fw-700} | any string (allows spaces). | `my dictionary key` |
| [key](#){: .p-1 .text-blue-100 .fw-700} | any dictionary key (allows spaces). | `my dictionary key` |
| [value](#){: .p-1 .text-blue-100 .fw-700} | Any regular expression | `^4\\..*` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} has not [something](#){: .p-1 .text-blue-100 .fw-700}
This is a filtering function. Thus, found resources from previous step will be filtered based on these values.
Expand Down Expand Up @@ -331,6 +385,60 @@ is
| [key](#){: .p-1 .text-blue-100 .fw-700} | any dictionary key (allows spaces). | `my dictionary key` |
| [value](#){: .p-1 .text-blue-100 .fw-700} | any dictionary value (allows spaces). | `my dictionary value` |

### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} has not \"[something]\"(#){: .p-1 .text-blue-100 .fw-700} regex
This is a filtering function with regular expression support on values. Thus, found resources from previous step will be filtered based on these values.

> __Possible sentences :__
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
is not "[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
has
not "[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
does not include
"[something](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
does not contain
"[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[dictionary](#){: .p-1 .text-green-200 .fw-700}
does not include an entry where
"
[key](#){: .p-1 .text-blue-100 .fw-700}
"
is
is "[value](#){: .p-1 .text-blue-100 .fw-700}" regex
>
>
| key | Description | Examples |
|:---:|:----------|:-|
| [property](#){: .p-1 .text-green-200 .fw-700} | any property that resources have. Using `type` will give the Terraform resource type; `address` is the name you have given it | `address` `name` `size` |
| [dictionary](#){: .p-1 .text-green-200 .fw-700} | a dictionary property that resource has. | `tags` |
| [something](#){: .p-1 .text-blue-100 .fw-700} | Any regular expression | `aws_s3_.*"` |
| [some string with spaces](#){: .p-1 .text-blue-100 .fw-700} | any string (allows spaces). | `my dictionary key` |
| [key](#){: .p-1 .text-blue-100 .fw-700} | any dictionary key (allows spaces). | `my dictionary key` |
| [value](#){: .p-1 .text-blue-100 .fw-700} | Any regular expression | `^4\\..*` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} metadata has [something](#){: .p-1 .text-blue-100 .fw-700}
This is a filtering function. Thus, found resources from previous step will be filtered based on these values.
Expand Down Expand Up @@ -391,6 +499,51 @@ metadata contains
| [property](#){: .p-1 .text-green-200 .fw-700} | any metadata that resources have. Using `actions` will give the Terraform's actions on the resource on an apply; `address` is the name you have given it | `address` `name` `actions` |
| [something](#){: .p-1 .text-blue-100 .fw-700} | any string or numeric value that the property has. | `s3_my_bucket` `my-bucket` `create` `"something with spaces"` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} metadata has "[something](#){: .p-1 .text-blue-100 .fw-700}" regex
This step has the same functionality with `When its property metadata has something` step. Additionaly this step
supports regular expressions for matching values.

> __Possible sentences :__
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata is "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata has "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata includes "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata contains ""
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
| key | Description | Examples |
|:---:|:----------|:-|
| [property](#){: .p-1 .text-green-200 .fw-700} | any metadata that resources have. Using `actions` will give the Terraform's actions on the resource on an apply; `address` is the name you have given it | `address` `name` `actions` |
| " [regex](#){: .p-1 .text-blue-100 .fw-700} " | any regular expression with special characters escaped | `"module.project1.*"` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} metadata has not [something](#){: .p-1 .text-blue-100 .fw-700}
This is a filtering function. Thus, found resources from previous step will be filtered based on these values.
Expand Down Expand Up @@ -458,6 +611,59 @@ metadata does not contain
| [property](#){: .p-1 .text-green-200 .fw-700} | any metadata that resources have. Using `actions` will give the Terraform's actions on the resource on an apply; `address` is the name you have given it | `address` `name` `actions` |
| [something](#){: .p-1 .text-blue-100 .fw-700} | any string or numeric value that the property has. | `s3_my_bucket` `my-bucket` `create` `"something with spaces"` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} metadata has not [something](#){: .p-1 .text-blue-100 .fw-700}
This step has the same functionality with `When its property metadata has not something` step. Additionaly this step
supports regular expressions for matching values.

> __Possible sentences :__
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata is not "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata has not "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata does not have "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata does not include "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
[When](#){: .p-1 .text-red-200}
its
[property](#){: .p-1 .text-green-200 .fw-700}
metadata does not contain "
[something](#){: .p-1 .text-blue-100 .fw-700}"
regex
>
>
| key | Description | Examples |
|:---:|:----------|:-|
| [property](#){: .p-1 .text-green-200 .fw-700} | any metadata that resources have. Using `actions` will give the Terraform's actions on the resource on an apply; `address` is the name you have given it | `address` `name` `actions` |
| " [regular expression](#){: .p-1 .text-blue-100 .fw-700} "| any regular expression with special characters escaped | `"module.project1.*"` |

------------------------
### [When](#){: .p-1 .text-red-200} its [property](#){: .p-1 .text-green-200 .fw-700} reference has [something](#){: .p-1 .text-blue-100 .fw-700}
This is a filtering function, specific for resource referencing. The resource references will be checked based on
Expand Down
15 changes: 10 additions & 5 deletions terraform_compliance/common/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,22 @@ class Null(object):


class Match(object):
def __init__(self, case_sensitive):
def __init__(self, case_sensitive, regex_flag=None):
self.case_sensitive = case_sensitive
self.regex_flag = regex_flag

def equals(self, left, right):
if not isinstance(left, (bool, int, float, str)) or not isinstance(right, (bool, int, float, str)):
raise TypeError

if self.case_sensitive:
return str(left) == str(right)
else:
return str(left).lower() == str(right).lower()
if self.regex_flag: # Regex enabled matches
re_flag = 0 if self.case_sensitive else re.IGNORECASE
return True if re.match(right, left, flags=re_flag) else False
else: # Regex disabled matches
if self.case_sensitive:
return str(left) == str(right)
else:
return str(left).lower() == str(right).lower()

# gets something from a dictionary
# needle == key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def handle_radish_errors(error_text):
},
{
'class': StepDefinitionNotFoundError,
'text': '{}. Looks like a syntax error.'.format(str(error_text).split('\n')[3])
'text': '{}. Looks like a syntax error.'.format(str(error_text).split('\n'))
},
]

Expand Down
6 changes: 3 additions & 3 deletions terraform_compliance/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ def cli(arghandling=ArgHandling(), argparser=ArgumentParser(prog=__app_name__,

try:
result = call_radish(args=commands[1:])
return result
except IndexError as e:
print(e)

return result
print("FATAL ERROR: It looks like there is a syntax error on your feature file.")
return


if __name__ == '__main__':
Expand Down
44 changes: 40 additions & 4 deletions terraform_compliance/steps/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@ def wrapper(_step_obj, name, type_name='resource', _terraform_config=world):
@when(u'its {key:PROPERTY} metadata contains {value:PROPERTY}')
@when(u'its {key:PROPERTY} metadata includes {value:PROPERTY}')
def wrapper(_step_obj, key, value):
return its_key_metadata_has_something(_step_obj, key, value)
return its_key_metadata_has_something(_step_obj, key, value, regex_match=False)

@when(u'its {key:PROPERTY} metadata has "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata is "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata contains "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata includes "{search_regex}" regex')
def wrapper(_step_obj, key, search_regex):
return its_key_metadata_has_something(_step_obj, key, search_regex, regex_match=True)

@when(u'its {key:PROPERTY} metadata has not {value:PROPERTY}')
@when(u'its {key:PROPERTY} metadata is not {value:PROPERTY}')
Expand All @@ -73,6 +79,13 @@ def wrapper(_step_obj, key, value):
def wrapper(_step_obj, key, value):
return its_key_metadata_has_something(_step_obj, key, value, has_step=False)

@when(u'its {key:PROPERTY} metadata has not "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata is not "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata does not have "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata does not contain "{search_regex}" regex')
@when(u'its {key:PROPERTY} metadata does not include "{search_regex}" regex')
def wrapper(_step_obj, key, search_regex):
return its_key_metadata_has_something(_step_obj, key, search_regex, has_step=False, regex_match=True)

@when(u'its {key:PROPERTY} is {value:PROPERTY}')
@when(u'its {key:PROPERTY} has {value:PROPERTY}')
Expand All @@ -84,7 +97,19 @@ def wrapper(_step_obj, key, value):
@when(u'its {address:PROPERTY} {key:PROPERTY} contains {value:PROPERTY}')
@when(u'its {key:PROPERTY} includes an entry where {value:PROPERTY} is {dict_value:PROPERTY}')
def wrapper(_step_obj, key, value, dict_value=None, address=Null):
return its_key_is_value(_step_obj, key, value, dict_value, address)
return its_key_is_value(_step_obj, key, value, dict_value, address, regex_match=False)

@when(u'its {key:PROPERTY} is "{search_regex}" regex')
@when(u'its {key:PROPERTY} has "{search_regex}" regex')
@when(u'its {key:PROPERTY} includes "{search_regex}" regex')
@when(u'its {key:PROPERTY} contains "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} is "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} has "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} includes "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} contains "{search_regex}" regex')
@when(u'its {key:PROPERTY} includes an entry where {value:PROPERTY} is "{dict_value}" regex')
def wrapper(_step_obj, key, search_regex, dict_value=None, address=Null):
return its_key_is_value(_step_obj, key, search_regex, dict_value, address, regex_match=True)


@when(u'its {key:PROPERTY} is not {value:PROPERTY}')
Expand All @@ -97,8 +122,19 @@ def wrapper(_step_obj, key, value, dict_value=None, address=Null):
@when(u'its {address:PROPERTY} {key:PROPERTY} does not contain {value:PROPERTY}')
@when(u'its {key:PROPERTY} does not include an entry where {value:PROPERTY} is {dict_value:PROPERTY}')
def wrapper(_step_obj, key, value, dict_value=None, address=Null):
return its_key_is_not_value(_step_obj, key, value, dict_value, address)

return its_key_is_not_value(_step_obj, key, value, dict_value, address, regex_match=False)

@when(u'its {key:PROPERTY} is not "{search_regex}" regex')
@when(u'its {key:PROPERTY} has not "{search_regex}" regex')
@when(u'its {key:PROPERTY} does not include "{search_regex}" regex')
@when(u'its {key:PROPERTY} does not contain "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} is not "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} has not "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} does not include "{search_regex}" regex')
@when(u'its {address:PROPERTY} {key:PROPERTY} does not contain "{search_regex}" regex')
@when(u'its {key:PROPERTY} does not include an entry where {value:PROPERTY} is "{dict_value}" regex')
def wrapper(_step_obj, key, search_regex, dict_value=None, address=Null):
return its_key_is_not_value(_step_obj, key, search_regex, dict_value, address, regex_match=True)

@when(u'it contain {something:PROPERTY}') # This is just here for not breaking backward compatibility. I know its wrong.
@when(u'it contains {something:PROPERTY}')
Expand Down
Loading

0 comments on commit 3a69925

Please sign in to comment.