22import time
33
44import boto3
5- from botocore .exceptions import (
6- ClientError , NoCredentialsError , ParamValidationError
7- )
5+ from botocore .exceptions import ClientError , NoCredentialsError , ParamValidationError
86
97from .exceptions import (
10- BucketNameAlreadyInUse , CannotGetCurrentUser , CannotListAccountAliases ,
11- CredentialsNotFound , InvalidBucketName , InvalidUserName , UserNameTaken
8+ BucketNameAlreadyInUse ,
9+ CannotGetCurrentUser ,
10+ CannotListAccountAliases ,
11+ CredentialsNotFound ,
12+ InvalidBucketName ,
13+ InvalidUserName ,
14+ UserNameTaken ,
1215)
1316
14-
15- POLICY_NAME_FORMAT = '{bucket_name}-owner-policy'
17+ POLICY_NAME_FORMAT = "{bucket_name}-owner-policy"
1618
1719REQUIRE_HTTPS_CONDITION = {
1820 "Bool" : {
2123 },
2224 "NumericGreaterThanEquals" : {
2325 # Require TLS >= 1.2
24- "s3:TlsVersion" : [
25- "1.2"
26- ]
27- }
26+ "s3:TlsVersion" : ["1.2" ]
27+ },
2828}
2929
3030
3131class BucketCreator :
3232 def __init__ (self , profile_name = None , region_name = None ):
33- self .session = boto3 .session .Session (profile_name = profile_name ,
34- region_name = region_name )
35- self .s3 = self .session .resource ('s3' )
36- self .s3_client = self .session .client ('s3' )
37- self .iam = self .session .resource ('iam' )
33+ self .session = boto3 .session .Session (
34+ profile_name = profile_name , region_name = region_name
35+ )
36+ self .s3 = self .session .resource ("s3" )
37+ self .s3_client = self .session .client ("s3" )
38+ self .iam = self .session .resource ("iam" )
3839
3940 def commit (self , data ):
40- bucket = self .create_bucket (data [' bucket_name' ], data [' region' ])
41- user = self .create_user (bucket , data [' user_name' ])
41+ bucket = self .create_bucket (data [" bucket_name" ], data [" region" ])
42+ user = self .create_user (bucket , data [" user_name" ])
4243 self .set_bucket_policy (
4344 bucket ,
4445 user ,
4546 allow_public_acls = data ["allow_public_acls" ],
46- public_get_object_paths = data .get (' public_get_object_paths' )
47+ public_get_object_paths = data .get (" public_get_object_paths" ),
4748 )
48- if data .get (' cors_origins' ):
49- self .set_cors (bucket , data [' cors_origins' ])
50- if data .get (' enable_versioning' ):
49+ if data .get (" cors_origins" ):
50+ self .set_cors (bucket , data [" cors_origins" ])
51+ if data .get (" enable_versioning" ):
5152 self .enable_versioning (bucket )
5253
53- def get_bucket_policy_statement_for_get_object (self , bucket , public_get_object_paths ):
54+ def get_bucket_policy_statement_for_get_object (
55+ self , bucket , public_get_object_paths
56+ ):
5457 """
5558 Create policy statement to enable the public to perform s3:getObject
5659 on specified paths.
5760 """
5861 if public_get_object_paths :
62+
5963 def format_path (path ):
60- if path .startswith ('/' ):
64+ if path .startswith ("/" ):
6165 path = path [1 :]
6266 return "arn:aws:s3:::{bucket_name}/{path}" .format (
6367 bucket_name = bucket .name ,
6468 path = path ,
6569 )
70+
6671 paths_resources = []
6772 for path in public_get_object_paths :
6873 paths_resources .append (format_path (path ))
@@ -73,7 +78,7 @@ def format_path(path):
7378 "Action" : ["s3:GetObject" ],
7479 "Resource" : paths_resources ,
7580 # Require HTTPS for public requests
76- "Condition" : REQUIRE_HTTPS_CONDITION
81+ "Condition" : REQUIRE_HTTPS_CONDITION ,
7782 }
7883
7984 def get_bucket_policy_statements_for_user_access (self , bucket , user ):
@@ -82,38 +87,32 @@ def get_bucket_policy_statements_for_user_access(self, bucket, user):
8287 yield {
8388 "Sid" : "AllowUserManageBucket" ,
8489 "Effect" : "Allow" ,
85- "Principal" : {
86- "AWS" : user .arn
87- },
90+ "Principal" : {"AWS" : user .arn },
8891 "Action" : [
8992 "s3:ListBucket" ,
9093 "s3:GetBucketLocation" ,
9194 "s3:ListBucketMultipartUploads" ,
92- "s3:ListBucketVersions"
95+ "s3:ListBucketVersions" ,
9396 ],
94- "Resource" : "arn:aws:s3:::{bucket_name}" .format (
95- bucket_name = bucket .name
96- ),
97+ "Resource" : "arn:aws:s3:::{bucket_name}" .format (bucket_name = bucket .name ),
9798 # Require HTTPS for API
98- "Condition" : REQUIRE_HTTPS_CONDITION
99+ "Condition" : REQUIRE_HTTPS_CONDITION ,
99100 }
100101 # Create policy statement giving the created user full access over the
101102 # objects.
102103 yield {
103104 "Sid" : "AllowUserManageBucketObjects" ,
104105 "Effect" : "Allow" ,
105- "Principal" : {
106- "AWS" : user .arn
107- },
106+ "Principal" : {"AWS" : user .arn },
108107 "Action" : "s3:*" ,
109- "Resource" : "arn:aws:s3:::{bucket_name}/*" .format (
110- bucket_name = bucket .name
111- ),
108+ "Resource" : "arn:aws:s3:::{bucket_name}/*" .format (bucket_name = bucket .name ),
112109 # Require HTTPS for API
113- "Condition" : REQUIRE_HTTPS_CONDITION
110+ "Condition" : REQUIRE_HTTPS_CONDITION ,
114111 }
115112
116- def set_bucket_policy (self , bucket , user , allow_public_acls , public_get_object_paths = None ):
113+ def set_bucket_policy (
114+ self , bucket , user , allow_public_acls , public_get_object_paths = None
115+ ):
117116 policy_statement = []
118117 public_access = bool (public_get_object_paths )
119118
@@ -124,38 +123,42 @@ def set_bucket_policy(self, bucket, user, allow_public_acls, public_get_object_p
124123 "BlockPublicAcls" : not allow_public_acls ,
125124 "IgnorePublicAcls" : not allow_public_acls ,
126125 "BlockPublicPolicy" : not public_access ,
127- "RestrictPublicBuckets" : not public_access
128- }
126+ "RestrictPublicBuckets" : not public_access ,
127+ },
129128 )
130129 if public_access or allow_public_acls :
131- print (' Configured public access to bucket.' )
130+ print (" Configured public access to bucket." )
132131
133132 if public_access :
134133 policy_statement .append (
135134 self .get_bucket_policy_statement_for_get_object (
136135 bucket , public_get_object_paths
137136 )
138137 )
139- policy_statement .extend (list (
140- self .get_bucket_policy_statements_for_user_access (bucket , user )
141- ))
142- policy = json .dumps ({
143- "Version" : "2012-10-17" ,
144- "Statement" : policy_statement ,
145- })
138+ policy_statement .extend (
139+ list (self .get_bucket_policy_statements_for_user_access (bucket , user ))
140+ )
141+ policy = json .dumps (
142+ {
143+ "Version" : "2012-10-17" ,
144+ "Statement" : policy_statement ,
145+ }
146+ )
146147 while True :
147148 try :
148149 bucket .Policy ().put (Policy = policy )
149150 except ClientError as e :
150- if e .response ['Error' ]['Code' ] == 'MalformedPolicy' :
151- print ('Waiting for the user to be available to be '
152- 'attached to the policy (wait 5s).' )
151+ if e .response ["Error" ]["Code" ] == "MalformedPolicy" :
152+ print (
153+ "Waiting for the user to be available to be "
154+ "attached to the policy (wait 5s)."
155+ )
153156 time .sleep (5 )
154157 continue
155158 raise e
156159 else :
157160 break
158- print (' Bucket policy set.' )
161+ print (" Bucket policy set." )
159162
160163 def create_bucket (self , name , region ):
161164 """
@@ -164,23 +167,25 @@ def create_bucket(self, name, region):
164167 create_bucket_kwargs = {}
165168 create_bucket_config = {}
166169 # us-east-1 does not work with location specified.
167- if region != ' us-east-1' :
168- create_bucket_config [' LocationConstraint' ] = region
170+ if region != " us-east-1" :
171+ create_bucket_config [" LocationConstraint" ] = region
169172 if create_bucket_config :
170- create_bucket_kwargs ['CreateBucketConfiguration' ] = (
171- create_bucket_config
172- )
173+ create_bucket_kwargs ["CreateBucketConfiguration" ] = create_bucket_config
173174 bucket = self .s3 .Bucket (name )
174175 response = bucket .create (** create_bucket_kwargs )
175- msg = 'Created bucket "{bucket_name}" at "{bucket_location}" in ' \
176- 'region "{region}".'
177- print (msg .format (
178- bucket_name = name ,
179- bucket_location = response ['Location' ],
180- region = region ,
181- ))
176+ msg = (
177+ 'Created bucket "{bucket_name}" at "{bucket_location}" in '
178+ 'region "{region}".'
179+ )
180+ print (
181+ msg .format (
182+ bucket_name = name ,
183+ bucket_location = response ["Location" ],
184+ region = region ,
185+ )
186+ )
182187 print ()
183- print (' \t AWS_STORAGE_BUCKET_NAME' , name )
188+ print (" \t AWS_STORAGE_BUCKET_NAME" , name )
184189 print ()
185190 bucket .wait_until_exists ()
186191 return bucket
@@ -191,22 +196,22 @@ def enable_versioning(self, bucket):
191196
192197 def create_user (self , bucket , user_name ):
193198 user = self .iam .User (user_name ).create ()
194- self .iam .meta .client .get_waiter (' user_exists' ).wait (UserName = user_name )
199+ self .iam .meta .client .get_waiter (" user_exists" ).wait (UserName = user_name )
195200 user .load ()
196- print ('Created IAM user "{user_name}".' .format (
197- user_name = user .arn
198- ))
201+ print ('Created IAM user "{user_name}".' .format (user_name = user .arn ))
199202 self .create_user_access_key_pair (user )
200203 return user
201204
202205 def create_user_access_key_pair (self , user ):
203206 access_key_pair = user .create_access_key_pair ()
204- print ('Created access key pair for user "{user}".' .format (
205- user = user .arn ,
206- ))
207+ print (
208+ 'Created access key pair for user "{user}".' .format (
209+ user = user .arn ,
210+ )
211+ )
207212 print ()
208- print (' \t AWS_ACCESS_KEY_ID' , access_key_pair .access_key_id )
209- print (' \t AWS_SECRET_ACCESS_KEY' , access_key_pair .secret_access_key )
213+ print (" \t AWS_ACCESS_KEY_ID" , access_key_pair .access_key_id )
214+ print (" \t AWS_SECRET_ACCESS_KEY" , access_key_pair .secret_access_key )
210215 print ()
211216 return access_key_pair
212217
@@ -216,32 +221,32 @@ def set_cors(self, bucket, origins):
216221 # not empty.
217222 next (iter (origins ))
218223 except StopIteration :
219- raise ValueError ("'origins' cannot be empty." )
224+ raise ValueError ("'origins' cannot be empty." ) from None
220225 config = {
221- ' CORSRules' : [
226+ " CORSRules" : [
222227 {
223- ' AllowedMethods' : [' GET' ],
224- ' AllowedOrigins' : origins ,
225- ' MaxAgeSeconds' : 3000 ,
226- ' AllowedHeaders' : [' Authorization' ],
228+ " AllowedMethods" : [" GET" ],
229+ " AllowedOrigins" : origins ,
230+ " MaxAgeSeconds" : 3000 ,
231+ " AllowedHeaders" : [" Authorization" ],
227232 }
228233 ]
229234 }
230- msg = " Set CORS for domains {domains} to bucket \ " {bucket_name}\" ."
231- print (msg .format (domains = ', ' .join (origins ), bucket_name = bucket .name ))
235+ msg = ' Set CORS for domains {domains} to bucket "{bucket_name}".'
236+ print (msg .format (domains = ", " .join (origins ), bucket_name = bucket .name ))
232237 bucket .Cors ().put (CORSConfiguration = config )
233238
234239 def validate_bucket_name (self , bucket_name ):
235240 try :
236241 self .s3 .meta .client .head_bucket (Bucket = bucket_name )
237242 except ClientError as e :
238243 # Bucket does not exist, proceed with creation.
239- if e .response [' Error' ][ ' Code' ] == ' 404' :
244+ if e .response [" Error" ][ " Code" ] == " 404" :
240245 return
241246 # No access to the bucket means that it already exists but we
242247 # cannot run head request on it.
243- elif e .response [' Error' ][ ' Code' ] == ' 403' :
244- raise BucketNameAlreadyInUse
248+ elif e .response [" Error" ][ " Code" ] == " 403" :
249+ raise BucketNameAlreadyInUse () from None
245250 else :
246251 raise e
247252 except ParamValidationError as e :
@@ -253,9 +258,9 @@ def validate_user_name(self, user_name):
253258 try :
254259 self .iam .User (user_name ).load ()
255260 except ClientError as e :
256- if e .response [' Error' ][ ' Code' ] == ' ValidationError' :
261+ if e .response [" Error" ][ " Code" ] == " ValidationError" :
257262 raise InvalidUserName (str (e )) from e
258- if not e .response [' Error' ][ ' Code' ] == ' EntityAlreadyExists' :
263+ if not e .response [" Error" ][ " Code" ] == " EntityAlreadyExists" :
259264 return
260265 raise e
261266 else :
@@ -269,7 +274,7 @@ def get_current_user(self):
269274 except NoCredentialsError as e :
270275 raise CredentialsNotFound from e
271276 except ClientError as e :
272- if e .response [' Error' ][ ' Code' ] == ' AccessDenied' :
277+ if e .response [" Error" ][ " Code" ] == " AccessDenied" :
273278 raise CannotGetCurrentUser from e
274279 raise e
275280
@@ -279,10 +284,10 @@ def get_current_account_alias(self):
279284 except NoCredentialsError as e :
280285 raise CredentialsNotFound from e
281286 except ClientError as e :
282- if e .response [' Error' ][ ' Code' ] == ' AccessDenied' :
287+ if e .response [" Error" ][ " Code" ] == " AccessDenied" :
283288 raise CannotListAccountAliases from e
284289 raise e
285290 try :
286- return response [' AccountAliases' ][0 ]
291+ return response [" AccountAliases" ][0 ]
287292 except IndexError :
288293 return
0 commit comments