Skip to content

Commit b052765

Browse files
Pczajka fix aio wif (#2678)
1 parent bd62974 commit b052765

4 files changed

Lines changed: 72 additions & 10 deletions

File tree

.github/workflows/check_installation.yml

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ concurrency:
1616
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
1717

1818
jobs:
19-
test-installation:
20-
name: Test Boto Dependency
19+
test-installation-boto:
20+
name: Test boto dependency
2121
runs-on: ubuntu-latest
2222
steps:
2323
- uses: actions/checkout@v4
@@ -54,3 +54,44 @@ jobs:
5454
# Deactivate and clean up
5555
deactivate
5656
rm -rf test_no_boto_env
57+
58+
test-installation-aioboto:
59+
name: Test aioboto dependency
60+
runs-on: ubuntu-latest
61+
steps:
62+
- uses: actions/checkout@v4
63+
64+
- name: Set up Python
65+
uses: actions/setup-python@v4
66+
with:
67+
python-version: 3.12
68+
69+
- name: Test installation with [aio] (should include aioboto)
70+
shell: bash
71+
run: |
72+
python -m venv test_no_boto_env
73+
source test_no_boto_env/bin/activate
74+
75+
python -m pip install '.[aio]'
76+
77+
# Check that aioboto3 and aiobotocore are installed
78+
pip freeze | grep aioboto || exit 1 # aioboto3 and aiobotocore should be installed
79+
80+
# Deactivate and clean up
81+
deactivate
82+
rm -rf test_no_boto_env
83+
84+
- name: Test [aio] installation with SNOWFLAKE_NO_BOTO=1 (should exclude aioboto)
85+
shell: bash
86+
run: |
87+
python -m venv test_no_boto_env
88+
source test_no_boto_env/bin/activate
89+
90+
SNOWFLAKE_NO_BOTO=1 python -m pip install .
91+
92+
# Check that boto3, botocore, aioboto, aiobotocore are NOT installed
93+
pip freeze | grep boto && exit 1
94+
95+
# Deactivate and clean up
96+
deactivate
97+
rm -rf test_no_boto_env

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,15 @@ class SetDefaultInstallationExtras(egg_info):
186186

187187
def finalize_options(self):
188188
super().finalize_options()
189-
190189
# if not explicitly excluded, add boto dependencies to install_requires
191190
if not SNOWFLAKE_NO_BOTO:
192191
boto_extras = self.distribution.extras_require.get("boto", [])
193192
self.distribution.install_requires += boto_extras
194193

194+
if "aio" in self.distribution.extras_require:
195+
aioboto_extras = self.distribution.extras_require.get("aioboto", [])
196+
self.distribution.extras_require["aio"] += aioboto_extras
197+
195198

196199
# Update command classes
197200
cmd_class["egg_info"] = SetDefaultInstallationExtras

src/snowflake/connector/aio/_wif_util.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,7 @@ async def create_aws_attestation(
8080
If the application isn't running on AWS or no credentials were found, raises an error.
8181
"""
8282
if not installed_aioboto:
83-
raise MissingDependencyError(
84-
msg="AWS Workload Identity Federation can't be used because aioboto3 or aiobotocore optional dependency is not installed. Try installing missing dependencies.",
85-
errno=ER_WIF_CREDENTIALS_NOT_FOUND,
86-
)
83+
raise MissingDependencyError("aioboto3 or aiobotocore")
8784

8885
session = await get_aws_session(impersonation_path)
8986
aws_creds = await session.get_credentials()
@@ -105,7 +102,9 @@ async def create_aws_attestation(
105102
},
106103
)
107104

108-
botocore.auth.SigV4Auth(aws_creds, "sts", region).add_auth(request)
105+
# Freeze aiobotocore credentials for use with synchronous botocore signing
106+
frozen_creds = await aws_creds.get_frozen_credentials()
107+
botocore.auth.SigV4Auth(frozen_creds, "sts", region).add_auth(request)
109108

110109
assertion_dict = {
111110
"url": request.url,

test/unit/aio/csp_helpers_async.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,21 @@ class FakeGceMetadataServiceAsync(FakeMetadataServiceAsync, FakeGceMetadataServi
154154
pass
155155

156156

157+
class AsyncCredentialsWrapper:
158+
"""Wrapper around boto credentials to make get_frozen_credentials async for testing."""
159+
160+
def __init__(self, credentials):
161+
self._credentials = credentials
162+
163+
async def get_frozen_credentials(self):
164+
"""Async version of get_frozen_credentials that returns the wrapped credentials."""
165+
return self._credentials
166+
167+
def __getattr__(self, name):
168+
"""Delegate all other attributes to the wrapped credentials."""
169+
return getattr(self._credentials, name)
170+
171+
157172
class FakeAwsEnvironmentAsync(FakeAwsEnvironment):
158173
"""Emulates the AWS environment-specific functions used in async wif_util.py.
159174
@@ -166,15 +181,19 @@ async def get_region(self):
166181
return self.region
167182

168183
async def get_credentials(self):
169-
return self.credentials
184+
if self.credentials is None:
185+
return None
186+
return AsyncCredentialsWrapper(self.credentials)
170187

171188
def __enter__(self):
172189
# First call the parent's __enter__ to get base functionality
173190
super().__enter__()
174191

175192
# Then add async-specific patches
176193
async def async_get_credentials():
177-
return self.credentials
194+
if self.credentials is None:
195+
return None
196+
return AsyncCredentialsWrapper(self.credentials)
178197

179198
async def async_get_caller_identity():
180199
return {"Arn": self.arn}

0 commit comments

Comments
 (0)