1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
5 import concurrent
.futures
as futures
10 from pprint
import pprint
15 from mozbuild
.util
import memoize
19 def create_aws_session():
21 This function creates an aws session that is
22 shared between upload and delete both.
25 level
= os
.environ
.get("MOZ_SCM_LEVEL", "1")
27 "1": "gecko-docs.mozilla.org-l1",
28 "2": "gecko-docs.mozilla.org-l2",
29 "3": "gecko-docs.mozilla.org",
31 secrets_url
= "http://taskcluster/secrets/v1/secret/"
32 secrets_url
+= "project/releng/gecko/build/level-{}/gecko-docs-upload".format(level
)
34 # Get the credentials from the TC secrets service. Note that these
35 # differ per SCM level
36 if "TASK_ID" in os
.environ
:
37 print("Using AWS credentials from the secrets service")
38 session
= requests
.Session()
39 res
= session
.get(secrets_url
)
40 res
.raise_for_status()
41 secret
= res
.json()["secret"]
42 session
= boto3
.session
.Session(
43 aws_access_key_id
=secret
["AWS_ACCESS_KEY_ID"],
44 aws_secret_access_key
=secret
["AWS_SECRET_ACCESS_KEY"],
48 print("Trying to use your AWS credentials..")
49 session
= boto3
.session
.Session(region_name
=region
)
51 s3
= session
.client("s3", config
=botocore
.client
.Config(max_pool_connections
=20))
57 def get_s3_keys(s3
, bucket
):
58 kwargs
= {"Bucket": bucket
}
61 response
= s3
.list_objects_v2(**kwargs
)
62 for obj
in response
["Contents"]:
63 all_keys
.append(obj
["Key"])
66 kwargs
["ContinuationToken"] = response
["NextContinuationToken"]
73 def s3_set_redirects(redirects
):
75 s3
, bucket
= create_aws_session()
77 configuration
= {"IndexDocument": {"Suffix": "index.html"}, "RoutingRules": []}
79 for path
, redirect
in redirects
.items():
81 "Condition": {"KeyPrefixEquals": path
},
82 "Redirect": {"ReplaceKeyPrefixWith": redirect
},
84 if os
.environ
.get("MOZ_SCM_LEVEL") == "3":
85 rule
["Redirect"]["HostName"] = "firefox-source-docs.mozilla.org"
87 configuration
["RoutingRules"].append(rule
)
89 s3
.put_bucket_website(
91 WebsiteConfiguration
=configuration
,
95 def s3_delete_missing(files
, key_prefix
=None):
96 """Delete files in the S3 bucket.
98 Delete files on the S3 bucket that doesn't match the files
99 given as the param. If the key_prefix is not specified, missing
100 files that has main/ as a prefix will be removed. Otherwise, it
101 will remove files with the same prefix as key_prefix.
103 s3
, bucket
= create_aws_session()
104 files_on_server
= get_s3_keys(s3
, bucket
)
107 path
for path
in files_on_server
if path
.startswith(key_prefix
)
111 path
for path
in files_on_server
if not path
.startswith("main/")
113 files
= [key_prefix
+ "/" + path
if key_prefix
else path
for path
, f
in files
]
114 files_to_delete
= [path
for path
in files_on_server
if path
not in files
]
117 while files_to_delete
:
118 keys_to_remove
= [{"Key": key
} for key
in files_to_delete
[:query_size
]]
119 response
= s3
.delete_objects(
122 "Objects": keys_to_remove
,
125 pprint(response
, indent
=2)
126 files_to_delete
= files_to_delete
[query_size
:]
129 def s3_upload(files
, key_prefix
=None):
130 """Upload files to an S3 bucket.
132 ``files`` is an iterable of ``(path, BaseFile)`` (typically from a
135 Keys in the bucket correspond to source filenames. If ``key_prefix`` is
136 defined, key names will be ``<key_prefix>/<path>``.
138 s3
, bucket
= create_aws_session()
140 def upload(f
, path
, bucket
, key
, extra_args
):
141 # Need to flush to avoid buffering/interleaving from multiple threads.
142 sys
.stdout
.write("uploading %s to %s\n" % (path
, key
))
144 s3
.upload_fileobj(f
, bucket
, key
, ExtraArgs
=extra_args
)
147 with futures
.ThreadPoolExecutor(20) as e
:
148 for path
, f
in files
:
149 content_type
, content_encoding
= mimetypes
.guess_type(path
)
152 if content_type
.startswith("text/"):
153 content_type
+= '; charset="utf-8"'
154 extra_args
["ContentType"] = content_type
156 extra_args
["ContentEncoding"] = content_encoding
159 key
= "%s/%s" % (key_prefix
, path
)
163 # The file types returned by mozpack behave like file objects. But
164 # they don't accept an argument to read(). So we wrap in a BytesIO.
166 e
.submit(upload
, io
.BytesIO(f
.read()), path
, bucket
, key
, extra_args
)
169 s3_delete_missing(files
, key_prefix
)
170 # Need to do this to catch any exceptions.