Closed etienne-monier closed 1 year ago
Hi @etienne-monier,
Looking at your pytest output, you're passing in a BUCKET_NAME of None
which isn't supported. That's what's causing the error. Is it actually a hardcoded value in your pipeline?
Look at the params
on the first line below, the value for Bucket is what you're passing in immediately after your # Create bucket
comment. You need to ensure it's a valid type before creating the bucket.
params = {'Bucket': None}
kwargs = {'context': {'auth_type': None, 'client_config': <botocore.config.Config object at 0x7f8a162cb3a0>, 'client_region': '...reaming_input': False}, 'event_name': 'before-parameter-build.s3.HeadBucket', 'model': OperationModel(name=HeadBucket)}
bucket = None
def validate_bucket_name(params, **kwargs):
if 'Bucket' not in params:
return
bucket = params['Bucket']
> if not VALID_BUCKET.search(bucket) and not VALID_S3_ARN.search(bucket):
E TypeError: expected string or bytes-like object
I saw this strange error. This is hardcoded in tests/test_s3.py
, which is cat-ed above.
To make sure we're looking at the correct failure point, can you change the code to the following and report the stack trace for the failure.
# Create bucket
+ current_bucket = 3.Bucket(BUCKET_NAME)
+ all_buckets = s3.buckets.all() # This calls list_buckets under the hood
- if s3.Bucket(BUCKET_NAME) not in s3.buckets.all():
+ if current_bucket not in all_buckets:
s3.create_bucket(Bucket=BUCKET_NAME)
The only other case I can see us hitting this code path is if the configured test endpoint is returning an invalid response, causing the list buckets response to attempt creating bucket instances without a name (None
).
Here are the logs for the modified code:
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.9.12, pytest-7.1.1, pluggy-1.0.0 -- /usr/local/bin/python
cachedir: .pytest_cache
rootdir: /test-bug-boto3-on-ci
collecting ... collected 1 item
tests/test_s3.py::test_s3 FAILED [100%]
=================================== FAILURES ===================================
___________________________________ test_s3 ____________________________________
def test_s3():
"""Create a bucket in local minio instance"""
# Get the resource
s3 = boto3.resource(
"s3",
aws_access_key_id="minioadmin",
aws_secret_access_key="minioadmin",
endpoint_url=os.getenv("TEST_ENDPOINT"),
)
# Create bucket
current_bucket = s3.Bucket(BUCKET_NAME)
all_buckets = s3.buckets.all() # This calls list_buckets under the hood
> if current_bucket not in all_buckets:
tests/test_s3.py:24:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.9/site-packages/boto3/resources/collection.py:81: in __iter__
for page in self.pages():
/usr/local/lib/python3.9/site-packages/boto3/resources/collection.py:166: in pages
pages = [getattr(client, self._py_operation_name)(**params)]
/usr/local/lib/python3.9/site-packages/botocore/client.py:508: in _api_call
return self._make_api_call(operation_name, kwargs)
/usr/local/lib/python3.9/site-packages/botocore/client.py:898: in _make_api_call
http, parsed_response = self._make_request(
/usr/local/lib/python3.9/site-packages/botocore/client.py:921: in _make_request
return self._endpoint.make_request(operation_model, request_dict)
/usr/local/lib/python3.9/site-packages/botocore/endpoint.py:119: in make_request
return self._send_request(request_dict, operation_model)
/usr/local/lib/python3.9/site-packages/botocore/endpoint.py:202: in _send_request
while self._needs_retry(
/usr/local/lib/python3.9/site-packages/botocore/endpoint.py:354: in _needs_retry
responses = self._event_emitter.emit(
/usr/local/lib/python3.9/site-packages/botocore/hooks.py:412: in emit
return self._emitter.emit(aliased_event_name, **kwargs)
/usr/local/lib/python3.9/site-packages/botocore/hooks.py:256: in emit
return self._emit(event_name, kwargs)
/usr/local/lib/python3.9/site-packages/botocore/hooks.py:239: in _emit
response = handler(**kwargs)
/usr/local/lib/python3.9/site-packages/botocore/utils.py:1579: in redirect_from_error
new_region = self.get_bucket_region(bucket, response)
/usr/local/lib/python3.9/site-packages/botocore/utils.py:1638: in get_bucket_region
response = self._client.head_bucket(Bucket=bucket)
/usr/local/lib/python3.9/site-packages/botocore/client.py:508: in _api_call
return self._make_api_call(operation_name, kwargs)
/usr/local/lib/python3.9/site-packages/botocore/client.py:878: in _make_api_call
request_dict = self._convert_to_request_dict(
/usr/local/lib/python3.9/site-packages/botocore/client.py:936: in _convert_to_request_dict
api_params = self._emit_api_params(
/usr/local/lib/python3.9/site-packages/botocore/client.py:969: in _emit_api_params
self.meta.events.emit(
/usr/local/lib/python3.9/site-packages/botocore/hooks.py:412: in emit
return self._emitter.emit(aliased_event_name, **kwargs)
/usr/local/lib/python3.9/site-packages/botocore/hooks.py:256: in emit
return self._emit(event_name, kwargs)
/usr/local/lib/python3.9/site-packages/botocore/hooks.py:239: in _emit
response = handler(**kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
params = {'Bucket': None}
kwargs = {'context': {'auth_type': None, 'client_config': <botocore.config.Config object at 0x7f7045d5a670>, 'client_region': '...reaming_input': False}, 'event_name': 'before-parameter-build.s3.HeadBucket', 'model': OperationModel(name=HeadBucket)}
bucket = None
def validate_bucket_name(params, **kwargs):
if 'Bucket' not in params:
return
bucket = params['Bucket']
> if not VALID_BUCKET.search(bucket) and not VALID_S3_ARN.search(bucket):
E TypeError: expected string or bytes-like object
/usr/local/lib/python3.9/site-packages/botocore/handlers.py:270: TypeError
=========================== short test summary info ============================
FAILED tests/test_s3.py::test_s3 - TypeError: expected string or bytes-like o...
============================== 1 failed in 0.66s ===============================
Cleaning up file based variables 00:01
ERROR: Job failed: exit code 1
Thanks for checking, so this is the response from the Minio server. It's not returning valid contents for our list buckets call that is invoked by .all()
. When parsing the response, we appear to be encountering a bucket without a name which can't exist.
I'm not sure there's much we can do in this case, you may need to reach out to Minio to understand their response format and determine where they may be deviating from S3.
Ok, after some searches, the problem comes from my company proxy.
The problem is that it seems boto3 does not catch no_proxy
environment variable. I must specify it as a client (or ressource) configuration.
The solution is to modify the test module into
#!/bin env python
# pylint: disable=redefined-outer-name
import boto3
from botocore.config import Config
import os
BUCKET_NAME = "mybucket"
"""The test bucket name"""
def test_s3():
"""Create a bucket in local minio instance"""
extra_config = {}
if os.getenv("BOTO3_EMPY_PROXY") is not None:
extra_config["config"] = Config(proxies={})
# Get the resource
s3 = boto3.resource(
"s3",
aws_access_key_id="admin",
aws_secret_access_key="password",
endpoint_url=os.getenv("TEST_ENDPOINT"),
**extra_config,
)
# Create bucket
current_bucket = s3.Bucket(BUCKET_NAME)
all_buckets = s3.buckets.all() # This calls list_buckets under the hood
if current_bucket not in all_buckets:
s3.create_bucket(Bucket=BUCKET_NAME)
assert True
I finally don't know if this is desired. Let me know.
Hi @etienne-monier - Just checking in here to see if you've found a workaround or solution to this. Please let us know if there's anything else we could assist you further.
The workaround was given in my previous comment. That's to explicitly tell boto3 not to consider proxies. This is due to non-considering NO_PROXY
environment variable.
Here is the boto3 documentation on proxies for more reference: https://boto3.amazonaws.com/v1/documentation/api/1.18.55/guide/configuration.html#using-proxies
We are now converting guidance issues to GitHub Discussions so I will convert this issue. Please let us know if you had any additional feedback on this.
Describe the bug
I want to make functional tests for my S3-dependent app. To that end, I deploy a local minio docker image and I define a pytest fixture creating a bucket if missing. This works on my local laptop, but not on Gitlab CI.
Expected Behavior
I expect my pytest fixture to look if the desired bucket exists.
Current Behavior
Here are the gitlab CI logs:
Reproduction Steps
Here is the full minimal example.
Required packages
boto3 and pytest for python docker installation is required
File contents
File
tests/test_s3.py
File
Makefile
File
.gitlab-ci.yml
To test locally
Run
make minio-restart
to start minio service and runmake test
to launch test.To test on gitlab CI
The problem is that a gitlab CI runner is required. To test it, simply create a repository with the above files and push it on gitlab. Look at the pipeline logs to have the error.
Possible Solution
No idea :(
Additional Information/Context
To launch a gitlab instance with runner, I found that docker-compose file. To get the
root
user password, typesudo docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
(cf this doc)Yes, that's complicated.
SDK version used
boto3==1.24.27
Environment details (OS name and version, etc.)
Xubuntu 20.04 and GitLab Community Edition 13.9.7