Skip to content

Commit

Permalink
Merge from aws/aws-sam-cli/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
aws-sam-cli-bot authored Dec 10, 2024
2 parents 6130371 + 400ed03 commit 92a3b19
Show file tree
Hide file tree
Showing 15 changed files with 965 additions and 415 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/update-reproducibles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
python: 3.11
target: update-reproducible-linux-reqs
- os: macos-latest
python: 3.8
python: 3.11
target: update-reproducible-mac-reqs
- os: windows-latest
python: 3.12
Expand Down
10 changes: 4 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,17 @@ schema:
# Verifications to run before sending a pull request
pr: init dev schema black-check

# (jfuss) We updated to have two requirement files, one for mac and one for linux. This
# is meant to be a short term fix when upgrading the Linux installer to be python3.11 from
# python3.7. Having different requirements is not ideal but this allows us to isolate changes
# giving us the ability to roll out upgrade to Linux first. When we update the MacOS installer
# we can move to a single file again.
# lucashuy: Linux and MacOS are on the same Python version,
# however we should follow up in a different change
# to consider combining these files again
update-reproducible-linux-reqs:
python3.11 -m venv venv-update-reproducible-linux
venv-update-reproducible-linux/bin/pip install --upgrade pip-tools pip
venv-update-reproducible-linux/bin/pip install -r requirements/base.txt
venv-update-reproducible-linux/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-linux.txt

update-reproducible-mac-reqs:
python3.8 -m venv venv-update-reproducible-mac
python3.11 -m venv venv-update-reproducible-mac
venv-update-reproducible-mac/bin/pip install --upgrade pip-tools pip
venv-update-reproducible-mac/bin/pip install -r requirements/base.txt
venv-update-reproducible-mac/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-mac.txt
Expand Down
4 changes: 2 additions & 2 deletions installer/pyinstaller/build-mac.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if [ "$openssl_version" = "" ]; then
fi

if [ "$python_version" = "" ]; then
python_version="3.8.20";
python_version="3.11.10";
fi

if ! [ "$build_binary_name" = "" ]; then
Expand Down Expand Up @@ -100,7 +100,7 @@ sudo make -j8 install
cd ..

echo "Installing Python Libraries"
/usr/local/bin/python3.8 -m venv venv
/usr/local/bin/python3.11 -m venv venv
./venv/bin/pip install --upgrade pip
./venv/bin/pip install -r src/requirements/reproducible-mac.txt

Expand Down
6 changes: 3 additions & 3 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
chevron~=0.12
click~=8.1
Flask<3.1
Flask<3.2
boto3>=1.29.2,<2
jmespath~=1.0.1
ruamel_yaml~=0.18.6
Expand All @@ -11,7 +11,7 @@ aws-sam-translator==1.94.0
docker~=7.1.0
dateparser~=1.2
requests~=2.32.3
aws_lambda_builders==1.52.0
aws_lambda_builders==1.53.0
tomlkit==0.13.2
# NOTE: For supporting watchdog in Python3.8, version is pinned to 4.0.2 as
# version 5.0.2 introduced some breaking changes for versions > Python3.8
Expand All @@ -31,7 +31,7 @@ regex!=2021.10.8
tzlocal==5.2

#Adding cfn-lint dependency for SAM validate
cfn-lint~=1.19.0
cfn-lint~=1.20.1

# Type checking boto3 objects
boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray,sqs,kinesis]==1.35.63
8 changes: 4 additions & 4 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-r pre-dev.txt

coverage==7.6.7; python_version>="3.9"
coverage==7.6.8; python_version>="3.9"
coverage==7.6.1; python_version<"3.9"
pytest-cov==6.0.0; python_version>="3.9"
pytest-cov==5.0.0; python_version<"3.9"
Expand All @@ -10,15 +10,15 @@ pytest-cov==5.0.0; python_version<"3.9"
# mypy adds new rules in new minor versions, which could cause our PR check to fail
# here we fix its version and upgrade it manually in the future
mypy==1.13.0
types-pywin32==308.0.0.20241029
types-pywin32==308.0.0.20241128
types-PyYAML==6.0.12.20240917
types-chevron==0.14.2.20240310
types-psutil==6.1.0.20241102
types-setuptools==75.5.0.20241116
types-setuptools==75.6.0.20241126
types-Pygments==2.18.0.20240506
types-colorama==0.4.15.20240311
types-dateparser==1.2.0.20240420
types-docutils==0.21.0.20241005
types-docutils==0.21.0.20241128
types-jsonschema==4.23.0.20240813
types-pyOpenSSL==24.1.0.20240722
# lucashuy: pin `types-request` based on the Python version since newer versions of
Expand Down
2 changes: 1 addition & 1 deletion requirements/pre-dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruff==0.7.4
ruff==0.8.0
239 changes: 125 additions & 114 deletions requirements/reproducible-linux.txt

