2014-11-16 17:08:42 +00:00
|
|
|
# This file is dual licensed under the terms of the Apache License, Version
|
|
|
|
|
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
|
|
|
|
# for complete details.
|
2014-07-06 09:51:26 +00:00
|
|
|
|
2014-02-19 22:01:06 +00:00
|
|
|
import getpass
|
2020-03-21 19:14:46 +00:00
|
|
|
import io
|
2014-02-20 15:48:46 +00:00
|
|
|
import os
|
2017-05-20 21:37:40 +00:00
|
|
|
import subprocess
|
2014-02-20 00:44:06 +00:00
|
|
|
import time
|
2023-01-02 00:38:57 +00:00
|
|
|
import typing
|
2023-01-02 02:22:32 +00:00
|
|
|
import urllib
|
2019-05-25 15:11:49 +00:00
|
|
|
import zipfile
|
|
|
|
|
|
2017-05-20 21:37:40 +00:00
|
|
|
import click
|
2020-03-21 19:14:46 +00:00
|
|
|
import requests
|
|
|
|
|
|
2014-02-20 00:44:06 +00:00
|
|
|
|
2023-01-02 00:38:57 +00:00
|
|
|
def run(*args: str) -> None:
|
2017-08-04 17:45:11 +00:00
|
|
|
print("[running] {0}".format(list(args)))
|
2023-01-02 00:38:57 +00:00
|
|
|
subprocess.check_call(list(args))
|
2017-05-20 21:37:40 +00:00
|
|
|
|
|
|
|
|
|
2023-01-02 00:38:57 +00:00
|
|
|
def wait_for_build_complete_github_actions(
|
|
|
|
|
session: requests.Session, token: str, run_url: str
|
|
|
|
|
) -> None:
|
2020-03-21 19:14:46 +00:00
|
|
|
while True:
|
2020-07-20 18:06:29 +00:00
|
|
|
response = session.get(
|
|
|
|
|
run_url,
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": "token {}".format(token),
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-03-21 19:14:46 +00:00
|
|
|
response.raise_for_status()
|
|
|
|
|
if response.json()["conclusion"] is not None:
|
|
|
|
|
break
|
|
|
|
|
time.sleep(3)
|
|
|
|
|
|
|
|
|
|
|
2023-01-02 00:38:57 +00:00
|
|
|
def download_artifacts_github_actions(
|
|
|
|
|
session: requests.Session, token: str, run_url: str
|
|
|
|
|
) -> typing.List[str]:
|
2020-07-20 18:06:29 +00:00
|
|
|
response = session.get(
|
|
|
|
|
run_url,
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": "token {}".format(token),
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-03-21 19:14:46 +00:00
|
|
|
response.raise_for_status()
|
|
|
|
|
|
2020-07-20 18:06:29 +00:00
|
|
|
response = session.get(
|
|
|
|
|
response.json()["artifacts_url"],
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": "token {}".format(token),
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-03-21 19:14:46 +00:00
|
|
|
response.raise_for_status()
|
|
|
|
|
paths = []
|
|
|
|
|
for artifact in response.json()["artifacts"]:
|
2020-07-20 18:06:29 +00:00
|
|
|
response = session.get(
|
|
|
|
|
artifact["archive_download_url"],
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": "token {}".format(token),
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-03-21 19:14:46 +00:00
|
|
|
with zipfile.ZipFile(io.BytesIO(response.content)) as z:
|
|
|
|
|
for name in z.namelist():
|
2022-11-03 11:41:03 +00:00
|
|
|
if not name.endswith(".whl") and not name.endswith(".tar.gz"):
|
2020-03-21 19:14:46 +00:00
|
|
|
continue
|
|
|
|
|
p = z.open(name)
|
|
|
|
|
out_path = os.path.join(
|
2020-08-27 02:59:43 +00:00
|
|
|
os.path.dirname(__file__),
|
|
|
|
|
"dist",
|
|
|
|
|
os.path.basename(name),
|
2020-03-21 19:14:46 +00:00
|
|
|
)
|
|
|
|
|
with open(out_path, "wb") as f:
|
|
|
|
|
f.write(p.read())
|
|
|
|
|
paths.append(out_path)
|
|
|
|
|
return paths
|
|
|
|
|
|
|
|
|
|
|
2023-01-02 00:38:57 +00:00
|
|
|
def fetch_github_actions_artifacts(
|
|
|
|
|
token: str, version: str
|
|
|
|
|
) -> typing.List[str]:
|
2020-03-21 19:14:46 +00:00
|
|
|
session = requests.Session()
|
|
|
|
|
|
|
|
|
|
response = session.get(
|
|
|
|
|
(
|
2022-12-01 01:40:48 +00:00
|
|
|
f"https://api.github.com/repos/pyca/cryptography/actions"
|
|
|
|
|
f"/workflows/wheel-builder.yml/runs?event=push&branch={version}"
|
2020-03-21 19:14:46 +00:00
|
|
|
),
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": "token {}".format(token),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
2023-01-02 00:38:57 +00:00
|
|
|
run_url: str = response.json()["workflow_runs"][0]["url"]
|
2020-03-21 19:14:46 +00:00
|
|
|
wait_for_build_complete_github_actions(session, token, run_url)
|
|
|
|
|
return download_artifacts_github_actions(session, token, run_url)
|
|
|
|
|
|
|
|
|
|
|
2023-01-02 02:22:32 +00:00
|
|
|
def wait_for_build_complete_circleci(
|
|
|
|
|
session: requests.Session, token: str, pipeline_id: str
|
|
|
|
|
) -> None:
|
|
|
|
|
while True:
|
|
|
|
|
response = session.get(
|
|
|
|
|
f"https://circleci.com/api/v2/pipeline/{pipeline_id}/workflow",
|
|
|
|
|
headers={
|
|
|
|
|
"Circle-Token": token,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
status = response.json()["items"][0]["status"]
|
|
|
|
|
if status == "success":
|
|
|
|
|
break
|
|
|
|
|
elif status not in ("running", "on_hold", "not_run"):
|
|
|
|
|
raise ValueError(f"CircleCI build failed with status {status}")
|
|
|
|
|
time.sleep(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_artifacts_circleci(
|
|
|
|
|
session: requests.Session, urls: typing.List[str]
|
|
|
|
|
) -> typing.List[str]:
|
|
|
|
|
paths = []
|
|
|
|
|
for url in urls:
|
|
|
|
|
name = os.path.basename(urllib.parse.urlparse(url).path)
|
|
|
|
|
response = session.get(url)
|
|
|
|
|
out_path = os.path.join(
|
|
|
|
|
os.path.dirname(__file__),
|
|
|
|
|
"dist",
|
|
|
|
|
os.path.basename(name),
|
|
|
|
|
)
|
|
|
|
|
with open(out_path, "wb") as f:
|
|
|
|
|
f.write(response.content)
|
|
|
|
|
paths.append(out_path)
|
|
|
|
|
return paths
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_circleci_artifacts(token: str, version: str) -> typing.List[str]:
|
|
|
|
|
session = requests.Session()
|
|
|
|
|
|
|
|
|
|
response = session.get(
|
|
|
|
|
"https://circleci.com/api/v2/pipeline?org-slug=gh/pyca",
|
|
|
|
|
headers={"Circle-Token": token},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
pipeline_id = None
|
|
|
|
|
for item in response.json()["items"]:
|
|
|
|
|
if item["project_slug"] == "gh/pyca/cryptography":
|
|
|
|
|
if item["vcs"].get("tag", None) == version:
|
|
|
|
|
pipeline_id = item["id"]
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if pipeline_id is None:
|
|
|
|
|
raise ValueError(f"Could not find a pipeline for version {version}")
|
|
|
|
|
|
|
|
|
|
wait_for_build_complete_circleci(session, token, pipeline_id)
|
|
|
|
|
urls = fetch_circleci_artifact_urls(session, token, pipeline_id)
|
|
|
|
|
return download_artifacts_circleci(session, urls)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_circleci_artifact_urls(
|
|
|
|
|
session: requests.Session, token: str, pipeline_id: str
|
|
|
|
|
) -> typing.List[str]:
|
|
|
|
|
response = session.get(
|
|
|
|
|
f"https://circleci.com/api/v2/pipeline/{pipeline_id}/workflow",
|
|
|
|
|
headers={"Circle-Token": token},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
workflow_id = response.json()["items"][0]["id"]
|
|
|
|
|
job_response = session.get(
|
|
|
|
|
f"https://circleci.com/api/v2/workflow/{workflow_id}/job",
|
|
|
|
|
headers={"Circle-Token": token},
|
|
|
|
|
)
|
|
|
|
|
job_response.raise_for_status()
|
|
|
|
|
artifact_urls = []
|
|
|
|
|
for job in job_response.json()["items"]:
|
|
|
|
|
urls = fetch_circleci_artifact_url_from_job(
|
|
|
|
|
session, token, job["job_number"]
|
|
|
|
|
)
|
|
|
|
|
artifact_urls.extend(urls)
|
|
|
|
|
|
|
|
|
|
return artifact_urls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_circleci_artifact_url_from_job(
|
|
|
|
|
session: requests.Session, token: str, job: str
|
|
|
|
|
) -> typing.List[str]:
|
|
|
|
|
response = session.get(
|
|
|
|
|
f"https://circleci.com/api/v2/project/gh/pyca/cryptography/"
|
|
|
|
|
f"{job}/artifacts",
|
|
|
|
|
headers={"Circle-Token": token},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
urls = []
|
|
|
|
|
for item in response.json()["items"]:
|
|
|
|
|
url = item.get("url", None)
|
|
|
|
|
if url is not None:
|
|
|
|
|
urls.append(url)
|
|
|
|
|
|
|
|
|
|
return urls
|
|
|
|
|
|
|
|
|
|
|
2017-05-20 21:37:40 +00:00
|
|
|
@click.command()
|
|
|
|
|
@click.argument("version")
|
2023-01-02 00:38:57 +00:00
|
|
|
def release(version: str) -> None:
|
2014-01-06 20:04:53 +00:00
|
|
|
"""
|
|
|
|
|
``version`` should be a string like '0.4' or '1.0'.
|
|
|
|
|
"""
|
2022-07-06 21:46:17 +00:00
|
|
|
print(
|
|
|
|
|
f"Create a new GH PAT at: "
|
|
|
|
|
f"https://github.com/settings/tokens/new?"
|
|
|
|
|
f"description={version}&scopes=repo"
|
|
|
|
|
)
|
2023-01-02 02:22:32 +00:00
|
|
|
print(
|
|
|
|
|
"Get a CircleCI token at: "
|
|
|
|
|
"https://app.circleci.com/settings/user/tokens"
|
|
|
|
|
)
|
2020-03-21 19:14:46 +00:00
|
|
|
github_token = getpass.getpass("Github person access token: ")
|
2023-01-02 02:22:32 +00:00
|
|
|
circle_token = getpass.getpass("CircleCI token: ")
|
2020-03-21 19:14:46 +00:00
|
|
|
|
2021-09-30 00:38:28 +00:00
|
|
|
# Tag and push the tag (this will trigger the wheel builder in Actions)
|
2017-05-20 21:37:40 +00:00
|
|
|
run("git", "tag", "-s", version, "-m", "{0} release".format(version))
|
|
|
|
|
run("git", "push", "--tags")
|
2014-01-06 20:04:53 +00:00
|
|
|
|
2021-09-30 00:38:28 +00:00
|
|
|
# Wait for Actions to complete and download the wheels
|
2022-11-03 11:41:03 +00:00
|
|
|
github_actions_artifact_paths = fetch_github_actions_artifacts(
|
2020-03-21 19:14:46 +00:00
|
|
|
github_token, version
|
|
|
|
|
)
|
2023-01-02 02:22:32 +00:00
|
|
|
# Download wheels from CircleCI
|
|
|
|
|
circle_artifact_paths = fetch_circleci_artifacts(circle_token, version)
|
|
|
|
|
|
|
|
|
|
artifact_paths = github_actions_artifact_paths + circle_artifact_paths
|
2021-09-30 00:38:28 +00:00
|
|
|
|
2022-08-02 12:08:59 +00:00
|
|
|
# Upload wheels and sdist
|
2023-01-02 02:22:32 +00:00
|
|
|
run("twine", "upload", *artifact_paths)
|
2017-05-20 21:37:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
release()
|