diff --git a/.gitignore b/.gitignore index c9bc162..71904e8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ /aws-cli-2.17.13.tar.gz /aws-cli-2.17.18.tar.gz /aws-cli-2.22.9.tar.gz +/aws-cli-2.27.0.tar.gz diff --git a/0001-Bump-the-ceiling-for-botocore-memory-leak-tests-to-1.patch b/0001-Bump-the-ceiling-for-botocore-memory-leak-tests-to-1.patch index 140ef2e..6560c5d 100644 --- a/0001-Bump-the-ceiling-for-botocore-memory-leak-tests-to-1.patch +++ b/0001-Bump-the-ceiling-for-botocore-memory-leak-tests-to-1.patch @@ -2,7 +2,7 @@ From 4b5762bb17f172d9f9e058df8908651856ff4a69 Mon Sep 17 00:00:00 2001 From: Adam Williamson Date: Thu, 13 Jun 2024 10:33:42 -0700 Subject: [PATCH] Bump the ceiling for botocore memory leak tests to 15 MiB - + See https://github.com/boto/botocore/issues/3205 for the background on this. In rebuilding awscli2 for Python 3.13 in Fedora Rawhide, we found that two of these tests fail because @@ -12,12 +12,12 @@ a memory *leak*, so bumping the ceiling seems appropriate. I'm sending this upstream so I have a reference for the downstream package and to raise awareness of the issue, but the correct fix may be something else. - + Signed-off-by: Adam Williamson --- tests/functional/botocore/leak/test_resource_leaks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) - + diff --git a/tests/functional/botocore/leak/test_resource_leaks.py b/tests/functional/botocore/leak/test_resource_leaks.py index df1c4fe7f..73027725f 100644 --- a/tests/functional/botocore/leak/test_resource_leaks.py @@ -35,4 +35,4 @@ index df1c4fe7f..73027725f 100644 self.cmd('create_client', 's3') -- 2.45.2 - + diff --git a/assertions.patch b/assertions.patch deleted file mode 100644 index 3545005..0000000 --- a/assertions.patch +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/tests/functional/eks/test_kubeconfig.py b/tests/functional/eks/test_kubeconfig.py -index 3d1bcf8..687eef2 100644 ---- a/tests/functional/eks/test_kubeconfig.py -+++ b/tests/functional/eks/test_kubeconfig.py -@@ -121,8 +121,9 @@ class TestKubeconfigLoader(unittest.TestCase): - ]) - loaded_config = self._loader.load_kubeconfig(simple_path) - self.assertEqual(loaded_config.content, content) -- self._validator.validate_config.called_with(Kubeconfig(simple_path, -- content)) -+ validated_config = self._validator.validate_config.call_args.args[0] -+ self.assertEqual(validated_config.path, simple_path) -+ self.assertEqual(validated_config.content, content) - - def test_load_noexist(self): - no_exist_path = os.path.join(self._temp_directory, -@@ -130,17 +131,18 @@ class TestKubeconfigLoader(unittest.TestCase): - loaded_config = self._loader.load_kubeconfig(no_exist_path) - self.assertEqual(loaded_config.content, - _get_new_kubeconfig_content()) -- self._validator.validate_config.called_with( -- Kubeconfig(no_exist_path, _get_new_kubeconfig_content())) -+ validated_config = self._validator.validate_config.call_args.args[0] -+ self.assertEqual(validated_config.path, no_exist_path) -+ self.assertEqual(validated_config.content, _get_new_kubeconfig_content()) - - def test_load_empty(self): - empty_path = self._clone_config("valid_empty_existing") - loaded_config = self._loader.load_kubeconfig(empty_path) - self.assertEqual(loaded_config.content, - _get_new_kubeconfig_content()) -- self._validator.validate_config.called_with( -- Kubeconfig(empty_path, -- _get_new_kubeconfig_content())) -+ validated_config = self._validator.validate_config.call_args.args[0] -+ self.assertEqual(validated_config.path, empty_path) -+ self.assertEqual(validated_config.content, _get_new_kubeconfig_content()) - - def test_load_directory(self): - current_directory = self._temp_directory diff --git a/awscli2.spec b/awscli2.spec index 0012bf7..d8869c3 100644 --- a/awscli2.spec +++ b/awscli2.spec @@ -1,18 +1,17 @@ %global pkgname aws-cli - + Name: awscli2 -Version: 2.22.9 +Version: 2.27.0 Release: %autorelease - Summary: Universal Command Line Environment for AWS, version 2 # all files are licensed under Apache-2.0, except: # - awscli/topictags.py is MIT # - awscli/botocore/vendored/six.py is MIT License: Apache-2.0 AND MIT URL: https://github.com/aws/aws-cli/tree/v2 - + Source0: https://github.com/aws/aws-cli/archive/%{version}/%{pkgname}-%{version}.tar.gz - + # adapt to whitespace formatting changes and removal of OrderedDict in ruamel-yaml Patch0: ruamel-yaml-0.17.32.patch # fix Python 3.12 incompatibilities @@ -21,51 +20,61 @@ Patch1: python312.patch # https://github.com/aws/aws-cli/pull/8744 # https://github.com/boto/botocore/issues/3205 Patch2: 0001-Bump-the-ceiling-for-botocore-memory-leak-tests-to-1.patch - +# compatibility fixes for urllib3 v2 +Patch3: urllib3-v2.patch +# fix Python 3.14 incompatibilities +Patch4: python314.patch + + BuildArch: noarch - + BuildRequires: python%{python3_pkgversion}-devel BuildRequires: python-unversioned-command BuildRequires: procps-ng - + Recommends: groff -Recommends: less - + Provides: bundled(python3dist(botocore)) = 2.0.0 Provides: bundled(python3dist(s3transfer)) = 0.5.1 - + Provides: awscli = %{version}-%{release} Obsoletes: awscli < 2 - + # provide an upgrade path from awscli-2 (Amazon Linux) Provides: awscli-2 = %{version}-%{release} Obsoletes: awscli-2 < %{version}-%{release} - -# python-awscrt does not build on s390x -ExcludeArch: s390x - - + +# python-awscrt does not build on i686 nor s390x +ExcludeArch: %{ix86} s390x + + %description This package provides version 2 of the unified command line interface to Amazon Web Services. - - + + %prep %autosetup -p1 -n %{pkgname}-%{version} - + # fix permissions find awscli/examples/ -type f -name '*.rst' -executable -exec chmod -x '{}' + - + # remove version caps on dependencies sed -i 's/,<=\?[^"]*"/"/' pyproject.toml - + +# loosen awscrt version requirement +sed -i 's/awscrt==/awscrt>=/' pyproject.toml + +# remove zipp dependency +sed -i "/zipp<3.21.0/d" pyproject.toml + # use unittest.mock find -type f -name '*.py' -exec sed \ -e 's/^\( *\)import mock$/\1from unittest import mock/' \ -e 's/^\( *\)from mock import mock/\1from unittest import mock/' \ -e 's/^\( *\)from mock import/\1from unittest.mock import/' \ -i '{}' + - + # Fedora does not run coverage tests. # mock is deprecated in Fedora. We use unittest.mock. # pip-tools is not used directly by the unit tests. @@ -78,44 +87,44 @@ sed \ -e '/pytest-cov/d' \ %{?rhel:-e '/pytest-xdist/d'} \ requirements-test.txt > _requirements-test.txt - - + + %generate_buildrequires %pyproject_buildrequires _requirements-test.txt - - + + %build %pyproject_wheel - - + + %install %pyproject_install %pyproject_save_files awscli - + # remove unnecessary scripts rm -vf %{buildroot}%{_bindir}/{aws_bash_completer,aws_zsh_completer.sh,aws.cmd} - + # install shell completion install -Dpm0644 bin/aws_bash_completer \ %{buildroot}%{bash_completions_dir}/aws install -Dpm0644 bin/aws_zsh_completer.sh \ %{buildroot}%{zsh_completions_dir}/_awscli - - + + %check # it appears that some tests modify the environment and remove PYTHONPATH # so it's not passed to botocore cmd-runner, inject it here sed -i '/self.driver.start(env=env)/i \ \ \ \ \ \ \ \ env["PYTHONPATH"] = "%{buildroot}%{python3_sitelib}"' \ tests/utils/botocore/__init__.py - + export TESTS_REMOVE_REPO_ROOT_FROM_PATH=1 TZ=UTC export OPENSSL_ENABLE_SHA1_SIGNATURES=yes %pytest --verbose %{!?rhel:--numprocesses=auto --dist=loadfile --maxprocesses=4} \ tests/unit tests/functional \ --ignore tests/functional/autocomplete/test_completion_files.py \ --ignore tests/functional/botocore/test_waiter_config.py - - + + %files -f %{pyproject_files} %license LICENSE.txt %doc README.rst @@ -123,7 +132,7 @@ export OPENSSL_ENABLE_SHA1_SIGNATURES=yes %{_bindir}/aws_completer %{bash_completions_dir}/aws %{zsh_completions_dir}/_awscli - - + + %changelog %autochangelog diff --git a/python312.patch b/python312.patch index bd2c9a6..4d05888 100644 --- a/python312.patch +++ b/python312.patch @@ -1,8 +1,8 @@ diff --git a/awscli/botocore/auth.py b/awscli/botocore/auth.py -index 0c1bc74a..de33e127 100644 +index 19fbe36..efb1b02 100644 --- a/awscli/botocore/auth.py +++ b/awscli/botocore/auth.py -@@ -395,7 +395,7 @@ class SigV4Auth(BaseSigner): +@@ -419,7 +419,7 @@ class SigV4Auth(BaseSigner): def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() @@ -11,16 +11,16 @@ index 0c1bc74a..de33e127 100644 request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) # This could be a retry. Make sure the previous # authorization header is removed first. -@@ -439,7 +439,7 @@ class SigV4Auth(BaseSigner): +@@ -465,7 +465,7 @@ class SigV4Auth(BaseSigner): if 'Date' in request.headers: del request.headers['Date'] datetime_timestamp = datetime.datetime.strptime( -- request.context['timestamp'], SIGV4_TIMESTAMP) -+ request.context['timestamp'], SIGV4_TIMESTAMP[:-1] + '%z') +- request.context['timestamp'], SIGV4_TIMESTAMP ++ request.context['timestamp'], SIGV4_TIMESTAMP[:-1] + '%z' + ) request.headers['Date'] = formatdate( - int(calendar.timegm(datetime_timestamp.timetuple()))) - if 'X-Amz-Date' in request.headers: -@@ -527,7 +527,7 @@ class S3ExpressPostAuth(S3ExpressAuth): + int(calendar.timegm(datetime_timestamp.timetuple())) +@@ -557,7 +557,7 @@ class S3ExpressPostAuth(S3ExpressAuth): REQUIRES_IDENTITY_CACHE = True def add_auth(self, request): @@ -29,9 +29,9 @@ index 0c1bc74a..de33e127 100644 request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) fields = {} -@@ -780,7 +780,7 @@ class S3SigV4PostAuth(SigV4Auth): - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html +@@ -818,7 +818,7 @@ class S3SigV4PostAuth(SigV4Auth): """ + def add_auth(self, request): - datetime_now = datetime.datetime.utcnow() + datetime_now = datetime.datetime.now(datetime.timezone.utc) @@ -39,38 +39,40 @@ index 0c1bc74a..de33e127 100644 fields = {} diff --git a/awscli/botocore/crt/auth.py b/awscli/botocore/crt/auth.py -index 534a7f8d..5046b35b 100644 +index 2b96c7e..86104f3 100644 --- a/awscli/botocore/crt/auth.py +++ b/awscli/botocore/crt/auth.py -@@ -55,10 +55,7 @@ class CrtSigV4Auth(BaseSigner): +@@ -56,11 +56,7 @@ class CrtSigV4Auth(BaseSigner): if self.credentials is None: raise NoCredentialsError() - # Use utcnow() because that's what gets mocked by tests, but set - # timezone because CRT assumes naive datetime is local time. - datetime_now = datetime.datetime.utcnow().replace( -- tzinfo=datetime.timezone.utc) +- tzinfo=datetime.timezone.utc +- ) + datetime_now = datetime.datetime.now(datetime.timezone.utc) # Use existing 'X-Amz-Content-SHA256' header if able existing_sha256 = self._get_existing_sha256(request) -@@ -245,10 +242,7 @@ class CrtSigV4AsymAuth(BaseSigner): +@@ -254,11 +250,7 @@ class CrtSigV4AsymAuth(BaseSigner): if self.credentials is None: raise NoCredentialsError() - # Use utcnow() because that's what gets mocked by tests, but set - # timezone because CRT assumes naive datetime is local time. - datetime_now = datetime.datetime.utcnow().replace( -- tzinfo=datetime.timezone.utc) +- tzinfo=datetime.timezone.utc +- ) + datetime_now = datetime.datetime.now(datetime.timezone.utc) # Use existing 'X-Amz-Content-SHA256' header if able existing_sha256 = self._get_existing_sha256(request) diff --git a/awscli/botocore/signers.py b/awscli/botocore/signers.py -index 604f6553..6c55277e 100644 +index 02e759b..5e894ae 100644 --- a/awscli/botocore/signers.py +++ b/awscli/botocore/signers.py -@@ -549,7 +549,7 @@ class S3PostPresigner(object): +@@ -713,7 +713,7 @@ class S3PostPresigner: policy = {} # Create an expiration date for the policy @@ -80,10 +82,10 @@ index 604f6553..6c55277e 100644 policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601) diff --git a/awscli/botocore/utils.py b/awscli/botocore/utils.py -index 89bcc2aa..8d3688c5 100644 +index 78efaba..9d7cd1d 100644 --- a/awscli/botocore/utils.py +++ b/awscli/botocore/utils.py -@@ -582,13 +582,13 @@ class InstanceMetadataFetcher(IMDSFetcher): +@@ -608,7 +608,7 @@ class InstanceMetadataFetcher(IMDSFetcher): return try: expiration = datetime.datetime.strptime( @@ -92,62 +94,63 @@ index 89bcc2aa..8d3688c5 100644 ) refresh_interval = self._config.get( "ec2_credential_refresh_window", 60 * 10 +@@ -616,7 +616,7 @@ class InstanceMetadataFetcher(IMDSFetcher): + refresh_interval_with_jitter = refresh_interval + random.randint( + 120, 600 ) - refresh_interval_with_jitter = refresh_interval + random.randint(120, 600) - current_time = datetime.datetime.utcnow() + current_time = datetime.datetime.now(datetime.timezone.utc) - refresh_offset = datetime.timedelta(seconds=refresh_interval_with_jitter) - extension_time = expiration - refresh_offset - if current_time >= extension_time: + refresh_offset = datetime.timedelta( + seconds=refresh_interval_with_jitter + ) diff --git a/awscli/compat.py b/awscli/compat.py -index b6ae8981..a41c4c6b 100644 +index e1d58bd..cd496d3 100644 --- a/awscli/compat.py +++ b/awscli/compat.py -@@ -29,6 +29,8 @@ from functools import partial +@@ -24,6 +24,7 @@ import shlex + import signal + import sys import urllib.parse as urlparse - from urllib.error import URLError - -+import queue +import urllib.request - from botocore.compat import six - from botocore.compat import OrderedDict - + import zipfile + from configparser import RawConfigParser + from functools import partial diff --git a/awscli/customizations/cloudformation/deployer.py b/awscli/customizations/cloudformation/deployer.py -index 3733c55e..8236d33c 100644 +index a5c512f..8783ebb 100644 --- a/awscli/customizations/cloudformation/deployer.py +++ b/awscli/customizations/cloudformation/deployer.py -@@ -20,7 +20,7 @@ import collections - from awscli.customizations.cloudformation import exceptions - from awscli.customizations.cloudformation.artifact_exporter import mktempfile, parse_s3_url - +@@ -15,7 +15,7 @@ import collections + import logging + import sys + import time -from datetime import datetime +from datetime import datetime, timezone - LOG = logging.getLogger(__name__) + import botocore -@@ -85,7 +85,7 @@ class Deployer(object): +@@ -98,7 +98,7 @@ class Deployer: :return: """ - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() - description = "Created by AWS CLI at {0} UTC".format(now) + description = f"Created by AWS CLI at {now} UTC" # Each changeset will get a unique name based on time diff --git a/awscli/customizations/cloudtrail/validation.py b/awscli/customizations/cloudtrail/validation.py -index 78e25408..ad135077 100644 +index f4229ba..1aa960c 100644 --- a/awscli/customizations/cloudtrail/validation.py +++ b/awscli/customizations/cloudtrail/validation.py -@@ -19,7 +19,7 @@ import re +@@ -18,7 +18,7 @@ import logging + import re import sys import zlib - from zlib import error as ZLibError -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone - from dateutil import tz, parser + from zlib import error as ZLibError - import cryptography -@@ -401,7 +401,7 @@ class DigestTraverser(object): + from awscrt.crypto import RSA, RSASignatureAlgorithm +@@ -444,7 +444,7 @@ class DigestTraverser: :param end_date: Date to stop validating at (inclusive). """ if end_date is None: @@ -156,20 +159,20 @@ index 78e25408..ad135077 100644 end_date = normalize_date(end_date) start_date = normalize_date(start_date) bucket = self.starting_bucket -@@ -703,7 +703,7 @@ class CloudTrailValidateLogs(BasicCommand): +@@ -830,7 +830,7 @@ class CloudTrailValidateLogs(BasicCommand): if args.end_time: self.end_time = normalize_date(parse_date(args.end_time)) else: - self.end_time = normalize_date(datetime.utcnow()) -+ self.end_time = datetime.now(timezone.utc) ++ self.end_time = normalize_date(datetime.now(timezone.utc)) if self.start_time > self.end_time: raise ParamValidationError( 'Invalid time range specified: start-time must ' diff --git a/awscli/customizations/codecommit.py b/awscli/customizations/codecommit.py -index 6b30e834..7859fb89 100644 +index b001562..cb9e757 100644 --- a/awscli/customizations/codecommit.py +++ b/awscli/customizations/codecommit.py -@@ -150,7 +150,7 @@ class CodeCommitGetCommand(BasicCommand): +@@ -159,7 +159,7 @@ class CodeCommitGetCommand(BasicCommand): request = AWSRequest() request.url = url_to_sign request.method = 'GIT' @@ -179,29 +182,29 @@ index 6b30e834..7859fb89 100644 split = urlsplit(request.url) # we don't want to include the port number in the signature diff --git a/awscli/customizations/codedeploy/push.py b/awscli/customizations/codedeploy/push.py -index 4e0fbcff..9f71f7c2 100644 +index bdde82e..3281ce4 100644 --- a/awscli/customizations/codedeploy/push.py +++ b/awscli/customizations/codedeploy/push.py -@@ -16,7 +16,7 @@ import sys - import zipfile +@@ -16,7 +16,7 @@ import os + import sys import tempfile - import contextlib + import zipfile -from datetime import datetime +from datetime import datetime, timezone from botocore.exceptions import ClientError -@@ -132,7 +132,7 @@ class Push(BasicCommand): +@@ -131,7 +131,7 @@ class Push(BasicCommand): + ) if not parsed_args.description: parsed_args.description = ( - 'Uploaded by AWS CLI {0} UTC'.format( -- datetime.utcnow().isoformat() -+ datetime.now(timezone.utc).isoformat() - ) +- f'Uploaded by AWS CLI {datetime.utcnow().isoformat()} UTC' ++ f'Uploaded by AWS CLI {datetime.now(timezone.utc).isoformat()} UTC' ) + def _push(self, params): diff --git a/awscli/customizations/datapipeline/__init__.py b/awscli/customizations/datapipeline/__init__.py -index c47ca94f..0c12c394 100644 +index 5665769..d197339 100644 --- a/awscli/customizations/datapipeline/__init__.py +++ b/awscli/customizations/datapipeline/__init__.py @@ -12,7 +12,7 @@ @@ -211,10 +214,10 @@ index c47ca94f..0c12c394 100644 -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone - from awscli.formatter import get_formatter from awscli.arguments import CustomArgument -@@ -186,7 +186,7 @@ class QueryArgBuilder(object): - """ + from awscli.customizations.commands import BasicCommand +@@ -197,7 +197,7 @@ class QueryArgBuilder: + def __init__(self, current_time=None): if current_time is None: - current_time = datetime.utcnow() @@ -223,32 +226,32 @@ index c47ca94f..0c12c394 100644 def build_query(self, parsed_args): diff --git a/awscli/customizations/ec2/bundleinstance.py b/awscli/customizations/ec2/bundleinstance.py -index cc6802d6..56c1efa6 100644 +index 240540f..8dc2e22 100644 --- a/awscli/customizations/ec2/bundleinstance.py +++ b/awscli/customizations/ec2/bundleinstance.py -@@ -118,7 +118,7 @@ def _generate_policy(params): +@@ -129,7 +129,7 @@ def _generate_policy(params): # Called if there is no policy supplied by the user. # Creates a policy that provides access for 24 hours. delta = datetime.timedelta(hours=24) - expires = datetime.datetime.utcnow() + delta + expires = datetime.datetime.now(datetime.timezone.utc) + delta expires_iso = expires.strftime("%Y-%m-%dT%H:%M:%S.%fZ") - policy = POLICY.format(expires=expires_iso, - bucket=params['Bucket'], + policy = POLICY.format( + expires=expires_iso, bucket=params['Bucket'], prefix=params['Prefix'] diff --git a/awscli/customizations/eks/get_token.py b/awscli/customizations/eks/get_token.py -index c85b86dd..9812be4f 100644 +index 00fc4d3..ee8c74c 100644 --- a/awscli/customizations/eks/get_token.py +++ b/awscli/customizations/eks/get_token.py -@@ -16,7 +16,7 @@ import json +@@ -14,7 +14,7 @@ import base64 + import json import os import sys - -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone - from botocore.signers import RequestSigner - from botocore.model import ServiceId -@@ -106,7 +106,7 @@ class GetTokenCommand(BasicCommand): + import botocore + from botocore.model import ServiceId +@@ -105,7 +105,7 @@ class GetTokenCommand(BasicCommand): ] def get_expiration_time(self): @@ -258,20 +261,24 @@ index c85b86dd..9812be4f 100644 ) return token_expiration.strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/awscli/customizations/logs/tail.py b/awscli/customizations/logs/tail.py -index cb315100..623e0272 100644 +index e22facd..9b464b4 100644 --- a/awscli/customizations/logs/tail.py +++ b/awscli/customizations/logs/tail.py -@@ -11,7 +11,8 @@ +@@ -10,11 +10,12 @@ + # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - from collections import defaultdict --from datetime import datetime, timedelta -+from datetime import datetime, timedelta, timezone +import functools import json import re import time -@@ -261,7 +262,7 @@ class TimestampUtils(object): + from collections import defaultdict +-from datetime import datetime, timedelta ++from datetime import datetime, timedelta, timezone + + import colorama + from botocore.utils import datetime2timestamp, parse_timestamp +@@ -266,7 +267,7 @@ class TimestampUtils: def __init__(self, now=None): self._now = now if now is None: @@ -281,10 +288,10 @@ index cb315100..623e0272 100644 def to_epoch_millis(self, timestamp): re_match = self._RELATIVE_TIMESTAMP_REGEX.match(timestamp) diff --git a/awscli/customizations/opsworks.py b/awscli/customizations/opsworks.py -index e91d4789..9f848c64 100644 +index ac23cd5..490f44d 100644 --- a/awscli/customizations/opsworks.py +++ b/awscli/customizations/opsworks.py -@@ -507,7 +507,7 @@ class OpsWorksRegister(BasicCommand): +@@ -566,7 +566,7 @@ class OpsWorksRegister(BasicCommand): "Resource": arn, } if timeout is not None: @@ -292,23 +299,23 @@ index e91d4789..9f848c64 100644 + valid_until = datetime.datetime.now(datetime.timezone.utc) + timeout statement["Condition"] = { "DateLessThan": { - "aws:CurrentTime": + "aws:CurrentTime": valid_until.strftime( diff --git a/tests/functional/botocore/test_credentials.py b/tests/functional/botocore/test_credentials.py -index 18bd248d..8af69de4 100644 +index 7703df6..1497e11 100644 --- a/tests/functional/botocore/test_credentials.py +++ b/tests/functional/botocore/test_credentials.py -@@ -18,7 +18,7 @@ import math +@@ -19,7 +19,7 @@ import tempfile + import threading import time - import tempfile - import shutil + import uuid -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone - import sys import pytest -@@ -46,8 +46,8 @@ from botocore.stub import Stubber - from botocore.tokens import SSOTokenProvider - from botocore.utils import datetime2timestamp + from botocore import UNSIGNED +@@ -61,8 +61,8 @@ from tests import ( + unittest, + ) -TIME_IN_ONE_HOUR = datetime.utcnow() + timedelta(hours=1) -TIME_IN_SIX_MONTHS = datetime.utcnow() + timedelta(hours=4320) @@ -318,27 +325,28 @@ index 18bd248d..8af69de4 100644 class TestCredentialRefreshRaces(unittest.TestCase): diff --git a/tests/functional/botocore/test_ec2.py b/tests/functional/botocore/test_ec2.py -index a5aec4aa..475134cc 100644 +index 81f4b76..39423aa 100644 --- a/tests/functional/botocore/test_ec2.py +++ b/tests/functional/botocore/test_ec2.py -@@ -85,13 +85,13 @@ class TestCopySnapshotCustomization(BaseSessionTest): +@@ -92,14 +92,14 @@ class TestCopySnapshotCustomization(BaseSessionTest): '%s\n' '\n' ) - self.now = datetime.datetime(2011, 9, 9, 23, 36) + self.now = datetime.datetime(2011, 9, 9, 23, 36, tzinfo=datetime.timezone.utc) self.datetime_patch = mock.patch.object( - botocore.auth.datetime, 'datetime', - mock.Mock(wraps=datetime.datetime) + botocore.auth.datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime), ) self.mocked_datetime = self.datetime_patch.start() - self.mocked_datetime.utcnow.return_value = self.now + self.mocked_datetime.now.return_value = self.now def tearDown(self): - super(TestCopySnapshotCustomization, self).tearDown() + super().tearDown() diff --git a/tests/functional/botocore/test_lex.py b/tests/functional/botocore/test_lex.py -index 659296fd..614691e8 100644 +index cded9c0..000f50d 100644 --- a/tests/functional/botocore/test_lex.py +++ b/tests/functional/botocore/test_lex.py @@ -10,7 +10,7 @@ @@ -348,10 +356,10 @@ index 659296fd..614691e8 100644 -from datetime import datetime +from datetime import datetime, timezone - from tests import mock, BaseSessionTest, ClientHTTPStubber + from tests import BaseSessionTest, ClientHTTPStubber, mock @@ -31,10 +31,10 @@ class TestLex(BaseSessionTest): - 'inputStream': b'' + 'inputStream': b'', } - timestamp = datetime(2017, 3, 22, 0, 0) @@ -364,7 +372,7 @@ index 659296fd..614691e8 100644 with self.http_stubber: self.client.post_content(**params) diff --git a/tests/functional/botocore/test_s3express.py b/tests/functional/botocore/test_s3express.py -index 390721ee..ebb89b0b 100644 +index 91ec532..1a516d2 100644 --- a/tests/functional/botocore/test_s3express.py +++ b/tests/functional/botocore/test_s3express.py @@ -108,7 +108,6 @@ class TestS3ExpressAuth: @@ -425,19 +433,19 @@ index 390721ee..ebb89b0b 100644 with ClientHTTPStubber(default_s3_client) as stubber: diff --git a/tests/functional/botocore/test_sts.py b/tests/functional/botocore/test_sts.py -index beb48030..e6978795 100644 +index 6bf343e..407ebab 100644 --- a/tests/functional/botocore/test_sts.py +++ b/tests/functional/botocore/test_sts.py -@@ -10,7 +10,7 @@ - # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +@@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. + import re -from datetime import datetime +from datetime import datetime, timezone - import re - from tests import BaseSessionTest -@@ -36,9 +36,9 @@ class TestSTSPresignedUrl(BaseSessionTest): + from botocore.config import Config + from botocore.stub import Stubber +@@ -32,9 +32,9 @@ class TestSTSPresignedUrl(BaseSessionTest): self.stubber.activate() def test_presigned_url_contains_no_content_type(self): @@ -449,8 +457,39 @@ index beb48030..e6978795 100644 url = self.client.generate_presigned_url('get_caller_identity', {}) # There should be no 'content-type' in x-amz-signedheaders +diff --git a/tests/functional/dsql/test_generate_db_auth_token.py b/tests/functional/dsql/test_generate_db_auth_token.py +index 2b48453..39871ab 100644 +--- a/tests/functional/dsql/test_generate_db_auth_token.py ++++ b/tests/functional/dsql/test_generate_db_auth_token.py +@@ -50,7 +50,7 @@ class TestGenerateDBConnectAuthToken(BaseTestGenerateDBConnectAuthToken): + clock = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=tzutc()) + + with mock.patch('datetime.datetime') as dt: +- dt.utcnow.return_value = clock ++ dt.now.return_value = clock + stdout, _, _ = self.run_cmd(command, expected_rc=0) + + # Expected hashes are always the same as session variables come from the BaseAwsCommandParamsTest class +@@ -79,7 +79,7 @@ class TestGenerateDBConnectAuthToken(BaseTestGenerateDBConnectAuthToken): + clock = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=tzutc()) + + with mock.patch('datetime.datetime') as dt: +- dt.utcnow.return_value = clock ++ dt.now.return_value = clock + stdout, _, _ = self.run_cmd(command, expected_rc=252) + + +@@ -92,7 +92,7 @@ class TestGenerateDBConnectAdminAuthToken(BaseTestGenerateDBConnectAuthToken): + clock = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=tzutc()) + + with mock.patch('datetime.datetime') as dt: +- dt.utcnow.return_value = clock ++ dt.now.return_value = clock + stdout, _, _ = self.run_cmd(command, expected_rc=0) + + # Expected hashes are always the same as session variables come from the BaseAwsCommandParamsTest class diff --git a/tests/functional/ec2/test_bundle_instance.py b/tests/functional/ec2/test_bundle_instance.py -index 1c485d88..d5037fe0 100644 +index 5442929..06747f9 100644 --- a/tests/functional/ec2/test_bundle_instance.py +++ b/tests/functional/ec2/test_bundle_instance.py @@ -31,7 +31,7 @@ class TestBundleInstance(BaseAWSCommandParamsTest): @@ -462,8 +501,8 @@ index 1c485d88..d5037fe0 100644 # returns the same datetime object. This is because this value # is embedded into the policy file that is generated and we # don't what the policy or its signature to change each time -@@ -41,7 +41,7 @@ class TestBundleInstance(BaseAWSCommandParamsTest): - mock.Mock(wraps=datetime.datetime) +@@ -42,7 +42,7 @@ class TestBundleInstance(BaseAWSCommandParamsTest): + mock.Mock(wraps=datetime.datetime), ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime(2013, 8, 9) @@ -472,10 +511,10 @@ index 1c485d88..d5037fe0 100644 def tearDown(self): super(TestBundleInstance, self).tearDown() diff --git a/tests/functional/ec2instanceconnect/test_opentunnel.py b/tests/functional/ec2instanceconnect/test_opentunnel.py -index 83f824d2..ddefc47f 100644 +index cd85b01..e83bf78 100644 --- a/tests/functional/ec2instanceconnect/test_opentunnel.py +++ b/tests/functional/ec2instanceconnect/test_opentunnel.py -@@ -310,10 +310,10 @@ def request_params_for_describe_eice(): +@@ -359,10 +359,10 @@ def request_params_for_describe_eice(): @pytest.fixture @@ -488,29 +527,29 @@ index 83f824d2..ddefc47f 100644 yield dt -@@ -393,7 +393,7 @@ class TestOpenTunnel: - describe_eice_response, - request_params_for_describe_instance, - request_params_for_describe_eice, -- datetime_utcnow_patch, -+ datetime_now_patch, +@@ -445,7 +445,7 @@ class TestOpenTunnel: + describe_eice_response, + request_params_for_describe_instance, + request_params_for_describe_eice, +- datetime_utcnow_patch, ++ datetime_now_patch, ): cli_runner.env["AWS_USE_FIPS_ENDPOINT"] = "false" cmdline = [ diff --git a/tests/functional/eks/test_get_token.py b/tests/functional/eks/test_get_token.py -index 89801f9b..cdf51f7b 100644 +index 0e78f9a..5600bdf 100644 --- a/tests/functional/eks/test_get_token.py +++ b/tests/functional/eks/test_get_token.py -@@ -11,7 +11,7 @@ - # ANY KIND, either express or implied. See the License for the specific - # language governing permissions and limitations under the License. +@@ -13,7 +13,7 @@ import base64 --from datetime import datetime -+from datetime import datetime, timezone import json import os +-from datetime import datetime ++from datetime import datetime, timezone -@@ -80,7 +80,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): + from awscli.compat import urlparse + from awscli.testutils import BaseAWSCommandParamsTest, mock +@@ -78,7 +78,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_get_token(self, mock_datetime): @@ -519,7 +558,7 @@ index 89801f9b..cdf51f7b 100644 cmd = 'eks get-token --cluster-name %s' % self.cluster_name response = self.run_get_token(cmd) self.assertEqual( -@@ -98,7 +98,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): +@@ -96,7 +96,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_query_nested_object(self, mock_datetime): @@ -528,7 +567,7 @@ index 89801f9b..cdf51f7b 100644 cmd = 'eks get-token --cluster-name %s' % self.cluster_name cmd += ' --query status' response = self.run_get_token(cmd) -@@ -120,7 +120,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): +@@ -119,7 +119,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_output_text(self, mock_datetime): @@ -537,7 +576,7 @@ index 89801f9b..cdf51f7b 100644 cmd = 'eks get-token --cluster-name %s' % self.cluster_name cmd += ' --output text' stdout, _, _ = self.run_cmd(cmd) -@@ -130,7 +130,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): +@@ -129,7 +129,7 @@ class TestGetTokenCommand(BaseAWSCommandParamsTest): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_output_table(self, mock_datetime): @@ -547,32 +586,32 @@ index 89801f9b..cdf51f7b 100644 cmd += ' --output table' stdout, _, _ = self.run_cmd(cmd) diff --git a/tests/functional/logs/test_tail.py b/tests/functional/logs/test_tail.py -index 6049a7f8..5707da93 100644 +index 0024bd4..8eaf61c 100644 --- a/tests/functional/logs/test_tail.py +++ b/tests/functional/logs/test_tail.py -@@ -152,7 +152,7 @@ class TestTailCommand(BaseAWSCommandParamsTest): +@@ -155,7 +155,7 @@ class TestTailCommand(BaseAWSCommandParamsTest): def test_tail_defaults_to_10m(self): datetime_mock = mock.Mock(wraps=datetime) - datetime_mock.utcnow = mock.Mock( + datetime_mock.now = mock.Mock( - return_value=datetime(1970, 1, 1, 0, 10, 1, tzinfo=tz.tzutc())) - with mock.patch('awscli.customizations.logs.tail.datetime', - new=datetime_mock): -@@ -177,7 +177,7 @@ class TestTailCommand(BaseAWSCommandParamsTest): + return_value=datetime(1970, 1, 1, 0, 10, 1, tzinfo=tz.tzutc()) + ) + with mock.patch( +@@ -182,7 +182,7 @@ class TestTailCommand(BaseAWSCommandParamsTest): def test_tail_with_relative_since(self): datetime_mock = mock.Mock(wraps=datetime) - datetime_mock.utcnow = mock.Mock( + datetime_mock.now = mock.Mock( - return_value=datetime(1970, 1, 1, 0, 0, 2, tzinfo=tz.tzutc())) - with mock.patch('awscli.customizations.logs.tail.datetime', - new=datetime_mock): + return_value=datetime(1970, 1, 1, 0, 0, 2, tzinfo=tz.tzutc()) + ) + with mock.patch( diff --git a/tests/functional/rds/test_generate_db_auth_token.py b/tests/functional/rds/test_generate_db_auth_token.py -index 79634ed0..1008ba39 100644 +index 4cc562a..4ff83e1 100644 --- a/tests/functional/rds/test_generate_db_auth_token.py +++ b/tests/functional/rds/test_generate_db_auth_token.py -@@ -51,7 +51,7 @@ class TestGenerateDBAuthToken(BaseAWSCommandParamsTest): +@@ -50,7 +50,7 @@ class TestGenerateDBAuthToken(BaseAWSCommandParamsTest): clock = datetime.datetime(2016, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: @@ -582,12 +621,12 @@ index 79634ed0..1008ba39 100644 expected = ( diff --git a/tests/functional/s3/test_presign_command.py b/tests/functional/s3/test_presign_command.py -index 2db338a0..03741d19 100644 +index 43ede9a..d315dd3 100644 --- a/tests/functional/s3/test_presign_command.py +++ b/tests/functional/s3/test_presign_command.py -@@ -18,13 +18,13 @@ from awscli.testutils import BaseAWSCommandParamsTest, mock, temporary_file - from awscli.testutils import create_clidriver - +@@ -22,13 +22,13 @@ from awscli.testutils import ( + temporary_file, + ) -# Values used to fix time.time() and datetime.datetime.utcnow() +# Values used to fix time.time() and datetime.datetime.now() @@ -596,12 +635,12 @@ index 2db338a0..03741d19 100644 DEFAULT_EXPIRES = 3600 FROZEN_TIME = mock.Mock(return_value=FROZEN_TIMESTAMP) FROZEN_DATETIME = mock.Mock( -- return_value=datetime.datetime(2016, 8, 18, 14, 33, 3, 0)) -+ return_value=datetime.datetime(2016, 8, 18, 14, 33, 3, 0, tzinfo=datetime.timezone.utc)) +- return_value=datetime.datetime(2016, 8, 18, 14, 33, 3, 0) ++ return_value=datetime.datetime(2016, 8, 18, 14, 33, 3, 0, tzinfo=datetime.timezone.utc) + ) - class TestPresignCommand(BaseAWSCommandParamsTest): -@@ -78,7 +78,7 @@ class TestPresignCommand(BaseAWSCommandParamsTest): +@@ -76,7 +76,7 @@ class TestPresignCommand(BaseAWSCommandParamsTest): def get_presigned_url_for_cmd(self, cmdline): with mock.patch('time.time', FROZEN_TIME): with mock.patch('datetime.datetime') as d: @@ -611,19 +650,19 @@ index 2db338a0..03741d19 100644 return stdout diff --git a/tests/integration/customizations/test_codecommit.py b/tests/integration/customizations/test_codecommit.py -index 7ffbed65..25c78faf 100644 +index 7077c08..d904290 100644 --- a/tests/integration/customizations/test_codecommit.py +++ b/tests/integration/customizations/test_codecommit.py -@@ -14,7 +14,7 @@ - import awscli - import os +@@ -12,7 +12,7 @@ + # language governing permissions and limitations under the License. + import os -from datetime import datetime +from datetime import datetime, timezone - from awscli.compat import StringIO - from botocore.session import Session -@@ -59,7 +59,7 @@ class TestCodeCommitCredentialHelper(unittest.TestCase): + from botocore.awsrequest import AWSRequest + from botocore.credentials import Credentials +@@ -64,7 +64,7 @@ class TestCodeCommitCredentialHelper(unittest.TestCase): @mock.patch('sys.stdout', new_callable=StringIOWithFileNo) @mock.patch.object(awscli.customizations.codecommit.datetime, 'datetime') def test_integration_using_cli_driver(self, dt_mock, stdout_mock): @@ -632,7 +671,7 @@ index 7ffbed65..25c78faf 100644 driver = create_clidriver() entry_point = AWSCLIEntryPoint(driver) rc = entry_point.main('codecommit credential-helper get'.split()) -@@ -75,7 +75,7 @@ class TestCodeCommitCredentialHelper(unittest.TestCase): +@@ -83,7 +83,7 @@ class TestCodeCommitCredentialHelper(unittest.TestCase): @mock.patch('sys.stdout', new_callable=StringIOWithFileNo) @mock.patch.object(awscli.customizations.codecommit.datetime, 'datetime') def test_integration_fips_using_cli_driver(self, dt_mock, stdout_mock): @@ -641,7 +680,7 @@ index 7ffbed65..25c78faf 100644 driver = create_clidriver() entry_point = AWSCLIEntryPoint(driver) rc = entry_point.main('codecommit credential-helper get'.split()) -@@ -91,7 +91,7 @@ class TestCodeCommitCredentialHelper(unittest.TestCase): +@@ -102,7 +102,7 @@ class TestCodeCommitCredentialHelper(unittest.TestCase): @mock.patch('sys.stdout', new_callable=StringIOWithFileNo) @mock.patch.object(awscli.customizations.codecommit.datetime, 'datetime') def test_integration_vpc_using_cli_driver(self, dt_mock, stdout_mock): @@ -651,10 +690,10 @@ index 7ffbed65..25c78faf 100644 entry_point = AWSCLIEntryPoint(driver) rc = entry_point.main('codecommit credential-helper get'.split()) diff --git a/tests/unit/botocore/auth/test_signers.py b/tests/unit/botocore/auth/test_signers.py -index 19d559ac..e7db8d8b 100644 +index 7effaf3..ff62c77 100644 --- a/tests/unit/botocore/auth/test_signers.py +++ b/tests/unit/botocore/auth/test_signers.py -@@ -27,10 +27,10 @@ from botocore.awsrequest import AWSRequest +@@ -28,10 +28,10 @@ from tests import mock, unittest class BaseTestWithFixedDate(unittest.TestCase): def setUp(self): @@ -667,10 +706,10 @@ index 19d559ac..e7db8d8b 100644 self.datetime_mock.strptime.return_value = self.fixed_date def tearDown(self): -@@ -357,9 +357,9 @@ class TestSigV4(unittest.TestCase): - with mock.patch.object( - botocore.auth.datetime, 'datetime', - mock.Mock(wraps=datetime.datetime)) as mock_datetime: +@@ -379,9 +379,9 @@ class TestSigV4(unittest.TestCase): + 'datetime', + mock.Mock(wraps=datetime.datetime), + ) as mock_datetime: - original_utcnow = datetime.datetime(2014, 1, 1, 0, 0) + original_now = datetime.datetime(2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) @@ -679,53 +718,54 @@ index 19d559ac..e7db8d8b 100644 # Go through the add_auth process once. This will attach # a timestamp to the request at the beginning of auth. auth.add_auth(request) -@@ -367,8 +367,8 @@ class TestSigV4(unittest.TestCase): +@@ -389,9 +389,8 @@ class TestSigV4(unittest.TestCase): # Ensure the date is in the Authorization header self.assertIn('20140101', request.headers['Authorization']) # Now suppose the utc time becomes the next day all of a sudden - mock_datetime.utcnow.return_value = datetime.datetime( -- 2014, 1, 2, 0, 0) +- 2014, 1, 2, 0, 0 +- ) + mock_datetime.now.return_value = datetime.datetime( + 2014, 1, 2, 0, 0, tzinfo=datetime.timezone.utc) # Smaller methods like the canonical request and string_to_sign # should have the timestamp attached to the request in their # body and not what the time is now mocked as. This is to ensure -@@ -534,8 +534,8 @@ class TestSigV4Presign(BasePresignTest): - mock.Mock(wraps=datetime.datetime) +@@ -563,8 +562,8 @@ class TestSigV4Presign(BasePresignTest): + mock.Mock(wraps=datetime.datetime), ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime( -- 2014, 1, 1, 0, 0) +- 2014, 1, 1, 0, 0 + mocked_datetime.now.return_value = datetime.datetime( -+ 2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) ++ 2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc + ) def tearDown(self): - self.datetime_patcher.stop() -@@ -729,8 +729,8 @@ class TestS3SigV4Post(BaseS3PresignPostTest): - mock.Mock(wraps=datetime.datetime) +@@ -780,8 +779,8 @@ class TestS3SigV4Post(BaseS3PresignPostTest): + mock.Mock(wraps=datetime.datetime), ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime( -- 2014, 1, 1, 0, 0) +- 2014, 1, 1, 0, 0 + mocked_datetime.now.return_value = datetime.datetime( -+ 2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) ++ 2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc + ) def tearDown(self): - self.datetime_patcher.stop() diff --git a/tests/unit/botocore/test_credentials.py b/tests/unit/botocore/test_credentials.py -index b9931216..7fdcf4ba 100644 +index feb35d0..70f2b47 100644 --- a/tests/unit/botocore/test_credentials.py +++ b/tests/unit/botocore/test_credentials.py -@@ -11,7 +11,7 @@ - # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF - # ANY KIND, either express or implied. See the License for the specific - # language governing permissions and limitations under the License. +@@ -16,7 +16,7 @@ import os + import shutil + import subprocess + import tempfile -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone - import subprocess - import os - import tempfile -@@ -110,7 +110,7 @@ class TestRefreshableCredentials(TestCredentials): + + import botocore.exceptions + import botocore.session +@@ -125,7 +125,7 @@ class TestRefreshableCredentials(TestCredentials): def test_refresh_needed(self): # The expiry time was set for 30 minutes ago, so if we @@ -734,7 +774,7 @@ index b9931216..7fdcf4ba 100644 # a refresh. self.mock_time.return_value = datetime.now(tzlocal()) self.assertTrue(self.creds.refresh_needed()) -@@ -290,8 +290,8 @@ class TestAssumeRoleCredentialFetcher(BaseEnvVar): +@@ -313,8 +313,8 @@ class TestAssumeRoleCredentialFetcher(BaseEnvVar): self.assertEqual(response, expected_response) def test_retrieves_from_cache(self): @@ -742,10 +782,10 @@ index b9931216..7fdcf4ba 100644 - utc_timestamp = date_in_future.isoformat() + 'Z' + date_in_future = datetime.now(timezone.utc) + timedelta(seconds=1000) + utc_timestamp = date_in_future.isoformat() - cache_key = ( - '793d6e2f27667ab2da104824407e486bfec24a47' - ) -@@ -702,8 +702,8 @@ class TestAssumeRoleWithWebIdentityCredentialFetcher(BaseEnvVar): + cache_key = '793d6e2f27667ab2da104824407e486bfec24a47' + cache = { + cache_key: { +@@ -793,8 +793,8 @@ class TestAssumeRoleWithWebIdentityCredentialFetcher(BaseEnvVar): self.assertEqual(response, expected_response) def test_retrieves_from_cache(self): @@ -753,10 +793,10 @@ index b9931216..7fdcf4ba 100644 - utc_timestamp = date_in_future.isoformat() + 'Z' + date_in_future = datetime.now(timezone.utc) + timedelta(seconds=1000) + utc_timestamp = date_in_future.isoformat() - cache_key = ( - '793d6e2f27667ab2da104824407e486bfec24a47' - ) -@@ -822,8 +822,8 @@ class TestAssumeRoleWithWebIdentityCredentialProvider(unittest.TestCase): + cache_key = '793d6e2f27667ab2da104824407e486bfec24a47' + cache = { + cache_key: { +@@ -958,8 +958,8 @@ class TestAssumeRoleWithWebIdentityCredentialProvider(unittest.TestCase): mock_loader_cls.assert_called_with('/some/path/token.jwt') def test_assume_role_retrieves_from_cache(self): @@ -765,9 +805,9 @@ index b9931216..7fdcf4ba 100644 + date_in_future = datetime.now(timezone.utc) + timedelta(seconds=1000) + utc_timestamp = date_in_future.isoformat() - cache_key = ( - 'c29461feeacfbed43017d20612606ff76abc073d' -@@ -1960,8 +1960,8 @@ class TestAssumeRoleCredentialProvider(unittest.TestCase): + cache_key = 'c29461feeacfbed43017d20612606ff76abc073d' + cache = { +@@ -2199,8 +2199,8 @@ class TestAssumeRoleCredentialProvider(unittest.TestCase): self.assertEqual(expiry_time, '2016-11-06T01:30:00UTC') def test_assume_role_retrieves_from_cache(self): @@ -777,8 +817,8 @@ index b9931216..7fdcf4ba 100644 + utc_timestamp = date_in_future.isoformat() self.fake_config['profiles']['development']['role_arn'] = 'myrole' - cache_key = ( -@@ -1988,8 +1988,8 @@ class TestAssumeRoleCredentialProvider(unittest.TestCase): + cache_key = '793d6e2f27667ab2da104824407e486bfec24a47' +@@ -2228,8 +2228,8 @@ class TestAssumeRoleCredentialProvider(unittest.TestCase): self.assertEqual(creds.token, 'baz-cached') def test_chain_prefers_cache(self): @@ -790,10 +830,10 @@ index b9931216..7fdcf4ba 100644 # The profile we will be using has a cache entry, but the profile it # is sourcing from does not. This should result in the cached diff --git a/tests/unit/botocore/test_signers.py b/tests/unit/botocore/test_signers.py -index a38d1b59..b0840d54 100644 +index b97bf58..ed54e9a 100644 --- a/tests/unit/botocore/test_signers.py +++ b/tests/unit/botocore/test_signers.py -@@ -607,9 +607,9 @@ class TestS3PostPresigner(BaseSignerTest): +@@ -686,9 +686,9 @@ class TestS3PostPresigner(BaseSignerTest): self.datetime_patch = mock.patch('botocore.signers.datetime') self.datetime_mock = self.datetime_patch.start() @@ -805,70 +845,70 @@ index a38d1b59..b0840d54 100644 self.datetime_mock.timedelta.return_value = self.fixed_delta def tearDown(self): -@@ -1004,7 +1004,7 @@ class TestGenerateDBAuthToken(BaseSignerTest): +@@ -1141,7 +1141,7 @@ class TestGenerateDBAuthToken(BaseSignerTest): clock = datetime.datetime(2016, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock result = generate_db_auth_token( - self.client, hostname, port, username) - + self.client, hostname, port, username + ) diff --git a/tests/unit/botocore/test_utils.py b/tests/unit/botocore/test_utils.py -index b4699c6c..2d128bf1 100644 +index 7538f02..f9579a3 100644 --- a/tests/unit/botocore/test_utils.py +++ b/tests/unit/botocore/test_utils.py -@@ -98,7 +98,7 @@ from botocore.stub import Stubber - from botocore.config import Config - from botocore.endpoint_provider import RuleSetEndpoint +@@ -102,7 +102,7 @@ from dateutil.tz import tzoffset, tzutc + + from tests import FreezeTime, RawResponse, create_session, mock, unittest -DATE = datetime.datetime(2021, 12, 10, 00, 00, 00) +DATE = datetime.datetime(2021, 12, 10, 00, 00, 00, tzinfo=datetime.timezone.utc) DT_FORMAT = "%Y-%m-%dT%H:%M:%SZ" -@@ -2913,7 +2913,7 @@ class TestInstanceMetadataFetcher(unittest.TestCase): - self, dt=None, offset=None, offset_func=operator.add - ): +@@ -3116,7 +3116,7 @@ class TestInstanceMetadataFetcher(unittest.TestCase): + + def _get_datetime(self, dt=None, offset=None, offset_func=operator.add): if dt is None: - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(datetime.timezone.utc) if offset is not None: dt = offset_func(dt, offset) - diff --git a/tests/unit/customizations/eks/test_get_token.py b/tests/unit/customizations/eks/test_get_token.py -index 9575aa0d..2664e1fe 100644 +index 7f830b3..0f019c3 100644 --- a/tests/unit/customizations/eks/test_get_token.py +++ b/tests/unit/customizations/eks/test_get_token.py -@@ -46,6 +46,6 @@ class TestGetTokenCommand(BaseTokenTest): +@@ -48,7 +48,7 @@ class TestGetTokenCommand(BaseTokenTest): cmd = GetTokenCommand(self._session) timestamp = cmd.get_expiration_time() try: - datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ') + datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S%z') except ValueError: - raise ValueError("Incorrect data format, should be %Y-%m-%dT%H:%M:%SZ") + raise ValueError( + "Incorrect data format, should be %Y-%m-%dT%H:%M:%SZ" diff --git a/tests/unit/customizations/test_opsworks.py b/tests/unit/customizations/test_opsworks.py -index d5a6eba3..bad37a90 100644 +index 0712d63..951ad31 100644 --- a/tests/unit/customizations/test_opsworks.py +++ b/tests/unit/customizations/test_opsworks.py -@@ -33,8 +33,8 @@ class TestOpsWorksBase(unittest.TestCase): - mock.Mock(wraps=datetime.datetime) +@@ -32,8 +32,8 @@ class TestOpsWorksBase(unittest.TestCase): + opsworks.datetime, "datetime", mock.Mock(wraps=datetime.datetime) ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime( -- 2013, 8, 9, 23, 42) +- 2013, 8, 9, 23, 42 + mocked_datetime.now.return_value = datetime.datetime( -+ 2013, 8, 9, 23, 42, tzinfo=datetime.timezone.utc) ++ 2013, 8, 9, 23, 42, tzinfo=datetime.timezone.utc + ) def tearDown(self): - self.datetime_patcher.stop() diff --git a/tests/utils/botocore/__init__.py b/tests/utils/botocore/__init__.py -index 106736f3..c76288b5 100644 +index bbe6a59..dddaf26 100644 --- a/tests/utils/botocore/__init__.py +++ b/tests/utils/botocore/__init__.py -@@ -559,12 +559,12 @@ class FreezeTime(contextlib.ContextDecorator): +@@ -579,12 +579,12 @@ class FreezeTime(contextlib.ContextDecorator): :param module: reference to imported module to patch (e.g. botocore.auth.datetime) :type date: datetime.datetime @@ -882,8 +922,8 @@ index 106736f3..c76288b5 100644 + date = datetime.datetime.now(datetime.timezone.utc) self.date = date self.datetime_patcher = mock.patch.object( - module, 'datetime', -@@ -573,7 +573,7 @@ class FreezeTime(contextlib.ContextDecorator): + module, 'datetime', mock.Mock(wraps=datetime.datetime) +@@ -592,7 +592,7 @@ class FreezeTime(contextlib.ContextDecorator): def __enter__(self, *args, **kwargs): mock = self.datetime_patcher.start() diff --git a/python314.patch b/python314.patch new file mode 100644 index 0000000..d2e92d6 --- /dev/null +++ b/python314.patch @@ -0,0 +1,139 @@ +diff --git a/awscli/arguments.py b/awscli/arguments.py +index 77639cf..3ab060b 100644 +--- a/awscli/arguments.py ++++ b/awscli/arguments.py +@@ -451,7 +451,7 @@ class CLIArgument(BaseCLIArgument): + cli_name = self.cli_name + parser.add_argument( + cli_name, +- help=self.documentation, ++ help=self.documentation.replace("%", "%%"), + type=self.cli_type, + required=self.required, + ) +@@ -602,7 +602,7 @@ class BooleanArgument(CLIArgument): + def add_to_parser(self, parser): + parser.add_argument( + self.cli_name, +- help=self.documentation, ++ help=self.documentation.replace("%", "%%"), + action=self._action, + default=self._default, + dest=self._destination, +diff --git a/awscli/customizations/logs/startlivetail.py b/awscli/customizations/logs/startlivetail.py +index cc5849b..bc71a3d 100644 +--- a/awscli/customizations/logs/startlivetail.py ++++ b/awscli/customizations/logs/startlivetail.py +@@ -833,7 +833,7 @@ class InteractiveUI(BaseLiveTailUI): + await self._application.run_async() + + def run(self): +- asyncio.get_event_loop().run_until_complete(self._run_ui()) ++ asyncio.run(self._run_ui()) + + + class PrintOnlyPrinter(BaseLiveTailPrinter): +diff --git a/awscli/customizations/wizard/app.py b/awscli/customizations/wizard/app.py +index c813b97..028fcaa 100644 +--- a/awscli/customizations/wizard/app.py ++++ b/awscli/customizations/wizard/app.py +@@ -10,9 +10,9 @@ + # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + # ANY KIND, either express or implied. See the License for the specific + # language governing permissions and limitations under the License. ++import asyncio + import json + import os +-from asyncio import new_event_loop, set_event_loop + from collections.abc import MutableMapping + + from prompt_toolkit.application import Application +@@ -77,14 +77,12 @@ class WizardApp(Application): + ) + + def run(self, pre_run=None, **kwargs): +- loop = new_event_loop() +- try: +- set_event_loop(loop) ++ def create_event_loop(): ++ loop = asyncio.new_event_loop() + loop.set_exception_handler(self._handle_exception) +- f = self.run_async(pre_run=pre_run, set_exception_handler=False) +- return loop.run_until_complete(f) +- finally: +- loop.close() ++ return loop ++ f = self.run_async(pre_run=pre_run, set_exception_handler=False) ++ return asyncio.run(f, loop_factory=create_event_loop) + + def _handle_exception(self, loop, context): + self.exit(exception=UnexpectedWizardException(context['exception'])) +diff --git a/tests/__init__.py b/tests/__init__.py +index db70912..0ab2e4a 100644 +--- a/tests/__init__.py ++++ b/tests/__init__.py +@@ -471,16 +471,9 @@ class PromptToolkitAppRunner: + # This is the function that will be passed to our thread to + # actually run the application + try: +- # When we run the app in a separate thread, there is no +- # default event loop. This ensures we create one as it is +- # likely the application will try to grab the default loop +- loop = asyncio.new_event_loop() +- asyncio.set_event_loop(loop) + app_run_context.return_value = target(*target_args) + except BaseException as e: + app_run_context.raised_exception = e +- finally: +- loop.close() + + def _wait_until_app_is_done_updating(self): + self._wait_until_app_is_done_rendering() +diff --git a/tests/functional/test_utils.py b/tests/functional/test_utils.py +--- a/tests/functional/test_utils.py ++++ b/tests/functional/test_utils.py +@@ -18,7 +18,7 @@ class TestWriteException(unittest.TestCa + def test_write_exception(self): + error_message = "Some error message." + ex = Exception(error_message) +- with codecs.open(self.outfile, 'w+', encoding='utf-8') as outfile: ++ with open(self.outfile, 'w+', encoding='utf-8') as outfile: + write_exception(ex, outfile) + outfile.seek(0) + +diff --git a/tests/unit/botocore/test_utils.py b/tests/unit/botocore/test_utils.py +--- a/tests/unit/botocore/test_utils.py ++++ b/tests/unit/botocore/test_utils.py +@@ -3646,20 +3646,28 @@ def test_lru_cache_weakref(): + cls2 = ClassWithCachedMethod() + + assert cls1.cached_fn.cache_info().currsize == 0 +- assert getrefcount(cls1) == 2 +- assert getrefcount(cls2) == 2 ++ refcount1 = getrefcount(cls1) ++ refcount2 = getrefcount(cls2) ++ assert refcount1 in (1, 2) ++ assert refcount2 in (1, 2) + # "The count returned is generally one higher than you might expect, because + # it includes the (temporary) reference as an argument to getrefcount()." + # https://docs.python.org/3.8/library/sys.html#getrefcount + ++ # However, as of 3.14: "The interpreter internally avoids some reference ++ # count modifications when loading objects onto the operands stack by ++ # borrowing references when possible. This can lead to smaller reference ++ # count values compared to previous Python versions." ++ # https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-refcount ++ + cls1.cached_fn(1, 1) + cls2.cached_fn(1, 1) + + # The cache now has two entries, but the reference count remains the same as + # before. + assert cls1.cached_fn.cache_info().currsize == 2 +- assert getrefcount(cls1) == 2 +- assert getrefcount(cls2) == 2 ++ assert getrefcount(cls1) == refcount1 ++ assert getrefcount(cls2) == refcount2 + + # Deleting one of the objects does not interfere with the cache entries + # related to the other object. diff --git a/ruamel-yaml-0.17.32.patch b/ruamel-yaml-0.17.32.patch index 471dd9f..c1f54f0 100644 --- a/ruamel-yaml-0.17.32.patch +++ b/ruamel-yaml-0.17.32.patch @@ -1,8 +1,8 @@ diff --git a/awscli/customizations/cloudformation/yamlhelper.py b/awscli/customizations/cloudformation/yamlhelper.py -index abdc749..9cf9496 100644 +index 8e3b291..a926d53 100644 --- a/awscli/customizations/cloudformation/yamlhelper.py +++ b/awscli/customizations/cloudformation/yamlhelper.py -@@ -92,8 +92,14 @@ def yaml_dump(dict_to_dump): +@@ -91,8 +91,14 @@ def yaml_dump(dict_to_dump): yaml.Representer = FlattenAliasRepresenter _add_yaml_1_1_boolean_resolvers(yaml.Resolver) yaml.Representer.add_representer(OrderedDict, _dict_representer) @@ -19,32 +19,32 @@ index abdc749..9cf9496 100644 def _dict_constructor(loader, node): diff --git a/awscli/customizations/eks/kubeconfig.py b/awscli/customizations/eks/kubeconfig.py -index 5130f7f..64526a7 100644 +index 2d302d8..d00c4a8 100644 --- a/awscli/customizations/eks/kubeconfig.py +++ b/awscli/customizations/eks/kubeconfig.py -@@ -44,7 +44,7 @@ def _get_new_kubeconfig_content(): - ("contexts", []), - ("current-context", ""), - ("kind", "Config"), -- ("preferences", OrderedDict()), -+ ("preferences", {}), - ("users", []) - ]) - -@@ -121,7 +121,7 @@ class KubeconfigValidator(object): - if (key in config.content and - type(config.content[key]) == list): +@@ -45,7 +45,7 @@ def _get_new_kubeconfig_content(): + ("contexts", []), + ("current-context", ""), + ("kind", "Config"), +- ("preferences", OrderedDict()), ++ ("preferences", {}), + ("users", []), + ] + ) +@@ -135,7 +135,7 @@ class KubeconfigValidator(object): + for key, value in self._validation_content.items(): + if key in config.content and type(config.content[key]) == list: for element in config.content[key]: - if not isinstance(element, OrderedDict): + if not isinstance(element, dict): raise KubeconfigCorruptedError( - f"Entry in {key} not a {dict}. ") - + f"Entry in {key} not a {dict}. " + ) diff --git a/awscli/customizations/eks/ordered_yaml.py b/awscli/customizations/eks/ordered_yaml.py -index 23834e0..5c0f92a 100644 +index f9b1a11..0cce12e 100644 --- a/awscli/customizations/eks/ordered_yaml.py +++ b/awscli/customizations/eks/ordered_yaml.py -@@ -46,10 +46,18 @@ def ordered_yaml_dump(to_dump, stream=None): +@@ -50,10 +50,18 @@ def ordered_yaml_dump(to_dump, stream=None): :type stream: file """ yaml = ruamel.yaml.YAML(typ="safe", pure=True) @@ -66,17 +66,35 @@ index 23834e0..5c0f92a 100644 + + return result diff --git a/tests/unit/customizations/cloudformation/test_yamlhelper.py b/tests/unit/customizations/cloudformation/test_yamlhelper.py -index 466ae2e..1adad4e 100644 +index e7ac758..b9632e9 100644 --- a/tests/unit/customizations/cloudformation/test_yamlhelper.py +++ b/tests/unit/customizations/cloudformation/test_yamlhelper.py -@@ -139,10 +139,10 @@ class TestYaml(BaseYAMLTest): - ' Name: name1\n' +@@ -130,28 +130,10 @@ class TestYaml(BaseYAMLTest): + ' Name: name1\n' ) output_dict = yaml_parse(input_template) -- expected_dict = OrderedDict([ -- ('B_Resource', OrderedDict([('Key2', {'Name': 'name2'}), ('Key1', {'Name': 'name1'})])), -- ('A_Resource', OrderedDict([('Key2', {'Name': 'name2'}), ('Key1', {'Name': 'name1'})])) -- ]) +- expected_dict = OrderedDict( +- [ +- ( +- 'B_Resource', +- OrderedDict( +- [ +- ('Key2', {'Name': 'name2'}), +- ('Key1', {'Name': 'name1'}), +- ] +- ), +- ), +- ( +- 'A_Resource', +- OrderedDict( +- [ +- ('Key2', {'Name': 'name2'}), +- ('Key1', {'Name': 'name1'}), +- ] +- ), +- ), +- ] +- ) + expected_dict = { + 'B_Resource': {'Key2': {'Name': 'name2'}, 'Key1': {'Name': 'name1'}}, + 'A_Resource': {'Key2': {'Name': 'name2'}, 'Key1': {'Name': 'name1'}} @@ -84,7 +102,7 @@ index 466ae2e..1adad4e 100644 self.assertEqual(expected_dict, output_dict) output_template = yaml_dump(output_dict) -@@ -156,7 +156,7 @@ class TestYaml(BaseYAMLTest): +@@ -165,7 +147,7 @@ class TestYaml(BaseYAMLTest): <<: *base """ output = yaml_parse(test_yaml) diff --git a/sources b/sources index a6f8408..22a13b9 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (aws-cli-2.22.9.tar.gz) = 36869662105f0aa10f294f96777c9be52c4603d3ce69f57713a225f38a975cebf0d4102d520a9378a9c88d5104eff7003b64c72628059f286b4f592dcdfeca20 +SHA512 (aws-cli-2.27.0.tar.gz) = 6651d978bfadb936ccf760603602988a70e794749d0772e7f2b2443db2381c72965d1691eedb7d082d5cd1179dc73688841ac8f0589367e2486f2b1295c59b71 diff --git a/urllib3-v2.patch b/urllib3-v2.patch new file mode 100644 index 0000000..285bb3f --- /dev/null +++ b/urllib3-v2.patch @@ -0,0 +1,124 @@ +diff --git a/awscli/botocore/awsrequest.py b/awscli/botocore/awsrequest.py +index 4ce0449..2b14009 100644 +--- a/awscli/botocore/awsrequest.py ++++ b/awscli/botocore/awsrequest.py +@@ -14,6 +14,7 @@ + import functools + import io + import logging ++from collections.abc import Mapping + import socket + import sys + +@@ -24,6 +25,7 @@ from botocore.compat import ( + HTTPResponse, + MutableMapping, + urlencode, ++ urlparse, + urlsplit, + urlunsplit, + ) +@@ -66,32 +68,34 @@ class AWSConnection: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._original_response_cls = self.response_class +- # We'd ideally hook into httplib's states, but they're all +- # __mangled_vars so we use our own state var. This variable is set +- # when we receive an early response from the server. If this value is +- # set to True, any calls to send() are noops. This value is reset to +- # false every time _send_request is called. This is to workaround the +- # fact that py2.6 (and only py2.6) has a separate send() call for the +- # body in _send_request, as opposed to endheaders(), which is where the +- # body is sent in all versions > 2.6. ++ # This variable is set when we receive an early response from the ++ # server. If this value is set to True, any calls to send() are noops. ++ # This value is reset to false every time _send_request is called. ++ # This is to workaround changes in urllib3 2.0 which uses separate ++ # send() calls in request() instead of delegating to endheaders(), ++ # which is where the body is sent in CPython's HTTPConnection. + self._response_received = False + self._expect_header_set = False ++ self._send_called = False + + def close(self): + super().close() + # Reset all of our instance state we were tracking. + self._response_received = False + self._expect_header_set = False ++ self._send_called = False + self.response_class = self._original_response_cls + +- def _send_request(self, method, url, body, headers, *args, **kwargs): ++ def request(self, method, url, body=None, headers=None, *args, **kwargs): ++ if headers is None: ++ headers = {} + self._response_received = False + if headers.get('Expect', b'') == b'100-continue': + self._expect_header_set = True + else: + self._expect_header_set = False + self.response_class = self._original_response_cls +- rval = super()._send_request( ++ rval = super().request( + method, url, body, headers, *args, **kwargs + ) + self._expect_header_set = False +@@ -210,10 +214,13 @@ class AWSConnection: + + def send(self, str): + if self._response_received: +- logger.debug( +- "send() called, but reseponse already received. " +- "Not sending data." +- ) ++ if not self._send_called: ++ # urllib3 2.0 chunks and calls send potentially ++ # thousands of times inside `request` unlike the ++ # standard library. Only log this once for sanity. ++ logger.debug("send() called, but reseponse already received. " ++ "Not sending data.") ++ self._send_called = True + return + return super().send(str) + +@@ -370,8 +377,14 @@ class AWSRequestPreparer: + def _prepare_url(self, original): + url = original.url + if original.params: +- params = urlencode(list(original.params.items()), doseq=True) +- url = f'{url}?{params}' ++ url_parts = urlparse(url) ++ delim = '&' if url_parts.query else '?' ++ if isinstance(original.params, Mapping): ++ params_to_encode = list(original.params.items()) ++ else: ++ params_to_encode = original.params ++ params = urlencode(params_to_encode, doseq=True) ++ url = delim.join((url, params)) + return url + + def _prepare_headers(self, original, prepared_body=None): +diff --git a/awscli/botocore/httpsession.py b/awscli/botocore/httpsession.py +index 516bb3f..25f982d 100644 +--- a/awscli/botocore/httpsession.py ++++ b/awscli/botocore/httpsession.py +@@ -328,7 +328,6 @@ class URLLib3Session: + + def _get_pool_manager_kwargs(self, **extra_kwargs): + pool_manager_kwargs = { +- 'strict': True, + 'timeout': self._timeout, + 'maxsize': self._max_pool_connections, + 'ssl_context': self._get_ssl_context(), +diff --git a/tests/unit/botocore/test_http_session.py b/tests/unit/botocore/test_http_session.py +index 90ed7d4..0a10c15 100644 +--- a/tests/unit/botocore/test_http_session.py ++++ b/tests/unit/botocore/test_http_session.py +@@ -148,7 +148,6 @@ class TestURLLib3Session(unittest.TestCase): + + def _assert_manager_call(self, manager, *assert_args, **assert_kwargs): + call_kwargs = { +- 'strict': True, + 'maxsize': mock.ANY, + 'timeout': mock.ANY, + 'ssl_context': mock.ANY,