Reputation: 135
I'm trying to copy a file from 1 bucket to another prefix in the same bucket using lambda & boto3 however I keep getting an error:
An error occurred (AccessDenied) when calling the CopyObject operation.
or
An error occurred (403) when calling the HeadObject operation: Forbidden
depending on which copy method I use.
The lambda function has a role assigned to it, which I think gives it all the permissions it requires:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:HeadObject",
"s3:ListObjects"
],
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
],
"Effect": "Allow"
},
{
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::bucket-name/folderA/folderB/*",
"arn:aws:s3:::bucket-name/folderC/folderD/*",
"arn:aws:s3:::bucket-name/folderE/folderF/*"
],
"Effect": "Allow"
}
]
}
The lambda function is:
#connect to s3
s3 = boto3.resource('s3')
dirs = {
"folderA/folderB": "folderC/folderD"
}
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
etag = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['eTag'], encoding='utf-8')
bucket = event['Records'][0]['s3']['bucket']['name']
filePathName = key.split("/")
sourceDir = filePathName[0] + "/" + filePathName[1]
fileName = filePathName[2]
sourceKey = sourceDir + "/" + fileName
source = {'Bucket': bucket, 'Key': sourceKey}
destination = dirs[sourceDir] + "/" + fileName
##########
# This option comes up with the An error occurred (AccessDenied) when calling the CopyObject operation. Error
###########
s3.Object(bucket, destination).copy_from(CopySource=source)
###########
## This option comes up with the An error occurred (403) when calling the HeadObject operation: Forbidden error
###########
s3.meta.client.copy(source, bucket, destination)
EDIT: forgot to mention, it works fine if I change the role to
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
],
"Effect": "Allow"
}
Upvotes: 5
Views: 12744
Reputation: 1
I noticed that many of the other solutions here are not particularly "turn-key", especially in terms of handling large buckets or working in an environment where aws-cli is not available.
This one doesn't rely on aws-cli and uses the boto3 paginators to handle large buckets.
import boto3
def recursive_s3_cp(source_bucket, source_bucket_path, dest_bucket, dest_bucket_path):
s3_client = boto3.client("s3")
paginator = s3_client.get_paginator('list_objects_v2')
response_iterator = paginator.paginate(
Bucket=source_bucket,
Prefix=source_bucket_path,
Delimiter="/"
)
result = response_iterator.build_full_result()
# Skip first item as it's a reference to the prefix directory
for bucket_object in result['Contents'][1:]:
new_path = bucket_object['Key'].replace(source_bucket_path, dest_bucket_path)
response = s3_client.copy_object(
# Tons of other parameters you can pass here if you need them
Bucket=dest_bucket,
CopySource={
'Bucket': source_bucket,
'Key': bucket_object['Key']
},
Key=new_path,
)
if not response['ResponseMetadata']['HTTPStatusCode'] == 200:
raise Exception('s3 copy failed with response: ', response)
Upvotes: 0
Reputation: 47
This question is old, but maybe for someone will be helpful:
copy_source = {
'Bucket': 'my_perfect_bucket',
'Key': 'media/old_path/glory.jpg'
}
s3.Object('my_perfect_bucket','media/new_path/glory.jpg').copy_from(CopySource=copy_source)
Upvotes: 1
Reputation: 2652
I was running into a similar problem. The solution: source
in CopySource=source
had to be a full path from the bucket root to the actual file instead of a dictionary of bucket name and key. So I think your code may have to be:
s3.Object(bucket, destination).copy_from(CopySource=bucket + sourceDir)
Upvotes: 10