diff --git a/bioconda_utils/artifacts.py b/bioconda_utils/artifacts.py index a679578ff1..7e0a410a96 100644 --- a/bioconda_utils/artifacts.py +++ b/bioconda_utils/artifacts.py @@ -42,7 +42,7 @@ def upload_pr_artifacts(config, repo, git_sha, dryrun=False, mulled_upload_targe # no PR found for the commit return UploadResult.NO_PR pr = prs[0] - artifacts = set(fetch_artifacts(pr, artifact_source)) + artifacts = set(fetch_artifacts(pr, artifact_source, repo)) if not artifacts: # no artifacts found, fail and rebuild packages logger.info("No artifacts found.") @@ -54,13 +54,19 @@ def upload_pr_artifacts(config, repo, git_sha, dryrun=False, mulled_upload_targe # download the artifact if artifact_source == "azure": artifact_path = os.path.join(tmpdir, os.path.basename(artifact)) - download_artifact(artifact, artifact_path) + download_artifact(artifact, artifact_path, artifact_source) zipfile.ZipFile(artifact_path).extractall(tmpdir) elif artifact_source == "circleci": artifact_dir = os.path.join(tmpdir, *(artifact.split("/")[-4:-1])) artifact_path = os.path.join(tmpdir, artifact_dir, os.path.basename(artifact)) - Path(artifact_dir).mkdir(parents=True, exist_ok=True) - download_artifact(artifact, artifact_path) + Path(artifact_dir).mkdir(parents=True, exist_ok=True) + download_artifact(artifact, artifact_path, artifact_source) + elif artifact_source == "github-actions": + artifact_dir = os.path.join(tmpdir, "artifacts") + artifact_path = os.path.join(artifact_dir, os.path.basename(artifact)) + Path(artifact_dir).mkdir(parents=True, exist_ok=True) + download_artifact(artifact, artifact_path, artifact_source) + zipfile.ZipFile(artifact_path).extractall(artifact_dir) # get all the contained packages and images and upload them platform_patterns = [repodata.platform2subdir(repodata.native_platform())] @@ -110,9 +116,16 @@ def upload_pr_artifacts(config, repo, git_sha, dryrun=False, mulled_upload_targe backoff.expo, requests.exceptions.RequestException ) -def download_artifact(url, to_path): +def download_artifact(url, to_path, artifact_source): logger.info(f"Downloading artifact {url}.") - resp = requests.get(url, stream=True, allow_redirects=True) + headers = {} + if artifact_source == "github-actions": + token = os.environ.get("GITHUB_TOKEN") + if not token: + logger.critical("GITHUB_TOKEN required to download GitHub Actions artifacts") + exit(1) + headers = {"Authorization": f"token {token}"} + resp = requests.get(url, stream=True, allow_redirects=True, headers=headers) resp.raise_for_status() with open(to_path, "wb") as f: for chunk in resp.iter_content(chunk_size=1024): @@ -120,7 +133,7 @@ def download_artifact(url, to_path): f.write(chunk) -def fetch_artifacts(pr, artifact_source): +def fetch_artifacts(pr, artifact_source, repo): """ Fetch artifacts from a PR. @@ -155,6 +168,13 @@ def fetch_artifacts(pr, artifact_source): # Circle CI builds artifact_url = get_circleci_artifacts(check_run, platform) yield from artifact_url + elif ( + artifact_source == "github-actions" and + check_run.app.slug == "github-actions" + ): + # GitHubActions builds + artifact_url = get_gha_artifacts(check_run, platform, repo) + yield from artifact_url def get_azure_artifacts(check_run): @@ -197,3 +217,18 @@ def get_circleci_artifacts(check_run, platform): continue else: yield artifact_url + +def parse_gha_build_id(url: str) -> str: + # Get workflow run id from URL + return re.search("runs/(\d+)/", url).group(1) + +def get_gha_artifacts(check_run, platform, repo): + gha_workflow_id = parse_gha_build_id(check_run.details_url) + if (gha_workflow_id) : + # The workflow run is different from the check run + run = repo.get_workflow_run(int(gha_workflow_id)) + artifacts = run.get_artifacts() + for artifact in artifacts: + # This URL is valid for 1 min and requires a token + artifact_url = artifact.archive_download_url + yield artifact_url diff --git a/bioconda_utils/cli.py b/bioconda_utils/cli.py index 5abcf1f159..89834c9816 100644 --- a/bioconda_utils/cli.py +++ b/bioconda_utils/cli.py @@ -524,7 +524,7 @@ def build(recipe_folder, config, packages="*", git_range=None, testonly=False, @arg('--dryrun', action='store_true', help='''Do not actually upload anything.''') @arg('--fallback', choices=['build', 'ignore'], default='build', help="What to do if no artifacts are found in the PR.") @arg('--quay-upload-target', help="Provide a quay.io target to push docker images to.") -@arg('--artifact-source', choices=['azure', 'circleci'], default='azure', help="Application hosting build artifacts (e.g., Azure or Circle CI).") +@arg('--artifact-source', choices=['azure', 'circleci','github-actions'], default='azure', help="Application hosting build artifacts (e.g., Azure, Circle CI, or GitHub Actions).") @enable_logging() def handle_merged_pr( recipe_folder,