194 lines
6.1 KiB
Python
Executable File
194 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import logging
|
|
import re
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
|
|
from cmd_utils import REPO_ROOT, Sh, init_log
|
|
|
|
DATA_PY = REPO_ROOT / "ci" / "jenkins" / "data.py"
|
|
|
|
|
|
def load_files_to_stash():
|
|
"""Load the files_to_stash dict from ci/jenkins/data.py."""
|
|
spec = importlib.util.spec_from_file_location("data", DATA_PY)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod.files_to_stash
|
|
|
|
|
|
def resolve_bundles(bundle_names: list[str]) -> list[str]:
|
|
"""Resolve a list of bundle names to a flat list of file paths."""
|
|
files_to_stash = load_files_to_stash()
|
|
items = []
|
|
for name in bundle_names:
|
|
if name not in files_to_stash:
|
|
known = list(files_to_stash.keys())
|
|
logging.error(f"Unknown bundle '{name}'. Known bundles: {known}")
|
|
raise SystemExit(1)
|
|
items.extend(files_to_stash[name])
|
|
return items
|
|
|
|
|
|
RETRY_SCRIPT = REPO_ROOT / "ci" / "scripts" / "jenkins" / "retry.sh"
|
|
S3_DOWNLOAD_REGEX = re.compile(r"download: s3://.* to (.*)")
|
|
SH = Sh()
|
|
|
|
|
|
class Action(Enum):
|
|
UPLOAD = 1
|
|
DOWNLOAD = 2
|
|
|
|
|
|
def show_md5(item: str) -> None:
|
|
if not Path(item).is_dir():
|
|
sh.run(f"md5sum {item}")
|
|
|
|
|
|
def parse_output_files(stdout: str) -> list[str]:
|
|
"""
|
|
Grab the list of downloaded files from the output of 'aws s3 cp'. Lines look
|
|
like:
|
|
|
|
download: s3://some/prefix/a_file.txt to a_file.txt
|
|
"""
|
|
files = []
|
|
for line in stdout.split("\n"):
|
|
line = line.strip()
|
|
if line == "":
|
|
continue
|
|
m = S3_DOWNLOAD_REGEX.match(line)
|
|
if m:
|
|
files.append(m.groups()[0])
|
|
|
|
return files
|
|
|
|
|
|
def chmod(files: list[str]) -> None:
|
|
"""
|
|
S3 has no concept of file permissions so add them back in here to every file
|
|
"""
|
|
# Add execute bit for downloads
|
|
to_chmod = [str(f) for f in files]
|
|
logging.info(f"Adding execute bit for files: {to_chmod}")
|
|
if len(to_chmod) > 0:
|
|
SH.run(f"chmod +x {' '.join(to_chmod)}")
|
|
|
|
|
|
def s3(source: str, destination: str, recursive: bool) -> list[str]:
|
|
"""
|
|
Send or download the source to the destination in S3
|
|
"""
|
|
cmd = f". {RETRY_SCRIPT.relative_to(REPO_ROOT)} && retry 3 aws s3 cp --no-progress"
|
|
|
|
if recursive:
|
|
cmd += " --recursive"
|
|
|
|
cmd += f" {source} {destination}"
|
|
_, stdout = SH.tee(cmd)
|
|
return stdout
|
|
|
|
|
|
if __name__ == "__main__":
|
|
init_log()
|
|
help = "Uploads or downloads files from S3"
|
|
parser = argparse.ArgumentParser(description=help)
|
|
parser.add_argument("--action", help="either 'upload' or 'download'", required=True)
|
|
parser.add_argument("--bucket", help="s3 bucket", required=True)
|
|
parser.add_argument(
|
|
"--prefix", help="s3 bucket + tag (e.g. s3://tvm-ci-prod/PR-1234/cpu", required=True
|
|
)
|
|
parser.add_argument("--items", help="files and folders to upload", nargs="+")
|
|
parser.add_argument(
|
|
"--bundle",
|
|
help="bundle name(s) from ci/jenkins/data.py files_to_stash (repeatable)",
|
|
action="append",
|
|
dest="bundles",
|
|
metavar="NAME",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.items is not None and args.bundles is not None:
|
|
parser.error("--items and --bundle are mutually exclusive")
|
|
|
|
logging.info(args)
|
|
|
|
sh = Sh()
|
|
|
|
if Path.cwd() != REPO_ROOT:
|
|
logging.error(f"s3.py can only be executed from the repo root, instead was in {Path.cwd()}")
|
|
exit(1)
|
|
|
|
prefix = args.prefix.strip("/")
|
|
s3_path = f"s3://{args.bucket}/{prefix}"
|
|
logging.info(f"Using s3 path: {s3_path}")
|
|
|
|
if args.action == "upload":
|
|
action = Action.UPLOAD
|
|
elif args.action == "download":
|
|
action = Action.DOWNLOAD
|
|
else:
|
|
logging.error(f"Unsupported action: {args.action}")
|
|
exit(1)
|
|
|
|
if args.bundles is not None:
|
|
items = resolve_bundles(args.bundles)
|
|
elif args.items is not None:
|
|
items = args.items
|
|
else:
|
|
if args.action == "upload":
|
|
logging.error("Cannot upload without --items or --bundle")
|
|
exit(1)
|
|
else:
|
|
# Download the whole prefix
|
|
items = ["."]
|
|
|
|
for item in items:
|
|
if action == Action.DOWNLOAD:
|
|
source = s3_path
|
|
recursive = True
|
|
if item != ".":
|
|
source = s3_path + "/" + item
|
|
recursive = False
|
|
try:
|
|
stdout = s3(source=source, destination=item, recursive=recursive)
|
|
except Exception:
|
|
# Optional artifacts (e.g. per-backend device runtime DSOs) may not
|
|
# exist in S3 when the build config didn't produce them. Skip silently.
|
|
logging.warning(f"Download failed for {item}, skipping (may be optional)")
|
|
continue
|
|
files = parse_output_files(stdout)
|
|
chmod(files)
|
|
for file in files:
|
|
# Show md5 after downloading
|
|
show_md5(file)
|
|
elif action == Action.UPLOAD:
|
|
if not Path(item).exists():
|
|
logging.warning(f"The path doesn't exist: {item}")
|
|
continue
|
|
show_md5(item)
|
|
if Path(item).is_dir():
|
|
if len(list(Path(item).glob("**/*"))) == 0:
|
|
raise RuntimeError(f"Cannot upload empty folder with name: {item}")
|
|
s3(item, s3_path + "/" + item, recursive=Path(item).is_dir())
|