Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🥅 Prompt for new tokens on call failures #62

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 7 additions & 25 deletions d3b_release_maker/cli.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
#!/usr/bin/env python
import click
import re
import subprocess

from d3b_release_maker.release_maker import make_release, new_notes


def get_repository():
"""
Try to retrieve the github repository by extracting it from the current git
repository's 'origin' url.
"""
try:
result = subprocess.check_output(
["git", "remote", "get-url", "origin"], stderr=subprocess.DEVNULL
)
except subprocess.CalledProcessError:
# If the git command fails, bail early
return None

result = result.decode().strip()
match = re.match(r".*[:/]([\w\d0-9-]+\/[\w\d-]+)", result)
if match:
return match.group(1)
return None
from d3b_release_maker.release_maker import (
make_release,
new_notes,
get_repository,
)


@click.group(context_settings={"help_option_names": ["-h", "--help"]})
Expand All @@ -37,13 +19,13 @@ def cli():
def options(function):
function = click.option(
"--blurb_file",
prompt="Optional markdown file containing a custom message to prepend to the notes for this release",
prompt="Pick an optional markdown file containing a custom message to prepend to the notes for this release",
default="",
help="Optional markdown file containing a custom message to prepend to the notes for this release",
)(function)
function = click.option(
"--repo",
prompt="The github repository (e.g. my-organization/my-project-name)",
prompt="Pick the github repository (e.g. my-organization/my-project-name)",
help="The github organization/repository to make a release for",
default=get_repository,
)(function)
Expand Down
157 changes: 114 additions & 43 deletions d3b_release_maker/release_maker.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
import os
import shutil
import stat
import subprocess
import sys
Expand Down Expand Up @@ -39,17 +38,50 @@
}


def get_gh_api_token():
while not os.getenv(config.GH_TOKEN_VAR, "").strip():
gh_token = getpass(
prompt=(
f"\nNOTICE: A GitHub API token (https://github.com/settings/tokens) was either not provided or was provided but is invalid.\n"
'The provided token needs "public_repo" access to work for public repositories or "repo" scope to work for private ones.\n'
f"To avoid this notice, you may store the token in a variable called {config.GH_TOKEN_VAR} in your shell environment.\n\n"
"Please enter a valid GitHub API token now: "
)
def get_repository():
"""
Try to retrieve the github repository by extracting it from the current git
repository's 'origin' url.
"""
try:
result = subprocess.check_output(
["git", "remote", "get-url", "origin"], stderr=subprocess.DEVNULL
)
os.environ[config.GH_TOKEN_VAR] = gh_token
except subprocess.CalledProcessError:
# If the git command fails, bail early
return None

result = result.decode().strip()
match = regex.match(r".*[:/]([\w\d0-9-]+\/[\w\d-]+)", result)
if match:
return match.group(1)
return None


def get_gh_api_token(previous=None):
"""Prompt for a good GitHub API token

:param previous: [description], defaults to None
:type previous: [type], optional
:return: [description]
:rtype: [type]
"""
msg = "\nNOTICE: A GitHub API token (https://github.com/settings/tokens)"
if not previous:
msg += " was either not provided or"

msg += (
" was provided but is not valid or does not have sufficient permission.\n\n"
'The GitHub API token needs "public_repo" access to work for only public'
' repositories or "repo" scope to work for both public and private ones.\n'
"To avoid this notice, you may store the token in a variable called "
f"{config.GH_TOKEN_VAR} in your shell environment.\n\n"
"Please enter a valid GitHub API token now OR press enter to try the same one again: "
)
while not os.getenv(config.GH_TOKEN_VAR, "").strip():
os.environ[config.GH_TOKEN_VAR] = getpass(prompt=msg)
if previous and not os.environ[config.GH_TOKEN_VAR]:
os.environ[config.GH_TOKEN_VAR] = previous

return os.getenv(config.GH_TOKEN_VAR).strip()

Expand Down Expand Up @@ -98,10 +130,14 @@ def get(self, url, **request_kwargs):
self.update_headers()
response = super().get(url, **request_kwargs)
if response.status_code != 200:
status = f"Got error {response.status_code} when fetching '{response.url}'"
try:
msg = response.json()
except json.decoder.JSONDecodeError:
msg = response.text

if response.status_code == 404:
raise UnknownObjectException(
response.status_code, response.url
)
raise UnknownObjectException(status, msg)
elif response.status_code == 401:
del os.environ[config.GH_TOKEN_VAR]
elif response.headers.get("X-Ratelimit-Remaining") == "0":
Expand All @@ -114,10 +150,7 @@ def get(self, url, **request_kwargs):
)
delay_until(datetime_of_reset)
else:
raise GithubException(
response.status_code,
f"Could not fetch {response.url}! Caused by: {response.text}",
)
raise GithubException(status, msg)
else:
break

Expand Down Expand Up @@ -484,28 +517,54 @@ def make_release(repo, blurb_file):
Generate a new changelog, run the script, and then make a PR on GitHub
"""
gh_token = get_gh_api_token()
default_branch, new_version, new_markdown, changelog = new_changelog(
repo, blurb_file
)
while True:
try:
(
default_branch,
new_version,
new_markdown,
changelog,
) = new_changelog(repo, blurb_file)
break
except UnknownObjectException as e:
# Didn't find the repo. Bad token? Bad url?
yes = click.confirm(f"{e.status}. Is that URL 100% correct?")
if yes:
del os.environ[config.GH_TOKEN_VAR]
gh_token = get_gh_api_token(gh_token)
else:
repo = click.prompt(
"Pick the github repository (e.g. my-organization/my-project-name)",
default=get_repository(),
)

if changelog:
# Freshly clone repo
tmp = os.path.join(tempfile.gettempdir(), "release_maker")
shutil.rmtree(tmp, ignore_errors=True)
print(f"Cloning https://github.com/{repo}.git to {tmp} ...")
subprocess.run(
[
"git",
"clone",
"--depth",
"1",
f"https://{gh_token}@github.com/{repo}.git",
tmp,
],
check=True,
capture_output=True,
)
os.chdir(tmp)
# Freshly clone repo into a clean workspace
while True:
tmp = tempfile.TemporaryDirectory()
os.chdir(tmp.name)
subprocess.run(["git", "init", "--quiet"])

try:
print(
f"Cloning https://github.com/{repo}.git to temporary location..."
)
subprocess.run(
[
"git",
"pull",
"--quiet",
"--depth",
"1",
f"https://{gh_token}:[email protected]/{repo}.git",
],
check=True,
capture_output=True,
)
break
except subprocess.CalledProcessError:
del os.environ[config.GH_TOKEN_VAR]
gh_token = get_gh_api_token(gh_token)

# Get the configuration
cfg = load_config()
Expand Down Expand Up @@ -554,11 +613,23 @@ def make_release(repo, blurb_file):
check=True,
capture_output=True,
)
subprocess.run(
["git", "push", "--force", "origin", release_branch_name],
check=True,
capture_output=True,
)
while True:
try:
subprocess.run(
[
"git",
"push",
"--force",
f"https://{gh_token}:[email protected]/{repo}.git",
release_branch_name,
],
check=True,
capture_output=True,
)
break
except subprocess.CalledProcessError:
del os.environ[config.GH_TOKEN_VAR]
gh_token = get_gh_api_token(gh_token)

# Create GitHub Pull Request
print("Submitting PR for release ...")
Expand Down