Skip to content

Instantly share code, notes, and snippets.

@ekohl
Created February 5, 2026 08:51
Show Gist options
  • Select an option

  • Save ekohl/f7ef0c372f9c151fd6b90c7c3044f69c to your computer and use it in GitHub Desktop.

Select an option

Save ekohl/f7ef0c372f9c151fd6b90c7c3044f69c to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import sys
from pathlib import Path
from subprocess import check_output
import requests
import yaml
from github import Github
from github.GithubException import UnknownObjectException
# TODO: configurable or autodetect?
HAS_OTP = False
RELEASE_FLOW_NAME = 'release'
ENVIRONMENT = 'release'
GITHUB_SECRET = 'github/changelog'
def get_secret(name: str) -> str:
return check_output(['pass', 'show', name], universal_newlines=True).splitlines()[0]
class Rubygems:
BASE_URL = 'https://rubygems.org/api/v1'
def __init__(self, api_key: str, otp: str | None = None):
self._session = requests.Session()
self._session.headers['Authorization'] = api_key
if otp:
self._session.headers['OTP'] = otp
def publishers(self, gem: str) -> list:
url = f'{self.BASE_URL}/gems/{gem}/trusted_publishers'
response = self._session.get(url)
response.raise_for_status()
# TODO: Somehow this never lists any publishers and always returns an empty list
return response.json()
def create_publisher(self, gem: str, owner: str, repository: str, filename: str,
environment: str):
url = f'{self.BASE_URL}/gems/{gem}/trusted_publishers'
data = {
"trusted_publisher_type": "OIDC::TrustedPublisher::GitHubAction",
"trusted_publisher": {
"repository_owner": owner,
"repository_name": repository or gem,
"workflow_filename": filename,
"environment": environment,
}
}
response = self._session.post(url, json=data)
response.raise_for_status()
return response.json()
def read_rubygems_api_key() -> str | None:
# TODO: ~/.config
for path in ('~/.gem/credentials',):
expanded = Path(path).expanduser()
if expanded.exists():
data = yaml.safe_load(expanded.read_bytes())
if data and ':rubygems_api_key' in data:
return data[':rubygems_api_key']
else:
return None
def main():
repo_name = sys.argv[1]
token = get_secret(GITHUB_SECRET)
github = Github(token)
repo = github.get_repo(repo_name)
for content in repo.get_contents('/'):
if content.path.endswith('.gemspec'):
gem_name = content.path.removesuffix('.gemspec')
break
else:
raise SystemExit(f'Could not find a gemspec for {repo_name}')
for content in repo.get_contents('/.github/workflows'):
if content.name.startswith(RELEASE_FLOW_NAME):
filename = content.name
break
else:
raise SystemExit(f'Could not find a release workflow for {repo_name}')
try:
repo.get_environment(ENVIRONMENT)
except UnknownObjectException:
repo.create_environment(ENVIRONMENT)
api_key = read_rubygems_api_key()
if not api_key:
raise SystemExit('Could not read API key')
otp = input('OTP: ') if HAS_OTP else None
rubygems = Rubygems(api_key, otp)
# TODO: check if a valid publisher already exists
try:
publisher = rubygems.create_publisher(gem=gem_name, owner=repo.owner.login,
repository=repo.name, filename=filename,
environment=ENVIRONMENT)
except requests.RequestException as e:
print('Unable to create trusted publisher', file=sys.stderr)
print(e, file=sys.stderr)
if e.response:
print(e.reponse.json(), file=sys.stderr)
else:
print('Created trusted publisher', publisher['trusted_publisher']['name'])
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment