Merge ~barryprice/spade/+git/spade:black into spade:master
- Git
- lp:~barryprice/spade/+git/spade
- black
- Merge into master
Proposed by
Barry Price
Status: | Superseded |
---|---|
Proposed branch: | ~barryprice/spade/+git/spade:black |
Merge into: | spade:master |
Prerequisite: | ~barryprice/spade/+git/spade:flake8 |
Diff against target: |
905 lines (+201/-191) 10 files modified
Makefile (+2/-1) spade/AzureBucket.py (+61/-63) spade/BucketObject.py (+2/-3) spade/GCEBucket.py (+14/-33) spade/LocalBucket.py (+15/-3) spade/ObjectIO.py (+5/-8) spade/S3Bucket.py (+61/-43) spade/Spade.py (+14/-8) spade/StdioBucket.py (+1/-1) spade/SwiftBucket.py (+26/-28) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Spade Developers | Pending | ||
Review via email: mp+394570@code.launchpad.net |
This proposal has been superseded by a proposal from 2020-11-27.
Commit message
Reformatted with black
Description of the change
To post a comment you must log in.
- c85283a... by Barry Price
-
Typo
- 6817bc5... by Barry Price
-
Merge branch 'master' into flake8
- 1734965... by Barry Price
-
Merge branch 'flake8' into black
Unmerged commits
- 1734965... by Barry Price
-
Merge branch 'flake8' into black
- 6817bc5... by Barry Price
-
Merge branch 'master' into flake8
- c85283a... by Barry Price
-
Typo
- a86afa5... by Barry Price
-
Reformatted with black
- aa2d273... by Barry Price
-
Address all Flake8 errors bar complexity (noqa: C901)
- 7e34f18... by Barry Price
-
Modernise, switch to python3 exclusively
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/Makefile b/Makefile | |||
2 | index 03544a4..ceb744e 100644 | |||
3 | --- a/Makefile | |||
4 | +++ b/Makefile | |||
5 | @@ -2,7 +2,8 @@ version := $(shell python3 -c "from spade import __version__; print(__version__) | |||
6 | 2 | tempfiles_snap := snap/snapcraft.yaml spade*.snap | 2 | tempfiles_snap := snap/snapcraft.yaml spade*.snap |
7 | 3 | 3 | ||
8 | 4 | blacken: | 4 | blacken: |
10 | 5 | @echo "Normalising python layout with black (but not really)." | 5 | @echo "Normalising python layout with black" |
11 | 6 | @tox -e black | ||
12 | 6 | 7 | ||
13 | 7 | lint: blacken | 8 | lint: blacken |
14 | 8 | @echo "Running flake8" | 9 | @echo "Running flake8" |
15 | diff --git a/spade/AzureBucket.py b/spade/AzureBucket.py | |||
16 | index 0f7a62d..e04830a 100644 | |||
17 | --- a/spade/AzureBucket.py | |||
18 | +++ b/spade/AzureBucket.py | |||
19 | @@ -26,15 +26,14 @@ from spade.ObjectIO import ObjectIO | |||
20 | 26 | 26 | ||
21 | 27 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | 27 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 |
22 | 28 | DEFAULT_DELIMITER = "/" | 28 | DEFAULT_DELIMITER = "/" |
24 | 29 | AZURE_SEGMENTSIZE = 1024 * 1024 * 4 # Azure block (segment) limit = 4MB | 29 | AZURE_SEGMENTSIZE = 1024 * 1024 * 4 # Azure block (segment) limit = 4MB |
25 | 30 | MAX_AZURE_OBJECTSIZE = 64 * 1024 * 1024 # Azure block blob limit = 64MB | 30 | MAX_AZURE_OBJECTSIZE = 64 * 1024 * 1024 # Azure block blob limit = 64MB |
26 | 31 | USER_META_PREFIX = "x-ms-meta-" | 31 | USER_META_PREFIX = "x-ms-meta-" |
27 | 32 | 32 | ||
28 | 33 | 33 | ||
30 | 34 | class AzureBucket(): | 34 | class AzureBucket: |
31 | 35 | def __init__(self, creds): | 35 | def __init__(self, creds): |
34 | 36 | wanted = ["private_key", | 36 | wanted = ["private_key", "storage_account"] |
33 | 37 | "storage_account"] | ||
35 | 38 | for k in wanted: | 37 | for k in wanted: |
36 | 39 | if k not in creds: | 38 | if k not in creds: |
37 | 40 | # Missing creds | 39 | # Missing creds |
38 | @@ -67,9 +66,19 @@ class AzureBucket(): | |||
39 | 67 | def _create_azure_auth_signature(self, bucket, headers, query, method): | 66 | def _create_azure_auth_signature(self, bucket, headers, query, method): |
40 | 68 | # Note: auth_headers are in a specific order | 67 | # Note: auth_headers are in a specific order |
41 | 69 | # see https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx | 68 | # see https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx |
45 | 70 | auth_headers = ("Content-Encoding", "Content-Language", "Content-Length", "Content-MD5", | 69 | auth_headers = ( |
46 | 71 | "Content-Type", "Date", "If-Modified-Since", "If-Match", "If-None-Match", | 70 | "Content-Encoding", |
47 | 72 | "If-Unmodified-Since", "Range") | 71 | "Content-Language", |
48 | 72 | "Content-Length", | ||
49 | 73 | "Content-MD5", | ||
50 | 74 | "Content-Type", | ||
51 | 75 | "Date", | ||
52 | 76 | "If-Modified-Since", | ||
53 | 77 | "If-Match", | ||
54 | 78 | "If-None-Match", | ||
55 | 79 | "If-Unmodified-Since", | ||
56 | 80 | "Range", | ||
57 | 81 | ) | ||
58 | 73 | canonicalized_headers = self._dict_to_str(headers, strip_final=False, startswith="x-ms-") | 82 | canonicalized_headers = self._dict_to_str(headers, strip_final=False, startswith="x-ms-") |
59 | 74 | query_string = self._dict_to_str(query) | 83 | query_string = self._dict_to_str(query) |
60 | 75 | 84 | ||
61 | @@ -84,9 +93,11 @@ class AzureBucket(): | |||
62 | 84 | signature += canonicalized_headers | 93 | signature += canonicalized_headers |
63 | 85 | signature += canonicalized_resource | 94 | signature += canonicalized_resource |
64 | 86 | 95 | ||
68 | 87 | encoded_sig = base64.b64encode(hmac.new(base64.b64decode(self.primaryKey), | 96 | encoded_sig = base64.b64encode( |
69 | 88 | msg=signature.encode("utf-8"), | 97 | hmac.new( |
70 | 89 | digestmod=hashlib.sha256).digest()) | 98 | base64.b64decode(self.primaryKey), msg=signature.encode("utf-8"), digestmod=hashlib.sha256 |
71 | 99 | ).digest() | ||
72 | 100 | ) | ||
73 | 90 | return encoded_sig.decode('utf-8') | 101 | return encoded_sig.decode('utf-8') |
74 | 91 | 102 | ||
75 | 92 | # Requests documented here: https://msdn.microsoft.com/en-GB/library/azure/dd135733.aspx | 103 | # Requests documented here: https://msdn.microsoft.com/en-GB/library/azure/dd135733.aspx |
76 | @@ -104,10 +115,9 @@ class AzureBucket(): | |||
77 | 104 | headers["Authorization"] = "SharedKey {}:{}".format(self.storageAccount, auth) | 115 | headers["Authorization"] = "SharedKey {}:{}".format(self.storageAccount, auth) |
78 | 105 | 116 | ||
79 | 106 | query_string = self._dict_to_str(query, kv_sep="=", entry_sep="&") | 117 | query_string = self._dict_to_str(query, kv_sep="=", entry_sep="&") |
84 | 107 | response, content = h.request(uri=self.blobUri + bucket + "?" + query_string, | 118 | response, content = h.request( |
85 | 108 | method=method, | 119 | uri=self.blobUri + bucket + "?" + query_string, method=method, body=body, headers=headers |
86 | 109 | body=body, | 120 | ) |
83 | 110 | headers=headers) | ||
87 | 111 | return response, content | 121 | return response, content |
88 | 112 | 122 | ||
89 | 113 | def listBuckets(self): | 123 | def listBuckets(self): |
90 | @@ -153,9 +163,7 @@ class AzureBucket(): | |||
91 | 153 | 163 | ||
92 | 154 | def getBucketMetadata(self, bucketName): | 164 | def getBucketMetadata(self, bucketName): |
93 | 155 | # Standard metadata | 165 | # Standard metadata |
97 | 156 | response, content = self._send_azure_request(bucketName, | 166 | response, content = self._send_azure_request(bucketName, query={"restype": "container"}, method="HEAD") |
95 | 157 | query={"restype": "container"}, | ||
96 | 158 | method="HEAD") | ||
98 | 159 | if response.get("status") == "200": | 167 | if response.get("status") == "200": |
99 | 160 | co = BucketObject(bucketName) | 168 | co = BucketObject(bucketName) |
100 | 161 | co.size = int(response.get("content-length", 0)) | 169 | co.size = int(response.get("content-length", 0)) |
101 | @@ -169,9 +177,7 @@ class AzureBucket(): | |||
102 | 169 | 177 | ||
103 | 170 | # User-defined metadata | 178 | # User-defined metadata |
104 | 171 | query = {"restype": "container", "comp": "metadata"} | 179 | query = {"restype": "container", "comp": "metadata"} |
108 | 172 | response, content = self._send_azure_request(bucketName, | 180 | response, content = self._send_azure_request(bucketName, query=query, method="HEAD") |
106 | 173 | query=query, | ||
107 | 174 | method="HEAD") | ||
109 | 175 | if response.get("status") == "200": | 181 | if response.get("status") == "200": |
110 | 176 | for k, v in response.items(): | 182 | for k, v in response.items(): |
111 | 177 | if k.lower().startswith(USER_META_PREFIX): | 183 | if k.lower().startswith(USER_META_PREFIX): |
112 | @@ -187,8 +193,7 @@ class AzureBucket(): | |||
113 | 187 | 193 | ||
114 | 188 | def getObjectMetadata(self, bucketName, objectName): | 194 | def getObjectMetadata(self, bucketName, objectName): |
115 | 189 | # Standard metadata | 195 | # Standard metadata |
118 | 190 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 196 | response, content = self._send_azure_request(bucketName + "/" + objectName, method="HEAD") |
117 | 191 | method="HEAD") | ||
119 | 192 | if response.get("status") == "200": | 197 | if response.get("status") == "200": |
120 | 193 | co = BucketObject(objectName) | 198 | co = BucketObject(objectName) |
121 | 194 | co.size = int(response.get("content-length", 0)) | 199 | co.size = int(response.get("content-length", 0)) |
122 | @@ -201,9 +206,9 @@ class AzureBucket(): | |||
123 | 201 | raise BucketError("Azure Failed to find object metadata") | 206 | raise BucketError("Azure Failed to find object metadata") |
124 | 202 | 207 | ||
125 | 203 | # User-defined metadata | 208 | # User-defined metadata |
129 | 204 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 209 | response, content = self._send_azure_request( |
130 | 205 | query={"comp": "metadata"}, | 210 | bucketName + "/" + objectName, query={"comp": "metadata"}, method="HEAD" |
131 | 206 | method="HEAD") | 211 | ) |
132 | 207 | if response.get("status") == "200": | 212 | if response.get("status") == "200": |
133 | 208 | for k, v in response.items(): | 213 | for k, v in response.items(): |
134 | 209 | if k.lower().startswith(USER_META_PREFIX): | 214 | if k.lower().startswith(USER_META_PREFIX): |
135 | @@ -225,9 +230,13 @@ class AzureBucket(): | |||
136 | 225 | return metadata.rawMetadata.get("etag", "") | 230 | return metadata.rawMetadata.get("etag", "") |
137 | 226 | 231 | ||
138 | 227 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): | 232 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): |
142 | 228 | systemMetadata = ["x-ms-blob-cache-control", "x-ms-blob-content-type", | 233 | systemMetadata = [ |
143 | 229 | "x-ms-blob-content-md5", "x-ms-blob-content-encoding", | 234 | "x-ms-blob-cache-control", |
144 | 230 | "x-ms-blob-content-language"] | 235 | "x-ms-blob-content-type", |
145 | 236 | "x-ms-blob-content-md5", | ||
146 | 237 | "x-ms-blob-content-encoding", | ||
147 | 238 | "x-ms-blob-content-language", | ||
148 | 239 | ] | ||
149 | 231 | 240 | ||
150 | 232 | newStandardMetadata = {} | 241 | newStandardMetadata = {} |
151 | 233 | newUserMetadata = {} | 242 | newUserMetadata = {} |
152 | @@ -252,18 +261,16 @@ class AzureBucket(): | |||
153 | 252 | newUserMetadata[USER_META_PREFIX + k] = v | 261 | newUserMetadata[USER_META_PREFIX + k] = v |
154 | 253 | 262 | ||
155 | 254 | # Standard metadata | 263 | # Standard metadata |
160 | 255 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 264 | response, content = self._send_azure_request( |
161 | 256 | query={"comp": "properties"}, | 265 | bucketName + "/" + objectName, query={"comp": "properties"}, headers=newStandardMetadata, method="PUT" |
162 | 257 | headers=newStandardMetadata, | 266 | ) |
159 | 258 | method="PUT") | ||
163 | 259 | if response.get("status") != "200": | 267 | if response.get("status") != "200": |
164 | 260 | raise BucketError("Azure Failed to set standard object metadata") | 268 | raise BucketError("Azure Failed to set standard object metadata") |
165 | 261 | 269 | ||
166 | 262 | # User metadata | 270 | # User metadata |
171 | 263 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 271 | response, content = self._send_azure_request( |
172 | 264 | query={"comp": "metadata"}, | 272 | bucketName + "/" + objectName, query={"comp": "metadata"}, headers=newUserMetadata, method="PUT" |
173 | 265 | headers=newUserMetadata, | 273 | ) |
170 | 266 | method="PUT") | ||
174 | 267 | if response.get("status") != "200": | 274 | if response.get("status") != "200": |
175 | 268 | raise BucketError("Azure Failed to set user object metadata") | 275 | raise BucketError("Azure Failed to set user object metadata") |
176 | 269 | return | 276 | return |
177 | @@ -272,8 +279,7 @@ class AzureBucket(): | |||
178 | 272 | headers = {} | 279 | headers = {} |
179 | 273 | if rangeStart is not None and rangeEnd is not None: | 280 | if rangeStart is not None and rangeEnd is not None: |
180 | 274 | headers["Range"] = "bytes={}-{}".format(rangeStart, rangeEnd) | 281 | headers["Range"] = "bytes={}-{}".format(rangeStart, rangeEnd) |
183 | 275 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 282 | response, content = self._send_azure_request(bucketName + "/" + objectName, headers=headers) |
182 | 276 | headers=headers) | ||
184 | 277 | if response.get("status") == "200" or response.get("status") == "206": | 283 | if response.get("status") == "200" or response.get("status") == "206": |
185 | 278 | return content | 284 | return content |
186 | 279 | elif response.get("status") == "416": | 285 | elif response.get("status") == "416": |
187 | @@ -293,11 +299,13 @@ class AzureBucket(): | |||
188 | 293 | blockId = base64.b64encode("{:06}".format(blockNumber).encode('utf-8')).decode('utf-8') | 299 | blockId = base64.b64encode("{:06}".format(blockNumber).encode('utf-8')).decode('utf-8') |
189 | 294 | headers = {} | 300 | headers = {} |
190 | 295 | headers["Content-Length"] = str(len(blockContent)) | 301 | headers["Content-Length"] = str(len(blockContent)) |
196 | 296 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 302 | response, content = self._send_azure_request( |
197 | 297 | query={"comp": "block", "blockid": blockId}, | 303 | bucketName + "/" + objectName, |
198 | 298 | headers=headers, | 304 | query={"comp": "block", "blockid": blockId}, |
199 | 299 | body=blockContent, | 305 | headers=headers, |
200 | 300 | method="PUT") | 306 | body=blockContent, |
201 | 307 | method="PUT", | ||
202 | 308 | ) | ||
203 | 301 | if not response.get("status") == "201": | 309 | if not response.get("status") == "201": |
204 | 302 | raise BucketError("Azure Failed to upload multi-part block") | 310 | raise BucketError("Azure Failed to upload multi-part block") |
205 | 303 | blockList.append(blockId) | 311 | blockList.append(blockId) |
206 | @@ -312,11 +320,9 @@ class AzureBucket(): | |||
207 | 312 | xml += "</BlockList>\n" | 320 | xml += "</BlockList>\n" |
208 | 313 | headers = {} | 321 | headers = {} |
209 | 314 | headers["Content-Length"] = str(len(xml)) | 322 | headers["Content-Length"] = str(len(xml)) |
215 | 315 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 323 | response, content = self._send_azure_request( |
216 | 316 | query={"comp": "blocklist"}, | 324 | bucketName + "/" + objectName, query={"comp": "blocklist"}, headers=headers, body=xml, method="PUT" |
217 | 317 | headers=headers, | 325 | ) |
213 | 318 | body=xml, | ||
214 | 319 | method="PUT") | ||
218 | 320 | if not response.get("status") == "201": | 326 | if not response.get("status") == "201": |
219 | 321 | raise BucketError("Azure Failed to upload blocklist") | 327 | raise BucketError("Azure Failed to upload blocklist") |
220 | 322 | return | 328 | return |
221 | @@ -329,10 +335,9 @@ class AzureBucket(): | |||
222 | 329 | headers["Content-Length"] = str(size) | 335 | headers["Content-Length"] = str(size) |
223 | 330 | headers["Content-Type"] = "application/octet-stream" | 336 | headers["Content-Type"] = "application/octet-stream" |
224 | 331 | headers["x-ms-blob-type"] = "BlockBlob" | 337 | headers["x-ms-blob-type"] = "BlockBlob" |
229 | 332 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 338 | response, content = self._send_azure_request( |
230 | 333 | headers=headers, | 339 | bucketName + "/" + objectName, headers=headers, body=src, method="PUT" |
231 | 334 | body=src, | 340 | ) |
228 | 335 | method="PUT") | ||
232 | 336 | if response.get("status") == "201": | 341 | if response.get("status") == "201": |
233 | 337 | return | 342 | return |
234 | 338 | else: | 343 | else: |
235 | @@ -342,8 +347,7 @@ class AzureBucket(): | |||
236 | 342 | return ObjectIO(self, bucketName, objectName, blockSize) | 347 | return ObjectIO(self, bucketName, objectName, blockSize) |
237 | 343 | 348 | ||
238 | 344 | def deleteObject(self, bucketName, objectName): | 349 | def deleteObject(self, bucketName, objectName): |
241 | 345 | response, content = self._send_azure_request(bucketName + "/" + objectName, | 350 | response, content = self._send_azure_request(bucketName + "/" + objectName, method="DELETE") |
240 | 346 | method="DELETE") | ||
242 | 347 | if response.get("status") == "202": | 351 | if response.get("status") == "202": |
243 | 348 | return | 352 | return |
244 | 349 | else: | 353 | else: |
245 | @@ -351,9 +355,7 @@ class AzureBucket(): | |||
246 | 351 | 355 | ||
247 | 352 | def createBucket(self, bucketName): | 356 | def createBucket(self, bucketName): |
248 | 353 | query = {"restype": "container"} | 357 | query = {"restype": "container"} |
252 | 354 | response, content = self._send_azure_request(bucketName, | 358 | response, content = self._send_azure_request(bucketName, query=query, method="PUT") |
250 | 355 | query=query, | ||
251 | 356 | method="PUT") | ||
253 | 357 | if response.get("status") == "201": | 359 | if response.get("status") == "201": |
254 | 358 | return | 360 | return |
255 | 359 | else: | 361 | else: |
256 | @@ -362,9 +364,7 @@ class AzureBucket(): | |||
257 | 362 | # Note: Azure doesn't care if the container is empty or not | 364 | # Note: Azure doesn't care if the container is empty or not |
258 | 363 | def deleteBucket(self, bucketName): | 365 | def deleteBucket(self, bucketName): |
259 | 364 | query = {"restype": "container"} | 366 | query = {"restype": "container"} |
263 | 365 | response, content = self._send_azure_request(bucketName, | 367 | response, content = self._send_azure_request(bucketName, query=query, method="DELETE") |
261 | 366 | query=query, | ||
262 | 367 | method="DELETE") | ||
264 | 368 | if response.get("status") == "202": | 368 | if response.get("status") == "202": |
265 | 369 | return | 369 | return |
266 | 370 | else: | 370 | else: |
267 | @@ -378,9 +378,7 @@ class AzureBucket(): | |||
268 | 378 | return [] | 378 | return [] |
269 | 379 | 379 | ||
270 | 380 | query = {"restype": "container", "comp": "acl"} | 380 | query = {"restype": "container", "comp": "acl"} |
274 | 381 | response, content = self._send_azure_request(bucketName, | 381 | response, content = self._send_azure_request(bucketName, query=query, method="HEAD") |
272 | 382 | query=query, | ||
273 | 383 | method="HEAD") | ||
275 | 384 | if response.get("status") != "200": | 382 | if response.get("status") != "200": |
276 | 385 | raise BucketError("Azure Failed to read ACL data") | 383 | raise BucketError("Azure Failed to read ACL data") |
277 | 386 | 384 | ||
278 | diff --git a/spade/BucketObject.py b/spade/BucketObject.py | |||
279 | index 9f8a258..7d35d24 100644 | |||
280 | --- a/spade/BucketObject.py | |||
281 | +++ b/spade/BucketObject.py | |||
282 | @@ -1,5 +1,4 @@ | |||
285 | 1 | class BucketObject(): | 1 | class BucketObject: |
284 | 2 | |||
286 | 3 | class ObjectType: | 2 | class ObjectType: |
287 | 4 | File = 0 | 3 | File = 0 |
288 | 5 | Directory = 1 | 4 | Directory = 1 |
289 | @@ -18,7 +17,7 @@ class BucketError(Exception): | |||
290 | 18 | pass | 17 | pass |
291 | 19 | 18 | ||
292 | 20 | 19 | ||
294 | 21 | class BucketAcl(): | 20 | class BucketAcl: |
295 | 22 | def __init__(self): | 21 | def __init__(self): |
296 | 23 | self.acls = {} | 22 | self.acls = {} |
297 | 24 | 23 | ||
298 | diff --git a/spade/GCEBucket.py b/spade/GCEBucket.py | |||
299 | index 2029886..56761f1 100644 | |||
300 | --- a/spade/GCEBucket.py | |||
301 | +++ b/spade/GCEBucket.py | |||
302 | @@ -23,6 +23,7 @@ import datetime | |||
303 | 23 | import base64 | 23 | import base64 |
304 | 24 | from apiclient import discovery, errors | 24 | from apiclient import discovery, errors |
305 | 25 | from apiclient.http import MediaIoBaseUpload | 25 | from apiclient.http import MediaIoBaseUpload |
306 | 26 | |||
307 | 26 | try: | 27 | try: |
308 | 27 | from oauth2client.service_account import ServiceAccountCredentials | 28 | from oauth2client.service_account import ServiceAccountCredentials |
309 | 28 | except ImportError: | 29 | except ImportError: |
310 | @@ -34,11 +35,9 @@ DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | |||
311 | 34 | DEFAULT_DELIMITER = "/" | 35 | DEFAULT_DELIMITER = "/" |
312 | 35 | 36 | ||
313 | 36 | 37 | ||
315 | 37 | class GCEBucket(): | 38 | class GCEBucket: |
316 | 38 | def __init__(self, creds): | 39 | def __init__(self, creds): |
320 | 39 | wanted = ["client_email", | 40 | wanted = ["client_email", "private_key", "project"] |
318 | 40 | "private_key", | ||
319 | 41 | "project"] | ||
321 | 42 | for k in wanted: | 41 | for k in wanted: |
322 | 43 | if k not in creds: | 42 | if k not in creds: |
323 | 44 | # Missing creds | 43 | # Missing creds |
324 | @@ -47,12 +46,9 @@ class GCEBucket(): | |||
325 | 47 | # httplib2.debuglevel = 4 | 46 | # httplib2.debuglevel = 4 |
326 | 48 | cloud_storage_auth_scope = 'https://www.googleapis.com/auth/devstorage.full_control' | 47 | cloud_storage_auth_scope = 'https://www.googleapis.com/auth/devstorage.full_control' |
327 | 49 | try: | 48 | try: |
330 | 50 | c = ServiceAccountCredentials.from_json_keyfile_dict(creds, | 49 | c = ServiceAccountCredentials.from_json_keyfile_dict(creds, scopes=cloud_storage_auth_scope) |
329 | 51 | scopes=cloud_storage_auth_scope) | ||
331 | 52 | except NameError: | 50 | except NameError: |
335 | 53 | c = SignedJwtAssertionCredentials(creds['client_email'], | 51 | c = SignedJwtAssertionCredentials(creds['client_email'], creds['private_key'], cloud_storage_auth_scope) |
333 | 54 | creds['private_key'], | ||
334 | 55 | cloud_storage_auth_scope) | ||
336 | 56 | http = c.authorize(httplib2.Http()) | 52 | http = c.authorize(httplib2.Http()) |
337 | 57 | self.gce = discovery.build('storage', 'v1', http=http) | 53 | self.gce = discovery.build('storage', 'v1', http=http) |
338 | 58 | self.project = creds["project"] | 54 | self.project = creds["project"] |
339 | @@ -79,10 +75,7 @@ class GCEBucket(): | |||
340 | 79 | def listBucketContents(self, bucketName, path=None, delimiter=DEFAULT_DELIMITER): | 75 | def listBucketContents(self, bucketName, path=None, delimiter=DEFAULT_DELIMITER): |
341 | 80 | contents = [] | 76 | contents = [] |
342 | 81 | fields_to_return = 'nextPageToken,prefixes,items' | 77 | fields_to_return = 'nextPageToken,prefixes,items' |
347 | 82 | req = self.gce.objects().list(bucket=bucketName, | 78 | req = self.gce.objects().list(bucket=bucketName, prefix=path, delimiter=delimiter, fields=fields_to_return) |
344 | 83 | prefix=path, | ||
345 | 84 | delimiter=delimiter, | ||
346 | 85 | fields=fields_to_return) | ||
348 | 86 | while req: | 79 | while req: |
349 | 87 | try: | 80 | try: |
350 | 88 | resp = req.execute() | 81 | resp = req.execute() |
351 | @@ -106,8 +99,7 @@ class GCEBucket(): | |||
352 | 106 | return contents | 99 | return contents |
353 | 107 | 100 | ||
354 | 108 | def getBucketMetadata(self, bucketName): | 101 | def getBucketMetadata(self, bucketName): |
357 | 109 | req = self.gce.buckets().get(bucket=bucketName, | 102 | req = self.gce.buckets().get(bucket=bucketName, projection="full") |
356 | 110 | projection="full") | ||
358 | 111 | try: | 103 | try: |
359 | 112 | resp = req.execute() | 104 | resp = req.execute() |
360 | 113 | except errors.HttpError: | 105 | except errors.HttpError: |
361 | @@ -126,9 +118,7 @@ class GCEBucket(): | |||
362 | 126 | raise BucketError("GCE Setting bucket metadata is not supported in GCE") | 118 | raise BucketError("GCE Setting bucket metadata is not supported in GCE") |
363 | 127 | 119 | ||
364 | 128 | def getObjectMetadata(self, bucketName, objectName): | 120 | def getObjectMetadata(self, bucketName, objectName): |
368 | 129 | req = self.gce.objects().get(bucket=bucketName, | 121 | req = self.gce.objects().get(bucket=bucketName, object=objectName, projection="full") |
366 | 130 | object=objectName, | ||
367 | 131 | projection="full") | ||
369 | 132 | try: | 122 | try: |
370 | 133 | resp = req.execute() | 123 | resp = req.execute() |
371 | 134 | except errors.HttpError: | 124 | except errors.HttpError: |
372 | @@ -157,8 +147,7 @@ class GCEBucket(): | |||
373 | 157 | return hexChecksum | 147 | return hexChecksum |
374 | 158 | 148 | ||
375 | 159 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): | 149 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): |
378 | 160 | systemMetadata = ["cacheControl", "contentDisposition", "contentEncoding", | 150 | systemMetadata = ["cacheControl", "contentDisposition", "contentEncoding", "contentLanguage", "contentType"] |
377 | 161 | "contentLanguage", "contentType"] | ||
379 | 162 | newMetadata = {} | 151 | newMetadata = {} |
380 | 163 | m = self.getObjectMetadata(bucketName, objectName) | 152 | m = self.getObjectMetadata(bucketName, objectName) |
381 | 164 | # Keep existing acls, system metadata and (optionally) user metadata | 153 | # Keep existing acls, system metadata and (optionally) user metadata |
382 | @@ -177,9 +166,7 @@ class GCEBucket(): | |||
383 | 177 | newMetadata[k] = v | 166 | newMetadata[k] = v |
384 | 178 | else: | 167 | else: |
385 | 179 | newMetadata["metadata"][k] = v | 168 | newMetadata["metadata"][k] = v |
389 | 180 | req = self.gce.objects().update(bucket=bucketName, | 169 | req = self.gce.objects().update(bucket=bucketName, object=objectName, body=newMetadata) |
387 | 181 | object=objectName, | ||
388 | 182 | body=newMetadata) | ||
390 | 183 | try: | 170 | try: |
391 | 184 | req.execute() | 171 | req.execute() |
392 | 185 | except errors.HttpError: | 172 | except errors.HttpError: |
393 | @@ -190,22 +177,18 @@ class GCEBucket(): | |||
394 | 190 | headers = {} | 177 | headers = {} |
395 | 191 | if rangeStart is not None and rangeEnd is not None: | 178 | if rangeStart is not None and rangeEnd is not None: |
396 | 192 | headers['range'] = "bytes={}-{}".format(rangeStart, rangeEnd) | 179 | headers['range'] = "bytes={}-{}".format(rangeStart, rangeEnd) |
399 | 193 | req = self.gce.objects().get_media(bucket=bucketName, | 180 | req = self.gce.objects().get_media(bucket=bucketName, object=objectName) |
398 | 194 | object=objectName) | ||
400 | 195 | http = req.http | 181 | http = req.http |
401 | 196 | resp, data = http.request(req.uri, headers=headers) | 182 | resp, data = http.request(req.uri, headers=headers) |
402 | 197 | if resp.get("status") == "416": | 183 | if resp.get("status") == "416": |
403 | 198 | # Requested range not satisfiable | 184 | # Requested range not satisfiable |
404 | 199 | data = "" | 185 | data = "" |
405 | 200 | elif resp.get("status")[0] != "2": | 186 | elif resp.get("status")[0] != "2": |
407 | 201 | raise(BucketError("GCE Failed to find object")) | 187 | raise (BucketError("GCE Failed to find object")) |
408 | 202 | return data | 188 | return data |
409 | 203 | 189 | ||
410 | 204 | def putObject(self, bucketName, objectName, src, size, blockSize=DEFAULT_BLOCKSIZE): | 190 | def putObject(self, bucketName, objectName, src, size, blockSize=DEFAULT_BLOCKSIZE): |
415 | 205 | media = MediaIoBaseUpload(src, | 191 | media = MediaIoBaseUpload(src, mimetype="application/octet-stream", chunksize=blockSize, resumable=True) |
412 | 206 | mimetype="application/octet-stream", | ||
413 | 207 | chunksize=blockSize, | ||
414 | 208 | resumable=True) | ||
416 | 209 | req = self.gce.objects().insert(bucket=bucketName, name=objectName, media_body=media) | 192 | req = self.gce.objects().insert(bucket=bucketName, name=objectName, media_body=media) |
417 | 210 | resp = None | 193 | resp = None |
418 | 211 | while resp is None: | 194 | while resp is None: |
419 | @@ -243,9 +226,7 @@ class GCEBucket(): | |||
420 | 243 | return | 226 | return |
421 | 244 | 227 | ||
422 | 245 | aclTypes = ["list", "read", "write"] | 228 | aclTypes = ["list", "read", "write"] |
426 | 246 | aclTypeMap = {"read": "READER", | 229 | aclTypeMap = {"read": "READER", "write": "WRITER", "list": "READER"} |
424 | 247 | "write": "WRITER", | ||
425 | 248 | "list": "READER"} | ||
427 | 249 | 230 | ||
428 | 250 | def translateAcl(self, aclData, aliases): | 231 | def translateAcl(self, aclData, aliases): |
429 | 251 | """ | 232 | """ |
430 | diff --git a/spade/LocalBucket.py b/spade/LocalBucket.py | |||
431 | index f2293ec..9f3f974 100644 | |||
432 | --- a/spade/LocalBucket.py | |||
433 | +++ b/spade/LocalBucket.py | |||
434 | @@ -10,7 +10,7 @@ DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | |||
435 | 10 | DEFAULT_DELIMITER = "/" | 10 | DEFAULT_DELIMITER = "/" |
436 | 11 | 11 | ||
437 | 12 | 12 | ||
439 | 13 | class LocalBucket(): | 13 | class LocalBucket: |
440 | 14 | def __init__(self, creds): | 14 | def __init__(self, creds): |
441 | 15 | wanted = ["bucket_root"] | 15 | wanted = ["bucket_root"] |
442 | 16 | for k in wanted: | 16 | for k in wanted: |
443 | @@ -79,8 +79,20 @@ class LocalBucket(): | |||
444 | 79 | co.date = self.standardTimestamp(stat.st_mtime) | 79 | co.date = self.standardTimestamp(stat.st_mtime) |
445 | 80 | co.contentType = "text/plain" | 80 | co.contentType = "text/plain" |
446 | 81 | raw = {} | 81 | raw = {} |
449 | 82 | for num, name in enumerate(["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", | 82 | for num, name in enumerate( |
450 | 83 | "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]): | 83 | [ |
451 | 84 | "st_mode", | ||
452 | 85 | "st_ino", | ||
453 | 86 | "st_dev", | ||
454 | 87 | "st_nlink", | ||
455 | 88 | "st_uid", | ||
456 | 89 | "st_gid", | ||
457 | 90 | "st_size", | ||
458 | 91 | "st_atime", | ||
459 | 92 | "st_mtime", | ||
460 | 93 | "st_ctime", | ||
461 | 94 | ] | ||
462 | 95 | ): | ||
463 | 84 | raw[name] = stat[num] | 96 | raw[name] = stat[num] |
464 | 85 | co.rawMetadata = raw | 97 | co.rawMetadata = raw |
465 | 86 | return co | 98 | return co |
466 | diff --git a/spade/ObjectIO.py b/spade/ObjectIO.py | |||
467 | index fc2b38f..dfdb804 100644 | |||
468 | --- a/spade/ObjectIO.py | |||
469 | +++ b/spade/ObjectIO.py | |||
470 | @@ -2,7 +2,7 @@ import os | |||
471 | 2 | from spade.BucketObject import BucketError | 2 | from spade.BucketObject import BucketError |
472 | 3 | 3 | ||
473 | 4 | 4 | ||
475 | 5 | class ObjectIO(): | 5 | class ObjectIO: |
476 | 6 | def __init__(self, parent, bucketName, objectName, blockSize=-1): | 6 | def __init__(self, parent, bucketName, objectName, blockSize=-1): |
477 | 7 | self.parent = parent | 7 | self.parent = parent |
478 | 8 | self.bucketName = bucketName | 8 | self.bucketName = bucketName |
479 | @@ -36,10 +36,7 @@ class ObjectIO(): | |||
480 | 36 | if self.closed: | 36 | if self.closed: |
481 | 37 | raise ValueError | 37 | raise ValueError |
482 | 38 | 38 | ||
487 | 39 | data = self.parent.getObject(self.bucketName, | 39 | data = self.parent.getObject(self.bucketName, self.objectName, self.pos, min(self.pos + n - 1, self.len)) |
484 | 40 | self.objectName, | ||
485 | 41 | self.pos, | ||
486 | 42 | min(self.pos + n - 1, self.len)) | ||
488 | 43 | self.pos = self.pos + len(data) | 40 | self.pos = self.pos + len(data) |
489 | 44 | return data | 41 | return data |
490 | 45 | 42 | ||
491 | @@ -56,19 +53,19 @@ class ObjectIO(): | |||
492 | 56 | while self.readlinePos < len(self.readlineBuffer): | 53 | while self.readlinePos < len(self.readlineBuffer): |
493 | 57 | lineEndPos = self.readlineBuffer.find(b"\n", self.readlinePos) | 54 | lineEndPos = self.readlineBuffer.find(b"\n", self.readlinePos) |
494 | 58 | if lineEndPos >= 0: | 55 | if lineEndPos >= 0: |
496 | 59 | line = self.readlineBuffer[self.readlinePos:lineEndPos + 1].decode('utf-8') | 56 | line = self.readlineBuffer[self.readlinePos : lineEndPos + 1].decode('utf-8') |
497 | 60 | self.readlinePos = lineEndPos + 1 | 57 | self.readlinePos = lineEndPos + 1 |
498 | 61 | return line | 58 | return line |
499 | 62 | else: | 59 | else: |
500 | 63 | newData = self.read(self.blockSize) | 60 | newData = self.read(self.blockSize) |
501 | 64 | if not newData: | 61 | if not newData: |
502 | 65 | return | 62 | return |
504 | 66 | self.readlineBuffer = self.readlineBuffer[self.readlinePos:] + newData | 63 | self.readlineBuffer = self.readlineBuffer[self.readlinePos :] + newData |
505 | 67 | self.readlinePos = 0 | 64 | self.readlinePos = 0 |
506 | 68 | 65 | ||
507 | 69 | # In case the last line has no newline | 66 | # In case the last line has no newline |
508 | 70 | if self.readlinePos < len(self.readlineBuffer): | 67 | if self.readlinePos < len(self.readlineBuffer): |
510 | 71 | return self.readlineBuffer[self.readlinePos:] | 68 | return self.readlineBuffer[self.readlinePos :] |
511 | 72 | 69 | ||
512 | 73 | def readlines(self): | 70 | def readlines(self): |
513 | 74 | while True: | 71 | while True: |
514 | diff --git a/spade/S3Bucket.py b/spade/S3Bucket.py | |||
515 | index 6d3036e..67f38fb 100644 | |||
516 | --- a/spade/S3Bucket.py | |||
517 | +++ b/spade/S3Bucket.py | |||
518 | @@ -25,15 +25,13 @@ from spade.ObjectIO import ObjectIO | |||
519 | 25 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | 25 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 |
520 | 26 | DEFAULT_DELIMITER = "/" | 26 | DEFAULT_DELIMITER = "/" |
521 | 27 | MAX_S3_OBJECTSIZE = 5 * 1024 * 1024 * 1024 # S3 object PUT limit = 5GB | 27 | MAX_S3_OBJECTSIZE = 5 * 1024 * 1024 * 1024 # S3 object PUT limit = 5GB |
523 | 28 | S3_SEGMENTSIZE = 1024 * 1024 * 1024 # Use 1GB segments for large object uploads | 28 | S3_SEGMENTSIZE = 1024 * 1024 * 1024 # Use 1GB segments for large object uploads |
524 | 29 | USER_META_PREFIX = "x-amz-meta-" | 29 | USER_META_PREFIX = "x-amz-meta-" |
525 | 30 | 30 | ||
526 | 31 | 31 | ||
528 | 32 | class S3Bucket(): | 32 | class S3Bucket: |
529 | 33 | def __init__(self, creds): | 33 | def __init__(self, creds): |
533 | 34 | wanted = ["aws_access_key_id", | 34 | wanted = ["aws_access_key_id", "aws_secret_access_key", "aws_region"] |
531 | 35 | "aws_secret_access_key", | ||
532 | 36 | "aws_region"] | ||
534 | 37 | for k in wanted: | 35 | for k in wanted: |
535 | 38 | if k not in creds: | 36 | if k not in creds: |
536 | 39 | # Missing creds | 37 | # Missing creds |
537 | @@ -44,8 +42,9 @@ class S3Bucket(): | |||
538 | 44 | 42 | ||
539 | 45 | def oldLibraryFixes(self): | 43 | def oldLibraryFixes(self): |
540 | 46 | try: | 44 | try: |
543 | 47 | self.regions = [{"name": region.name, "endpoint": region.endpoint} | 45 | self.regions = [ |
544 | 48 | for region in boto.regioninfo.get_regions("s3")] | 46 | {"name": region.name, "endpoint": region.endpoint} for region in boto.regioninfo.get_regions("s3") |
545 | 47 | ] | ||
546 | 49 | except AttributeError: | 48 | except AttributeError: |
547 | 50 | # Old boto library doesn't have this, so we'll use the ones we know about | 49 | # Old boto library doesn't have this, so we'll use the ones we know about |
548 | 51 | self.regions = [ | 50 | self.regions = [ |
549 | @@ -63,9 +62,22 @@ class S3Bucket(): | |||
550 | 63 | {'name': 'us-east-1', 'endpoint': 's3.amazonaws.com'}, | 62 | {'name': 'us-east-1', 'endpoint': 's3.amazonaws.com'}, |
551 | 64 | ] | 63 | ] |
552 | 65 | 64 | ||
556 | 66 | self.base_fields = set(['content-length', 'content-language', 'content-disposition', | 65 | self.base_fields = set( |
557 | 67 | 'content-encoding', 'expires', 'content-md5', 'last-modified', | 66 | [ |
558 | 68 | 'etag', 'cache-control', 'date', 'content-type', 'x-robots-tag']) | 67 | 'content-length', |
559 | 68 | 'content-language', | ||
560 | 69 | 'content-disposition', | ||
561 | 70 | 'content-encoding', | ||
562 | 71 | 'expires', | ||
563 | 72 | 'content-md5', | ||
564 | 73 | 'last-modified', | ||
565 | 74 | 'etag', | ||
566 | 75 | 'cache-control', | ||
567 | 76 | 'date', | ||
568 | 77 | 'content-type', | ||
569 | 78 | 'x-robots-tag', | ||
570 | 79 | ] | ||
571 | 80 | ) | ||
572 | 69 | 81 | ||
573 | 70 | def _connect(self): | 82 | def _connect(self): |
574 | 71 | self.endpoint = None | 83 | self.endpoint = None |
575 | @@ -78,11 +90,13 @@ class S3Bucket(): | |||
576 | 78 | if not self.endpoint: | 90 | if not self.endpoint: |
577 | 79 | raise NotImplementedError | 91 | raise NotImplementedError |
578 | 80 | 92 | ||
584 | 81 | self.s3 = boto.s3.connection.S3Connection(self.creds['aws_access_key_id'], | 93 | self.s3 = boto.s3.connection.S3Connection( |
585 | 82 | self.creds['aws_secret_access_key'], | 94 | self.creds['aws_access_key_id'], |
586 | 83 | is_secure=True, | 95 | self.creds['aws_secret_access_key'], |
587 | 84 | host=self.endpoint, | 96 | is_secure=True, |
588 | 85 | calling_format=OrdinaryCallingFormat()) | 97 | host=self.endpoint, |
589 | 98 | calling_format=OrdinaryCallingFormat(), | ||
590 | 99 | ) | ||
591 | 86 | 100 | ||
592 | 87 | # Boto/S3 gives a 301 redirect response if we try to access a bucket | 101 | # Boto/S3 gives a 301 redirect response if we try to access a bucket |
593 | 88 | # in another region, so make sure we're in the right one | 102 | # in another region, so make sure we're in the right one |
594 | @@ -94,7 +108,7 @@ class S3Bucket(): | |||
595 | 94 | 108 | ||
596 | 95 | # Strip off the first section (the current region) | 109 | # Strip off the first section (the current region) |
597 | 96 | try: | 110 | try: |
599 | 97 | websiteEndpoint = websiteEndpoint[websiteEndpoint.find(".") + 1:] | 111 | websiteEndpoint = websiteEndpoint[websiteEndpoint.find(".") + 1 :] |
600 | 98 | except UnboundLocalError: | 112 | except UnboundLocalError: |
601 | 99 | raise BucketError("S3 Failed to find bucket") | 113 | raise BucketError("S3 Failed to find bucket") |
602 | 100 | 114 | ||
603 | @@ -176,10 +190,12 @@ class S3Bucket(): | |||
604 | 176 | return metadata.rawMetadata.get("etag", "").strip('"') | 190 | return metadata.rawMetadata.get("etag", "").strip('"') |
605 | 177 | 191 | ||
606 | 178 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): | 192 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): |
611 | 179 | systemMetadata = ["x-amz-server-side-encryption", | 193 | systemMetadata = [ |
612 | 180 | "x-amz-storage-class", | 194 | "x-amz-server-side-encryption", |
613 | 181 | "x-amz-website-redirect-location", | 195 | "x-amz-storage-class", |
614 | 182 | "x-amz-server-side-encryption-aws-kms-key-id"] | 196 | "x-amz-website-redirect-location", |
615 | 197 | "x-amz-server-side-encryption-aws-kms-key-id", | ||
616 | 198 | ] | ||
617 | 183 | self.confirmRegion(bucketName) | 199 | self.confirmRegion(bucketName) |
618 | 184 | 200 | ||
619 | 185 | # Metadata to remove (if any) | 201 | # Metadata to remove (if any) |
620 | @@ -300,19 +316,17 @@ class S3Bucket(): | |||
621 | 300 | return | 316 | return |
622 | 301 | 317 | ||
623 | 302 | aclTypes = ["list", "read", "write", "readacl", "writeacl"] | 318 | aclTypes = ["list", "read", "write", "readacl", "writeacl"] |
629 | 303 | aclTypeMap = {"read": "READ", | 319 | aclTypeMap = {"read": "READ", "write": "WRITE", "list": "READ", "readacl": "READ_ACP", "writeacl": "WRITE_ACP"} |
625 | 304 | "write": "WRITE", | ||
626 | 305 | "list": "READ", | ||
627 | 306 | "readacl": "READ_ACP", | ||
628 | 307 | "writeacl": "WRITE_ACP"} | ||
630 | 308 | 320 | ||
631 | 309 | def createAclData(self, user, type): | 321 | def createAclData(self, user, type): |
638 | 310 | aclData = {"s3_display_name": None, | 322 | aclData = { |
639 | 311 | "s3_id": user, | 323 | "s3_display_name": None, |
640 | 312 | "s3_uri": None, | 324 | "s3_id": user, |
641 | 313 | "s3_email_address": None, | 325 | "s3_uri": None, |
642 | 314 | "s3_type": "CanonicalUser", | 326 | "s3_email_address": None, |
643 | 315 | "s3_permission": self.aclTypeMap[type]} | 327 | "s3_type": "CanonicalUser", |
644 | 328 | "s3_permission": self.aclTypeMap[type], | ||
645 | 329 | } | ||
646 | 316 | return aclData | 330 | return aclData |
647 | 317 | 331 | ||
648 | 318 | def translateAcl(self, aclData, aliases): | 332 | def translateAcl(self, aclData, aliases): |
649 | @@ -343,12 +357,14 @@ class S3Bucket(): | |||
650 | 343 | 357 | ||
651 | 344 | acls = BucketAcl() | 358 | acls = BucketAcl() |
652 | 345 | for grant in acl.acl.grants: | 359 | for grant in acl.acl.grants: |
659 | 346 | aclData = {"s3_display_name": grant.display_name, | 360 | aclData = { |
660 | 347 | "s3_id": grant.id, | 361 | "s3_display_name": grant.display_name, |
661 | 348 | "s3_uri": grant.uri, | 362 | "s3_id": grant.id, |
662 | 349 | "s3_email_address": grant.email_address, | 363 | "s3_uri": grant.uri, |
663 | 350 | "s3_type": grant.type, | 364 | "s3_email_address": grant.email_address, |
664 | 351 | "s3_permission": grant.permission} | 365 | "s3_type": grant.type, |
665 | 366 | "s3_permission": grant.permission, | ||
666 | 367 | } | ||
667 | 352 | if grant.permission in ["READ", "FULL_CONTROL"]: | 368 | if grant.permission in ["READ", "FULL_CONTROL"]: |
668 | 353 | if objectName: | 369 | if objectName: |
669 | 354 | acls.add("read", aclData) | 370 | acls.add("read", aclData) |
670 | @@ -392,12 +408,14 @@ class S3Bucket(): | |||
671 | 392 | uniqueAcls.append(user) | 408 | uniqueAcls.append(user) |
672 | 393 | 409 | ||
673 | 394 | for user in uniqueAcls: | 410 | for user in uniqueAcls: |
680 | 395 | grant = boto.s3.acl.Grant(permission=user["s3_permission"], | 411 | grant = boto.s3.acl.Grant( |
681 | 396 | type=user["s3_type"], | 412 | permission=user["s3_permission"], |
682 | 397 | id=user["s3_id"], | 413 | type=user["s3_type"], |
683 | 398 | display_name=user["s3_display_name"], | 414 | id=user["s3_id"], |
684 | 399 | uri=user["s3_uri"], | 415 | display_name=user["s3_display_name"], |
685 | 400 | email_address=["s3_email_address"]) | 416 | uri=user["s3_uri"], |
686 | 417 | email_address=["s3_email_address"], | ||
687 | 418 | ) | ||
688 | 401 | s3acl.acl.add_grant(grant) | 419 | s3acl.acl.add_grant(grant) |
689 | 402 | 420 | ||
690 | 403 | try: | 421 | try: |
691 | diff --git a/spade/Spade.py b/spade/Spade.py | |||
692 | index b8f4b12..5ebdfc0 100644 | |||
693 | --- a/spade/Spade.py | |||
694 | +++ b/spade/Spade.py | |||
695 | @@ -6,21 +6,25 @@ import re | |||
696 | 6 | import json | 6 | import json |
697 | 7 | import fnmatch | 7 | import fnmatch |
698 | 8 | from spade.BucketObject import BucketObject, BucketError | 8 | from spade.BucketObject import BucketObject, BucketError |
699 | 9 | |||
700 | 9 | try: | 10 | try: |
701 | 10 | from urllib.parse import urlparse | 11 | from urllib.parse import urlparse |
702 | 11 | except ImportError: | 12 | except ImportError: |
703 | 12 | from urlparse import urlparse | 13 | from urlparse import urlparse |
704 | 13 | 14 | ||
705 | 14 | import gzip | 15 | import gzip |
706 | 16 | |||
707 | 15 | compressionMethods = ["gzip"] | 17 | compressionMethods = ["gzip"] |
708 | 16 | if sys.version_info.major >= 3 and sys.version_info.minor >= 3: | 18 | if sys.version_info.major >= 3 and sys.version_info.minor >= 3: |
709 | 17 | import bz2 | 19 | import bz2 |
710 | 20 | |||
711 | 18 | compressionMethods.append("bzip2") | 21 | compressionMethods.append("bzip2") |
712 | 19 | import lzma | 22 | import lzma |
713 | 23 | |||
714 | 20 | compressionMethods.append("xz") | 24 | compressionMethods.append("xz") |
715 | 21 | else: | 25 | else: |
716 | 22 | # Define a fake lzma.LZMAError | 26 | # Define a fake lzma.LZMAError |
718 | 23 | class lzma(): | 27 | class lzma: |
719 | 24 | class LZMAError(Exception): | 28 | class LZMAError(Exception): |
720 | 25 | pass | 29 | pass |
721 | 26 | 30 | ||
722 | @@ -29,7 +33,7 @@ DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | |||
723 | 29 | DEFAULT_DELIMITER = "/" | 33 | DEFAULT_DELIMITER = "/" |
724 | 30 | 34 | ||
725 | 31 | 35 | ||
727 | 32 | class Result(): | 36 | class Result: |
728 | 33 | def __init__(self): | 37 | def __init__(self): |
729 | 34 | self.output = [] | 38 | self.output = [] |
730 | 35 | self.error = None | 39 | self.error = None |
731 | @@ -38,7 +42,7 @@ class Result(): | |||
732 | 38 | return "<Result object. output: '{}' error: '{}'>".format(self.output, self.error) | 42 | return "<Result object. output: '{}' error: '{}'>".format(self.output, self.error) |
733 | 39 | 43 | ||
734 | 40 | 44 | ||
736 | 41 | class Bucket(): | 45 | class Bucket: |
737 | 42 | def __init__(self, configFile): | 46 | def __init__(self, configFile): |
738 | 43 | self.configFile = configFile | 47 | self.configFile = configFile |
739 | 44 | self.config = json.loads(open(configFile, 'r').read()) | 48 | self.config = json.loads(open(configFile, 'r').read()) |
740 | @@ -102,21 +106,27 @@ class Bucket(): | |||
741 | 102 | 106 | ||
742 | 103 | if bucketType == "swift": | 107 | if bucketType == "swift": |
743 | 104 | from spade.SwiftBucket import SwiftBucket | 108 | from spade.SwiftBucket import SwiftBucket |
744 | 109 | |||
745 | 105 | return SwiftBucket(self.creds[scheme]) | 110 | return SwiftBucket(self.creds[scheme]) |
746 | 106 | elif bucketType == "s3": | 111 | elif bucketType == "s3": |
747 | 107 | from spade.S3Bucket import S3Bucket | 112 | from spade.S3Bucket import S3Bucket |
748 | 113 | |||
749 | 108 | return S3Bucket(self.creds[scheme]) | 114 | return S3Bucket(self.creds[scheme]) |
750 | 109 | elif bucketType == "gce": | 115 | elif bucketType == "gce": |
751 | 110 | from spade.GCEBucket import GCEBucket | 116 | from spade.GCEBucket import GCEBucket |
752 | 117 | |||
753 | 111 | return GCEBucket(self.creds[scheme]) | 118 | return GCEBucket(self.creds[scheme]) |
754 | 112 | elif bucketType == "azure": | 119 | elif bucketType == "azure": |
755 | 113 | from spade.AzureBucket import AzureBucket | 120 | from spade.AzureBucket import AzureBucket |
756 | 121 | |||
757 | 114 | return AzureBucket(self.creds[scheme]) | 122 | return AzureBucket(self.creds[scheme]) |
758 | 115 | elif bucketType == "local": | 123 | elif bucketType == "local": |
759 | 116 | from spade.LocalBucket import LocalBucket | 124 | from spade.LocalBucket import LocalBucket |
760 | 125 | |||
761 | 117 | return LocalBucket(self.creds[scheme]) | 126 | return LocalBucket(self.creds[scheme]) |
762 | 118 | elif bucketType == "stdio": | 127 | elif bucketType == "stdio": |
763 | 119 | from spade.StdioBucket import StdioBucket | 128 | from spade.StdioBucket import StdioBucket |
764 | 129 | |||
765 | 120 | return StdioBucket(self.creds[scheme]) | 130 | return StdioBucket(self.creds[scheme]) |
766 | 121 | else: | 131 | else: |
767 | 122 | raise NotImplementedError | 132 | raise NotImplementedError |
768 | @@ -449,11 +459,7 @@ class Bucket(): | |||
769 | 449 | ret.error = e | 459 | ret.error = e |
770 | 450 | yield ret | 460 | yield ret |
771 | 451 | try: | 461 | try: |
777 | 452 | dstBucket.putObject(dstUrl.netloc, | 462 | dstBucket.putObject(dstUrl.netloc, dstObjName, fSrc, objMetadata.size, blockSize=args.blockSize) |
773 | 453 | dstObjName, | ||
774 | 454 | fSrc, | ||
775 | 455 | objMetadata.size, | ||
776 | 456 | blockSize=args.blockSize) | ||
778 | 457 | except BucketError as e: | 463 | except BucketError as e: |
779 | 458 | ret.error = e | 464 | ret.error = e |
780 | 459 | fSrc.close() | 465 | fSrc.close() |
781 | diff --git a/spade/StdioBucket.py b/spade/StdioBucket.py | |||
782 | index ef6cb43..31ba1f6 100644 | |||
783 | --- a/spade/StdioBucket.py | |||
784 | +++ b/spade/StdioBucket.py | |||
785 | @@ -8,7 +8,7 @@ from spade.ObjectIO import ObjectIO | |||
786 | 8 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | 8 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 |
787 | 9 | 9 | ||
788 | 10 | 10 | ||
790 | 11 | class StdioBucket(): | 11 | class StdioBucket: |
791 | 12 | def __init__(self, creds): | 12 | def __init__(self, creds): |
792 | 13 | return | 13 | return |
793 | 14 | 14 | ||
794 | diff --git a/spade/SwiftBucket.py b/spade/SwiftBucket.py | |||
795 | index 2125b85..0c3adb0 100644 | |||
796 | --- a/spade/SwiftBucket.py | |||
797 | +++ b/spade/SwiftBucket.py | |||
798 | @@ -23,19 +23,15 @@ from spade.ObjectIO import ObjectIO | |||
799 | 23 | 23 | ||
800 | 24 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 | 24 | DEFAULT_BLOCKSIZE = 1024 * 1024 * 10 |
801 | 25 | DEFAULT_DELIMITER = "/" | 25 | DEFAULT_DELIMITER = "/" |
804 | 26 | MAX_SWIFT_OBJECTSIZE = 5 * 1024 * 1024 * 1024 # Swift object limit = 5GB | 26 | MAX_SWIFT_OBJECTSIZE = 5 * 1024 * 1024 * 1024 # Swift object limit = 5GB |
805 | 27 | SWIFT_SEGMENTSIZE = 1024 * 1024 * 1024 # Use 1GB segments for large object uploads | 27 | SWIFT_SEGMENTSIZE = 1024 * 1024 * 1024 # Use 1GB segments for large object uploads |
806 | 28 | USER_META_PREFIX = "x-object-meta-" | 28 | USER_META_PREFIX = "x-object-meta-" |
807 | 29 | ACL_META_PREFIX = "x-container-" | 29 | ACL_META_PREFIX = "x-container-" |
808 | 30 | 30 | ||
809 | 31 | 31 | ||
811 | 32 | class SwiftBucket(): | 32 | class SwiftBucket: |
812 | 33 | def __init__(self, creds): | 33 | def __init__(self, creds): |
818 | 34 | wanted = ["os_username", | 34 | wanted = ["os_username", "os_password", "os_tenant_name", "os_auth_url", "os_region_name"] |
814 | 35 | "os_password", | ||
815 | 36 | "os_tenant_name", | ||
816 | 37 | "os_auth_url", | ||
817 | 38 | "os_region_name"] | ||
819 | 39 | for k in wanted: | 35 | for k in wanted: |
820 | 40 | if k not in creds: | 36 | if k not in creds: |
821 | 41 | # Missing creds | 37 | # Missing creds |
822 | @@ -51,7 +47,8 @@ class SwiftBucket(): | |||
823 | 51 | key=creds["os_password"], | 47 | key=creds["os_password"], |
824 | 52 | retries=5, | 48 | retries=5, |
825 | 53 | auth_version="2.0", | 49 | auth_version="2.0", |
827 | 54 | os_options=options) | 50 | os_options=options, |
828 | 51 | ) | ||
829 | 55 | 52 | ||
830 | 56 | def standardTimestamp(self, timestamp): | 53 | def standardTimestamp(self, timestamp): |
831 | 57 | try: | 54 | try: |
832 | @@ -109,8 +106,7 @@ class SwiftBucket(): | |||
833 | 109 | return co | 106 | return co |
834 | 110 | 107 | ||
835 | 111 | def setBucketMetadata(self, bucketName, metadata, overwrite=False): # noqa: C901 | 108 | def setBucketMetadata(self, bucketName, metadata, overwrite=False): # noqa: C901 |
838 | 112 | systemMetadata = ["content-type", "x-container-bytes-used", | 109 | systemMetadata = ["content-type", "x-container-bytes-used", "x-container-object-count:", "x-timestamp"] |
837 | 113 | "x-container-object-count:", "x-timestamp"] | ||
839 | 114 | newMetadata = {} | 110 | newMetadata = {} |
840 | 115 | # Keep existing system metadata and (optionally) user metadata | 111 | # Keep existing system metadata and (optionally) user metadata |
841 | 116 | m = self.getBucketMetadata(bucketName) | 112 | m = self.getBucketMetadata(bucketName) |
842 | @@ -163,8 +159,14 @@ class SwiftBucket(): | |||
843 | 163 | return metadata.rawMetadata.get("etag", "").strip('"') | 159 | return metadata.rawMetadata.get("etag", "").strip('"') |
844 | 164 | 160 | ||
845 | 165 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): | 161 | def setObjectMetadata(self, bucketName, objectName, metadata, overwrite=False): |
848 | 166 | systemMetadata = ["content-type", "content-encoding", "content-disposition", | 162 | systemMetadata = [ |
849 | 167 | "x-delete-at", "x-delete-after", "etag"] | 163 | "content-type", |
850 | 164 | "content-encoding", | ||
851 | 165 | "content-disposition", | ||
852 | 166 | "x-delete-at", | ||
853 | 167 | "x-delete-after", | ||
854 | 168 | "etag", | ||
855 | 169 | ] | ||
856 | 168 | newMetadata = {} | 170 | newMetadata = {} |
857 | 169 | # Keep existing system metadata and (optionally) user metadata | 171 | # Keep existing system metadata and (optionally) user metadata |
858 | 170 | m = self.getObjectMetadata(bucketName, objectName) | 172 | m = self.getObjectMetadata(bucketName, objectName) |
859 | @@ -223,11 +225,13 @@ class SwiftBucket(): | |||
860 | 223 | segmentName = '{}/{}/{}/{}/{:08d}'.format(objectName, mtime, size, SWIFT_SEGMENTSIZE, segmentId) | 225 | segmentName = '{}/{}/{}/{}/{:08d}'.format(objectName, mtime, size, SWIFT_SEGMENTSIZE, segmentId) |
861 | 224 | segmentId += 1 | 226 | segmentId += 1 |
862 | 225 | try: | 227 | try: |
868 | 226 | self.swift.put_object(segmentBucketName, | 228 | self.swift.put_object( |
869 | 227 | segmentName, | 229 | segmentBucketName, |
870 | 228 | src, | 230 | segmentName, |
871 | 229 | content_length=segmentSize, | 231 | src, |
872 | 230 | content_type="application/octet-stream") | 232 | content_length=segmentSize, |
873 | 233 | content_type="application/octet-stream", | ||
874 | 234 | ) | ||
875 | 231 | except swiftclient.ClientException: | 235 | except swiftclient.ClientException: |
876 | 232 | raise BucketError("Swift Failed to put large object segment") | 236 | raise BucketError("Swift Failed to put large object segment") |
877 | 233 | bytesRead += segmentSize | 237 | bytesRead += segmentSize |
878 | @@ -239,12 +243,9 @@ class SwiftBucket(): | |||
879 | 239 | 'x-object-meta-mtime': mtime, | 243 | 'x-object-meta-mtime': mtime, |
880 | 240 | } | 244 | } |
881 | 241 | try: | 245 | try: |
888 | 242 | self.swift.put_object(bucketName, | 246 | self.swift.put_object( |
889 | 243 | objectName, | 247 | bucketName, objectName, '', content_length=0, content_type="application/octet-stream", headers=headers |
890 | 244 | '', | 248 | ) |
885 | 245 | content_length=0, | ||
886 | 246 | content_type="application/octet-stream", | ||
887 | 247 | headers=headers) | ||
891 | 248 | except swiftclient.ClientException: | 249 | except swiftclient.ClientException: |
892 | 249 | raise BucketError("Swift Failed to put large object manifest") | 250 | raise BucketError("Swift Failed to put large object manifest") |
893 | 250 | return | 251 | return |
894 | @@ -254,10 +255,7 @@ class SwiftBucket(): | |||
895 | 254 | return self.putLargeObject(bucketName, objectName, src, size, min(blockSize, SWIFT_SEGMENTSIZE)) | 255 | return self.putLargeObject(bucketName, objectName, src, size, min(blockSize, SWIFT_SEGMENTSIZE)) |
896 | 255 | 256 | ||
897 | 256 | try: | 257 | try: |
902 | 257 | self.swift.put_object(bucketName, | 258 | self.swift.put_object(bucketName, objectName, src, content_type="application/octet-stream") |
899 | 258 | objectName, | ||
900 | 259 | src, | ||
901 | 260 | content_type="application/octet-stream") | ||
903 | 261 | except swiftclient.exceptions.ClientException: | 259 | except swiftclient.exceptions.ClientException: |
904 | 262 | raise BucketError("Swift Failed to put object") | 260 | raise BucketError("Swift Failed to put object") |
905 | 263 | return | 261 | return |