Large diffs are not rendered by default.

276 changes: 123 additions & 153 deletions requirements/reproducible-mac.txt

Large diffs are not rendered by default.

239 changes: 125 additions & 114 deletions requirements/reproducible-win.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion samcli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
SAM CLI version
"""

__version__ = "1.131.0"
__version__ = "1.132.0"
130 changes: 116 additions & 14 deletions samcli/cli/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
LOG = logging.getLogger(__name__)


def _generate_match_regex(match_pattern, delim):
def _generate_match_regex(match_pattern, delim=None):
"""
Creates a regex string based on a match pattern (also a regex) that is to be
run on a string (which may contain escaped quotes) that is separated by delimiters.
Expand All @@ -32,13 +32,13 @@ def _generate_match_regex(match_pattern, delim):
str: regex expression
"""
result = f"""(\\"(?:\\\\{match_pattern}|[^\\"\\\\]+)*\\"|""" + f"""\'(?:\\\\{match_pattern}|[^\'\\\\]+)*\'"""

# Non capturing groups reduces duplicates in groups, but does not reduce matches.
return (
f"""(\\"(?:\\\\{match_pattern}|[^\\"\\\\]+)*\\"|"""
+ f"""\'(?:\\\\{match_pattern}|[^\'\\\\]+)*\'|"""
+ f"""(?:\\\\{match_pattern}|[^{delim}\\"\\\\]+)+)"""
)
if delim is not None:
# Non capturing groups reduces duplicates in groups, but does not reduce matches.
return result + f"""|(?:\\\\{match_pattern}|[^{delim}\\"\\\\]+)+)"""
else:
return result + ")"


def _unquote_wrapped_quotes(value):
Expand Down Expand Up @@ -194,6 +194,7 @@ def __init__(self, multiple_values_per_key=False):
TAG_REGEX = '[A-Za-z0-9\\"_:\\.\\/\\+-\\@=]'

_pattern = r"{tag}={tag}".format(tag=_generate_match_regex(match_pattern=TAG_REGEX, delim=" "))
_quoted_pattern = _generate_match_regex(match_pattern=TAG_REGEX)

name = "string,list"

Expand Down Expand Up @@ -222,13 +223,7 @@ def convert(self, value, param, ctx):
for k in tags:
self._add_value(result, _unquote_wrapped_quotes(k), _unquote_wrapped_quotes(tags[k]))
else:
groups = re.findall(self._pattern, val)

if not groups:
fail = True
for group in groups:
key, v = group
self._add_value(result, _unquote_wrapped_quotes(key), _unquote_wrapped_quotes(v))
fail = not self._parse_key_value_pair(result, val)

