Skip to content
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

Merged

Conversation

viksrivat
Copy link
Contributor

@viksrivat viksrivat commented Jul 9, 2019

Issue #, if available:
#528
Description of changes:
#1260

Checklist:

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@jfuss jfuss self-assigned this Jul 15, 2019
Copy link
Contributor

@jfuss jfuss left a 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.

"""
processed_template = {}
for key, val in self.resources.items():
processed_key = self.symbol_resolver.get_translation(key, IntrinsicResolver.REF) or key
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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)?

Copy link
Contributor Author

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.

Copy link
Contributor

@jfuss jfuss left a 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")
Copy link
Contributor

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.

Copy link
Contributor Author

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

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
Copy link
Contributor

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":
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@jfuss jfuss requested a review from sriram-mv July 30, 2019 22:51
Copy link
Contributor

@jfuss jfuss left a 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
Copy link
Contributor

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():
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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":
Copy link
Contributor

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.

samcli/lib/intrinsic_resolver/intrinsics_symbol_table.py Outdated Show resolved Hide resolved
@sriram-mv
Copy link
Contributor

One general feedback is to submit smaller PRs, for eg: instrintic property resolver with one just one instrinsic tested, and to build on them.

Copy link
Contributor

@jfuss jfuss left a 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):
Copy link
Contributor

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
Copy link
Contributor

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:
Copy link
Contributor

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":
Copy link
Contributor

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
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants