Skip to content

Commit

Permalink
Allow aiodynamo to fetch credentials for snapstart lambdas.
Browse files Browse the repository at this point in the history
For snapstart, the lambda boots once to do any static init.
Subsequently, this snapshotted version is used for handling subsequent
requests.

Normally for lambdas, the AWS credentials are in the environment. Since
snapstart will reuse a snapshot of the running application, it can't
pass in variables in this way since they need to be refreshed.

Instead, the `AWS_CONTAINER_CREDENTIALS_FULL_URI` env var is set and the
`ContainerMetadataCredentials` in aiodynamo should be used to get
credentials if using `Credentials.auto()` to initialise.

Unfortunately, it looks like AWS formats the expiration timestamps
slightly differently for this API on lambda, including the microseconds
now. The `parse_amazon_timestamp` function fails to parse the value,
resulting in `ContainerMetadataCredentials` being rejected as a
`ChainCredentials` candidate and we end up with no credentials at all.

I can't really find a definitive documentation of what the format should
be so I can point to it, but obviously we know the code as it is works
on ECS/EC2 etc so we must continue to be able to parse those. I've
simply added a fallback to use microseconds if the string has a
full-stop in it.

It seems botocore is using the `dateutil` package to handle their
parsing:

https://github.com/boto/botocore/blob/f49ead849aa5a4ea428d9f378de14db6f4c6d645/botocore/utils.py#L950
  • Loading branch information
aclemons committed Dec 6, 2024
1 parent d6f136d commit 7674d74
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 4 deletions.
9 changes: 6 additions & 3 deletions src/aiodynamo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,12 @@ def serialize_dict(value: Mapping[str, Any]) -> Dict[str, Dict[str, Any]]:


def parse_amazon_timestamp(timestamp: str) -> datetime.datetime:
return datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ").replace(
tzinfo=datetime.timezone.utc
)
if "." in timestamp:
value = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
else:
value = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")

return value.replace(tzinfo=datetime.timezone.utc)


async def wait(
Expand Down
24 changes: 23 additions & 1 deletion tests/unit/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
import datetime
from decimal import Decimal
from functools import partial
from typing import Any, Callable, Dict
Expand All @@ -10,7 +11,28 @@
)

from aiodynamo.types import NumericTypeConverter
from aiodynamo.utils import deserialize, dy2py
from aiodynamo.utils import deserialize, dy2py, parse_amazon_timestamp


@pytest.mark.parametrize(
("amazon_timestamp", "expected"),
[
(
"2020-03-12T15:37:51Z",
datetime.datetime(2020, 3, 12, 15, 37, 51, tzinfo=datetime.timezone.utc),
),
(
"2024-12-06T08:03:52.192266Z",
datetime.datetime(
2024, 12, 6, 8, 3, 52, 192266, tzinfo=datetime.timezone.utc
),
),
],
)
def test_parse_amazon_timestamp(
amazon_timestamp: str, expected: datetime.datetime
) -> None:
assert parse_amazon_timestamp(amazon_timestamp) == expected


def test_binary_decode() -> None:
Expand Down

0 comments on commit 7674d74

Please sign in to comment.