if fail:
return self.fail(
Expand All @@ -239,6 +234,66 @@ def convert(self, value, param, ctx):

return result

def _parse_key_value_pair(self, result: dict, key_value_string: str):
"""
This method processes a string in the format "'key1'='value1','key2'='value2'",
where spaces may exist within keys or values.
To optimize performance, the parsing is divided into two stages:
Stage 1: Optimized Parsing
1. Identify quoted strings containing spaces within values.
2. Temporarily replace spaces in these strings with a placeholder (e.g., "_").
3. Use a fast, standard parser to extract key-value pairs, as no spaces are expected.
4. Restore original spaces in the extracted key-value pairs.
Stage 2: Fallback Parsing
If Stage 1 fails to parse the string correctly,run against a comprehensive regex pattern
{tag}={tag}) to parse the entire string.
Parameters
----------
result: result dict
key_value_string: string to parse
Returns
-------
boolean - parse result
"""
parse_result = True

# Unquote an entire string
modified_val = _unquote_wrapped_quotes(key_value_string)

# Looking for a quote strings that contain spaces and proceed to replace them
quoted_strings_with_spaces = re.findall(self._quoted_pattern, modified_val)
quoted_strings_with_spaces_objects = [
TextWithSpaces(str_with_spaces) for str_with_spaces in quoted_strings_with_spaces
]
for s, replacement in zip(quoted_strings_with_spaces, quoted_strings_with_spaces_objects):
modified_val = modified_val.replace(s, replacement.replace_spaces())

# Use default parser to parse key=value
tags = self._multiple_space_separated_key_value_parser(modified_val)
if tags is not None:
for key, value in tags.items():
new_value = value
text_objects = [obj for obj in quoted_strings_with_spaces_objects if obj.modified_text == value]
if len(text_objects) > 0:
new_value = text_objects[0].restore_spaces()
self._add_value(result, _unquote_wrapped_quotes(key), _unquote_wrapped_quotes(new_value))
else:
# Otherwise, fall back to the original mechanism.
groups = re.findall(self._pattern, key_value_string)

if not groups:
parse_result = False
for group in groups:
key, v = group
self._add_value(result, _unquote_wrapped_quotes(key), _unquote_wrapped_quotes(v))

return parse_result

def _add_value(self, result: dict, key: str, new_value: str):
"""
Add a given value to a given key in the result map.
Expand Down Expand Up @@ -286,6 +341,22 @@ def _space_separated_key_value_parser(tag_value):
tags_dict = {**tags_dict, **parsed_tag}
return True, tags_dict

@staticmethod
def _multiple_space_separated_key_value_parser(tag_value):
"""
Method to parse space separated `Key1=Value1 Key2=Value2` type tags without using regex.
Parameters
----------
tag_value
"""
tags_dict = {}
for value in tag_value.split():
parsed, parsed_tag = CfnTags._standard_key_value_parser(value)
if not parsed:
return None
tags_dict.update(parsed_tag)
return tags_dict


class SigningProfilesOptionType(click.ParamType):
"""
Expand Down Expand Up @@ -560,3 +631,34 @@ def convert(
)

return {resource_id: [excluded_path]}


class TextWithSpaces:
def __init__(self, text) -> None:
self.text = text
self.modified_text = text
self.space_positions = [] # type: List[int]

def replace_spaces(self, replacement="_"):
"""
Replace spaces in a text with a replacement together with its original locations.
Input: "test 1"
Output: "test_1" [4]
"""
self.space_positions = [i for i, char in enumerate(self.text) if char == " "]
self.modified_text = self.text.replace(" ", replacement)

return self.modified_text

def restore_spaces(self):
"""
Restore spaces in a text from a original space locations.
Input: "test_1" [4]
Output: "test 1"
"""
text_list = list(self.modified_text)

for pos in self.space_positions:
text_list[pos] = " "

return "".join(text_list)
4 changes: 3 additions & 1 deletion samcli/commands/_utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,8 +849,10 @@ def resolve_image_repos_option(f):

def use_container_build_click_option():
return click.option(
"--use-container",
"--use-container/--no-use-container",
"-u",
required=False,
default=False,
is_flag=True,
help="Build functions within an AWS Lambda-like container.",
)
Expand Down
2 changes: 1 addition & 1 deletion samcli/runtime_config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"app_template_repo_commit": "e9255c4b848b523ca903e5ee0fbd28d52f2a4c4e"
"app_template_repo_commit": "4801f61b2288fa85f2e06cf5c1a8d8dbe22094cb"
}
8 changes: 8 additions & 0 deletions tests/unit/cli/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,14 @@ def test_must_fail_on_invalid_format(self, input):
["stage=int", "company:application=awesome-service", "company:department=engineering"],
{"stage": "int", "company:application": "awesome-service", "company:department": "engineering"},
),
# input as string with multiple key-values including spaces
(('tag1="son of anton" tag2="company abc"',), {"tag1": "son of anton", "tag2": "company abc"}),
(('tag1="son of anton" tag2="company abc"',), {"tag1": "son of anton", "tag2": "company abc"}),
(('\'tag1="son of anton" tag2="company abc"\'',), {"tag1": "son of anton", "tag2": "company abc"}),
(
('tag1="son of anton" tag2="company abc" tag:3="dummy tag"',),
{"tag1": "son of anton", "tag2": "company abc", "tag:3": "dummy tag"},
),
]
)
def test_successful_parsing(self, input, expected):
Expand Down
Loading

0 comments on commit 92a3b19

Please sign in to comment.