Skip to content

Commit 14075c8

Browse files
committed
re-add circleci artifact fetching
1 parent 14de74c commit 14075c8

File tree

4 files changed

+123
-19
lines changed

4 files changed

+123
-19
lines changed

images/bot/setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = bioconda-bot
3-
version = 0.0.1
3+
version = 0.0.2
44

55
[options]
66
python_requires = >=3.8

images/bot/src/bioconda_bot/comment.py

+69-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import re
44

55
from aiohttp import ClientSession
6+
from typing import List, Tuple
67
from yaml import safe_load
78

89
from .common import (
@@ -24,16 +25,23 @@
2425
# Given a PR and commit sha, post a comment with any artifacts
2526
async def make_artifact_comment(session: ClientSession, pr: int, sha: str) -> None:
2627
artifacts = await fetch_pr_sha_artifacts(session, pr, sha)
28+
29+
comment = compose_azure_comment(artifacts["azure"])
30+
if len(comment) > 0:
31+
comment += "\n\n"
32+
comment += compose_circlci_comment(artifacts["circleci"])
33+
34+
await send_comment(session, pr, comment)
35+
36+
def compose_azure_comment(artifacts: List[Tuple[str, str]]) -> str:
2737
nPackages = len(artifacts)
38+
comment = "## Azure\n\n"
2839

2940
if nPackages > 0:
30-
comment = "Package(s) built on Azure are ready for inspection:\n\n"
41+
comment += "Package(s) built on Azure are ready for inspection:\n\n"
3142
comment += "Arch | Package | Zip File\n-----|---------|---------\n"
32-
install_noarch = ""
33-
install_linux = ""
34-
install_osx = ""
3543

36-
# Table of packages and repodata.json
44+
# Table of packages and zips
3745
for URL, artifact in artifacts:
3846
if not (package_match := re.match(r"^((.+)\/(.+)\/(.+)\/(.+\.tar\.bz2))$", artifact)):
3947
continue
@@ -61,9 +69,9 @@ async def make_artifact_comment(session: ClientSession, pr: int, sha: str) -> No
6169
comment += "```\nconda install -c ./packages <package name>\n```\n"
6270

6371
# Table of containers
64-
comment += "***\n\nDocker image(s) built (images are in the LinuxArtifacts zip file above):\n\n"
65-
comment += "Package | Tag | Install with `docker`\n"
66-
comment += "--------|-----|----------------------\n"
72+
imageHeader = "***\n\nDocker image(s) built (images for Azure are in the LinuxArtifacts zip file above):\n\n"
73+
imageHeader += "Package | Tag | Install with `docker`\n"
74+
imageHeader += "--------|-----|----------------------\n"
6775

6876
for URL, artifact in artifacts:
6977
if artifact.endswith(".tar.gz"):
@@ -72,18 +80,68 @@ async def make_artifact_comment(session: ClientSession, pr: int, sha: str) -> No
7280
package_name, tag = image_name.split(':', 1)
7381
#image_url = URL[:-3] # trim off zip from format=
7482
#image_url += "file&subPath=%2F{}.tar.gz".format("%2F".join(["images", '%3A'.join([package_name, tag])]))
83+
comment += imageHeader
84+
imageHeader = "" # only add the header for the first image
7585
comment += f"{package_name} | {tag} | "
7686
comment += f'<details><summary>show</summary>`gzip -dc LinuxArtifacts/images/{image_name}.tar.gz \\| docker load`\n'
7787
comment += "\n\n"
7888
else:
79-
comment = (
89+
comment += (
8090
"No artifacts found on the most recent Azure build. "
8191
"Either the build failed, the artifacts have were removed due to age, or the recipe was blacklisted/skipped."
8292
)
83-
await send_comment(session, pr, comment)
93+
return comment
8494

95+
def compose_circlci_comment(artifacts: List[Tuple[str, str]]) -> str:
96+
nPackages = len(artifacts)
8597

86-
# Post a comment on a given PR with its CircleCI artifacts
98+
if nPackages < 1:
99+
return ""
100+
101+
comment = "## CircleCI\n\n"
102+
comment += "Package(s) built on CircleCI are ready for inspection:\n\n"
103+
comment += "Arch | Package | Repodata\n-----|---------|---------\n"
104+
105+
# Table of packages and repodata.json
106+
for URL, artifact in artifacts:
107+
if not (package_match := re.match(r"^((.+)\/(.+)\/(.+\.tar\.bz2))$", URL)):
108+
continue
109+
url, basedir, subdir, packageName = package_match.groups()
110+
repo_url = "/".join([basedir, subdir, "repodata.json"])
111+
conda_install_url = basedir
112+
113+
if subdir == "noarch":
114+
comment += "noarch |"
115+
elif subdir == "linux-64":
116+
comment += "linux-64 |"
117+
elif subdir == "linux-aarch64":
118+
comment += "linux-aarch64 |"
119+
else:
120+
comment += "osx-64 |"
121+
comment += f" [{packageName}]({URL}) | [repodata.json]({repo_url})\n"
122+
123+
# Conda install examples
124+
comment += "***\n\nYou may also use `conda` to install these:\n\n"
125+
comment += f"```\nconda install -c {conda_install_url} <package name>\n```\n"
126+
127+
# Table of containers
128+
imageHeader = "***\n\nDocker image(s) built:\n\n"
129+
imageHeader += "Package | Tag | Install with `docker`\n"
130+
imageHeader += "--------|-----|----------------------\n"
131+
132+
for URL, artifact in artifacts:
133+
if artifact.endswith(".tar.gz"):
134+
image_name = artifact.split("/").pop()[: -len(".tar.gz")]
135+
if ":" in image_name:
136+
package_name, tag = image_name.split(":", 1)
137+
comment += imageHeader
138+
imageHeader = "" # only add the header for the first image
139+
comment += f"[{package_name}]({URL}) | {tag} | "
140+
comment += f'<details><summary>show</summary>`curl -L "{URL}" \\| gzip -dc \\| docker load`</details>\n'
141+
comment += "</details>\n"
142+
return comment
143+
144+
# Post a comment on a given PR with its artifacts
87145
async def artifact_checker(session: ClientSession, issue_number: int) -> None:
88146
url = f"https://api.github.com/repos/bioconda/bioconda-recipes/pulls/{issue_number}"
89147
headers = {

images/bot/src/bioconda_bot/common.py

+50-6
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,40 @@ async def fetch_azure_zip_files(session: ClientSession, buildId: str) -> [(str,
137137
def parse_azure_build_id(url: str) -> str:
138138
return re.search("buildId=(\d+)", url).group(1)
139139

140+
# Find artifact zip files, download them and return their URLs and contents
141+
async def fetch_circleci_artifacts(session: ClientSession, workflowId: str) -> [(str, str)]:
142+
artifacts = []
143+
144+
url_wf = f"https://circleci.com/api/v2/workflow/{workflowId}/job"
145+
async with session.get(url_wf) as response:
146+
# Sometimes we get a 301 error, so there are no longer artifacts available
147+
if response.status == 301:
148+
return artifacts
149+
res_wf = await response.text()
150+
151+
res_wf_object = safe_load(res_wf)
152+
153+
if len(res_wf_object["items"]) == 0:
154+
return artifacts
155+
else:
156+
for job in res_wf_object["items"]:
157+
if job["name"].startswith(f"build_and_test-"):
158+
circleci_job_num = job["job_number"]
159+
url = f"https://circleci.com/api/v2/project/gh/bioconda/bioconda-recipes/{circleci_job_num}/artifacts"
160+
161+
async with session.get(url) as response:
162+
res = await response.text()
163+
164+
res_object = safe_load(res)
165+
for artifact in res_object["items"]:
166+
zipUrl = artifact["url"]
167+
pkg = artifact["path"]
168+
if zipUrl.endswith(".tar.bz2"): # (currently excluding container images) or zipUrl.endswith(".tar.gz"):
169+
artifacts.append((zipUrl, pkg))
170+
return artifacts
140171

141172
# Given a PR and commit sha, fetch a list of the artifact zip files URLs and their contents
142-
async def fetch_pr_sha_artifacts(session: ClientSession, pr: int, sha: str) -> List[Tuple[str, str]]:
173+
async def fetch_pr_sha_artifacts(session: ClientSession, pr: int, sha: str) -> Dict[str, List[Tuple[str, str]]]:
143174
url = f"https://api.github.com/repos/bioconda/bioconda-recipes/commits/{sha}/check-runs"
144175

145176
headers = {
@@ -151,15 +182,28 @@ async def fetch_pr_sha_artifacts(session: ClientSession, pr: int, sha: str) -> L
151182
res = await response.text()
152183
check_runs = safe_load(res)
153184

185+
artifact_sources = {}
154186
for check_run in check_runs["check_runs"]:
155-
# The names are "bioconda.bioconda-recipes (test_osx test_osx)" or similar
156-
if check_run["name"].startswith("bioconda.bioconda-recipes (test_"):
187+
if (
188+
"azure" not in artifact_sources and
189+
check_run["app"]["slug"] == "azure-pipelines" and
190+
check_run["name"].startswith("bioconda.bioconda-recipes (test_")
191+
):
192+
# azure builds
157193
# The azure build ID is in the details_url as buildId=\d+
158194
buildID = parse_azure_build_id(check_run["details_url"])
159195
zipFiles = await fetch_azure_zip_files(session, buildID)
160-
return zipFiles # We've already fetched all possible artifacts
161-
162-
return []
196+
artifact_sources["azure"] = zipFiles # We've already fetched all possible artifacts from Azure
197+
elif (
198+
"circleci" not in artifact_sources and
199+
check_run["app"]["slug"] == "circleci-checks"
200+
):
201+
# Circle CI builds
202+
workflowId = safe_load(check_run["external_id"])["workflow-id"]
203+
zipFiles = await fetch_circleci_artifacts(session, workflowId)
204+
artifact_sources["circleci"] = zipFiles # We've already fetched all possible artifacts from CircleCI
205+
206+
return artifact_sources
163207

164208

165209
async def get_sha_for_status(job_context: Dict[str, Any]) -> Optional[str]:

images/bot/src/bioconda_bot/merge.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,9 @@ async def upload_artifacts(session: ClientSession, pr: int) -> str:
271271
sha: str = pr_info["head"]["sha"]
272272

273273
# Fetch the artifacts (a list of (URL, artifact) tuples actually)
274-
artifacts = await fetch_pr_sha_artifacts(session, pr, sha)
274+
artifactDict = await fetch_pr_sha_artifacts(session, pr, sha)
275+
# Merge is deprecated, so leaving as Azure only
276+
artifacts = artifactDict["azure"]
275277
artifacts = [artifact for (URL, artifact) in artifacts if artifact.endswith((".gz", ".bz2"))]
276278
assert artifacts
277279

0 commit comments

Comments
 (0)