-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Intrinsics support #1261
Intrinsics support #1261
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't review any of the actually intrinsic resolver functions. This PR is pretty big and has lots of logic. It may be work breaking this up into a short form to make this easier to review and consume.
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
samcli/commands/local/lib/intrinsic_resolver/intrinsic_property_resolver.py
Outdated
Show resolved
Hide resolved
""" | ||
processed_template = {} | ||
for key, val in self.resources.items(): | ||
processed_key = self.symbol_resolver.get_translation(key, IntrinsicResolver.REF) or key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntrinsicResolver.REF
isn't need here, get_translation
already defaults to this.
What does get_translation do here? It's not clear what the translation is referring too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_translation simplifies something like to RestApi.Deployment -> NewRestApi.
processed_key = self.symbol_resolver.get_translation(key, IntrinsicResolver.REF) or key | ||
try: | ||
processed_resource = self.intrinsic_property_resolver(val) | ||
processed_template[processed_key] = processed_resource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I follow what is going on here. Isn't processed_key
always equal to key
(aka. the logical id)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are cases when processed_key != key. The example in the SAM Ref code was RestApi.Deployment -> NewRestApi. This will be updated based on parameters to the logical_id_translator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really good. Love how deep you went in unit testing. Make understand and verification easier now and in the future.
------- | ||
An InvalidIntrinsicException | ||
""" | ||
raise InvalidIntrinsicException("Fn::ImportValue is currently not supported by IntrinsicResolver") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing we need to make sure is that we log debug messages in these cases (maybe in info level) so customers understand what we support or don't support.
Sidenote: We will not want to fail resolving intrinsics initially, or at least until we know we have all things covered correctly. Otherwise, this will become a blocker for customers when they try invoking/building/etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, I was handling for that in resolve_template() function. This has a property called ignore_errors, which we can keep on till we have covered everything. If ignore_errors is turned on, then it will run LOG.error("Unable to process properties of %s.%s", key, resource_type) and keep the current resource unmodified.
Ex Code around line 170:
except (InvalidIntrinsicException, InvalidSymbolException) as e:
resource_type = val.get("Type", "")
if ignore_errors:
LOG.error("Unable to process properties of %s.%s", key, resource_type)
processed_template[key] = val
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be helpful if these logs were only printed if the --debug flag is set, or if the log only printed if the property was one that should be possible to process but for whatever reasons couldn't be. Currently there is a lot of noise in terminal for unprocessed properties that SAM CLI can not process i.e. ImportValue
------- | ||
This resolves the attribute | ||
""" | ||
# pylint: disable-msg=too-many-return-statements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an indication that we should split this function out or find a better way to manage the control flows to reduce return statements (in some sane way).
function_name = logical_id | ||
function_name = self.logical_id_translator.get(function_name) or function_name | ||
str_format = "arn:{partition_name}:{service_name}:{aws_region}:{account_id}:{function_name}" | ||
if service_name == "lambda": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what other services have similar (custom) things in their arns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we look at a few other services? This function looks like it should be generic but has function_name
in all arns, so it reads a little off. Also, I have the general concern that Lambda isn't the only special arn one here. Keeping this updated with every service, could be a big pain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The arn types are
arn:partition:service:region:account-id:resource
arn:partition:service:region:account-id:resourcetype/resource
arn:partition:service:region:account-id:resourcetype/resource/qualifier
arn:partition:service:region:account-id:resourcetype/resource:qualifier
arn:partition:service:region:account-id:resourcetype:resource
arn:partition:service:region:account-id:resourcetype:resource:qualifier
The ones I looked at sns/sqs followed the default
str_format = "arn:{partition_name}:{service_name}:{aws_region}:{account_id}:{function_name}"
Before, we also only supported dealing with Lambda Functions. This function will also only be called as a default case if they do not specify and override for resolving GetAtt for Arns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine but then we shouldn't have the str_format
be tied to function_name
. My callout is an arn for sqs doesn't have a function_name, it has a queue_name. Since this is broad we need to make sure these reads as such. Reading it as it is written, makes it seems like lambda is only supported.
The json might get outdated and tools like cfn-lint are handling it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looking good.
Can we do better on covering unit tests coverage here?
samcli/lib/intrinsic_resolver/intrinsic_property_resolver.py 264 15 53 3 93% 83, 156, 169, 245, 247, 468, 488-501, 244->245, 246->247, 467->468
samcli/lib/intrinsic_resolver/intrinsics_symbol_table.py 115 12 32 6 86% 198, 204, 211-215, 271, 289, 337, 366, 378, 390, 197->198, 203->204, 208->213, 209->211, 242->245, 270->271
@@ -0,0 +1,71 @@ | |||
""" | |||
A custom Exception to display Invalid Intrinsics and Symbol Table format. | |||
It also has a list of helper functions that cleanup the processing in IntrinsicResolver and IntrinsicSymbolTable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should separate out the two concrete things that are in this file. "It also has" indicates this is not scoped. Or you can rename this file to be more encompassing of the exceptions and the validation methods.
self._symbol_resolver = symbol_resolver | ||
|
||
processed_template = {} | ||
for key, val in self._resources.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and any of the other _ properties (self._mapping
, self._parameters
, or self._conditions
) can result in an AttributeError due to the early exit if the template is None or empty (line 95). This is also why it's important not to ignore the attribute-defined-outside-init that pylint throws.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these properties are now initialized before calling this to prevent an AttributeError
template: dict | ||
A dictionary containing all the resources and the attributes of the CloudFormation template. The class does | ||
not mutate the template. | ||
ignore_errors: bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should ignore_errors
ignore any and all errors/exceptions or only certain ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the docstring to reflect that ignore_errors only ignores InvalidIntrinsicExceptions
|
||
processed_template = {} | ||
for key, val in self._resources.items(): | ||
processed_key = self._symbol_resolver.get_translation(key) or key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should self._symbol_resolver.get(key)
just default to returning the key back if one can't be found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I think we should continue to return None for self._symbol_resolver.get(key) is because we may want to differentiate between keys that don't exist and symbols that references to a logical_id in the future. This also makes it feel like dict.get(), which returns a default of None.
function_name = logical_id | ||
function_name = self.logical_id_translator.get(function_name) or function_name | ||
str_format = "arn:{partition_name}:{service_name}:{aws_region}:{account_id}:{function_name}" | ||
if service_name == "lambda": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we look at a few other services? This function looks like it should be generic but has function_name
in all arns, so it reads a little off. Also, I have the general concern that Lambda isn't the only special arn one here. Keeping this updated with every service, could be a big pain.
One general feedback is to submit smaller PRs, for eg: instrintic property resolver with one just one instrinsic tested, and to build on them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there! flake8 is failing, make sure you run it locally and address the failures.
|
||
CONDITIONAL_FUNCTIONS = [FN_AND, FN_OR, FN_IF, FN_EQUALS, FN_NOT] | ||
|
||
def __init__(self, template=None, symbol_resolver=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this require a template?
----------- | ||
ignore_errors: bool | ||
An option to ignore errors that are InvalidIntrinsicException and InvalidSymbolException | ||
symbol_resolver: IntrinsicSymbolTable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it a little strange that after creating the object, you would want to override this on the resolve itself. What is the reasoning behind this, vs the caller creating another object with the new symbol_resolver?
"Arn:": arn_resolver | ||
} | ||
""" | ||
if template is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't do anything be set template to self._template
so this check can be wrapped into that assignment.
function_name = logical_id | ||
function_name = self.logical_id_translator.get(function_name) or function_name | ||
str_format = "arn:{partition_name}:{service_name}:{aws_region}:{account_id}:{function_name}" | ||
if service_name == "lambda": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine but then we shouldn't have the str_format
be tied to function_name
. My callout is an arn for sqs doesn't have a function_name, it has a queue_name. Since this is broad we need to make sure these reads as such. Reading it as it is written, makes it seems like lambda is only supported.
…om:viksrivat/aws-sam-cli into feature/cloud_formation_intrinsics_support
@@ -0,0 +1,1347 @@ | |||
import json | |||
from copy import deepcopy | |||
from pathlib import Path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pathlib is not in the Python2.7 standard library. You will need to do
try:
import pathlib
except ImportError:
import pathlib2 as pathlib
to make the Py2.7 tests pass.
Issue #, if available:
#528
Description of changes:
#1260
Checklist:
make pr
passesBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.