forge-helpers/pagure_create_issues.py

258 lines
7 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Create numbered test issues in a Pagure repository.
This script creates issues in a Pagure repository with sequential numbering,
useful for testing or setting up migration scenarios.
Note: Most of the code is AI-generated using Claude (Anthropic) via Cursor IDE.
"""
import argparse
from pathlib import Path
import sys
import time
from urllib.parse import urlparse
import requests
def parse_pagure_url(pagure_url: str) -> tuple[str, str]:
"""
Parse a Pagure project URL into API base URL and project path.
Args:
pagure_url: Full URL to the Pagure project
(e.g., 'https://pagure.io/fedora-qa/some-repo' or
'https://stg.pagure.io/fedora-qa/some-repo')
Returns:
Tuple of (api_base, project_path)
e.g., ('https://pagure.io/api/0', 'fedora-qa/some-repo')
Raises:
ValueError: If the URL is invalid
"""
parsed = urlparse(pagure_url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid Pagure URL: {pagure_url}")
api_base = f"{parsed.scheme}://{parsed.netloc}/api/0"
# Remove leading slash from path
project_path = parsed.path.lstrip("/")
if not project_path:
raise ValueError(f"No project path found in URL: {pagure_url}")
return api_base, project_path
def read_token_from_file(token_file: Path) -> str:
"""
Read API token from a file.
Args:
token_file: Path to the file containing the token
Returns:
The token string (stripped of whitespace)
Raises:
FileNotFoundError: If the file doesn't exist
ValueError: If the file is empty
"""
content = token_file.read_text().strip()
if not content:
raise ValueError(f"Token file is empty: {token_file}")
return content
def create_issue(
api_base: str, project: str, title: str, description: str, api_token: str
) -> bool:
"""
Create an issue in a Pagure project.
Args:
api_base: Pagure API base URL (e.g., 'https://pagure.io/api/0')
project: Pagure project path (e.g., 'fedora-qa/some-repo')
title: Issue title
description: Issue description/content
api_token: Pagure API token for authentication
Returns:
True if successful, False otherwise
"""
url = f"{api_base}/{project}/new_issue"
headers = {"Authorization": f"token {api_token}"}
data = {"title": title, "issue_content": description}
response = requests.post(url, headers=headers, data=data, timeout=30)
if response.status_code == 200:
return True
else:
print(f" Error creating issue: {response.status_code} - {response.text}")
return False
def process_issue_creation(
api_base: str,
project: str,
api_token: str,
count: int,
dry_run: bool,
delay: float,
) -> tuple[int, int]:
"""
Create multiple issues with sequential numbering.
Args:
api_base: Pagure API base URL
project: Pagure project path
api_token: Pagure API token
count: Number of issues to create
dry_run: If True, don't actually create issues
delay: Delay between API calls in seconds
Returns:
Tuple of (created, failed) counts
"""
created = 0
failed = 0
for n in range(1, count + 1):
title = f"issue {n}"
description = f"description {n}"
print(f"\nCreating issue {n}/{count}...")
print(f" Title: {title}")
print(f" Description: {description}")
if dry_run:
print(" [DRY RUN] Would create issue")
created += 1
else:
if create_issue(api_base, project, title, description, api_token):
print(" Success!")
created += 1
else:
failed += 1
# Delay between API calls to avoid rate limiting
if delay > 0 and n < count:
time.sleep(delay)
return created, failed
def main():
parser = argparse.ArgumentParser(
description="Create numbered test issues in a Pagure repository",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Dry run creating 5 issues (token from file)
%(prog)s https://pagure.io/fedora-qa/some-repo --count 5 --token-file ~/.pagure-token --dry-run
# Create 10 issues on staging Pagure
%(prog)s https://stg.pagure.io/fedora-qa/some-repo --count 10 --token YOUR_TOKEN
# Create issues with longer delay between calls
%(prog)s https://pagure.io/fedora-qa/some-repo --count 3 --token-file ~/.pagure-token --delay 2.0
""",
)
parser.add_argument(
"pagure_url",
help="Full URL to the Pagure project (e.g., 'https://pagure.io/fedora-qa/some-repo' or 'https://stg.pagure.io/fedora-qa/some-repo')",
)
parser.add_argument(
"--count",
type=int,
required=True,
help="Number of issues to create",
)
token_group = parser.add_mutually_exclusive_group(required=True)
token_group.add_argument(
"--token",
help="Pagure API token for authentication",
)
token_group.add_argument(
"--token-file",
type=Path,
help="Path to file containing the Pagure API token",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Preview changes without actually creating issues",
)
parser.add_argument(
"--delay",
type=float,
default=1.0,
help="Delay between API calls in seconds (default: 1.0)",
)
args = parser.parse_args()
# Validate count
if args.count < 1:
print("Error: --count must be at least 1")
sys.exit(1)
# Parse the Pagure URL
try:
api_base, project = parse_pagure_url(args.pagure_url)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
# Get the API token
if args.token:
api_token = args.token
else:
try:
api_token = read_token_from_file(args.token_file)
except FileNotFoundError:
print(f"Error: Token file not found: {args.token_file}")
sys.exit(1)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
print(f"Pagure URL: {args.pagure_url}")
print(f"Issues to create: {args.count}")
if args.dry_run:
print("Mode: DRY RUN (no changes will be made)")
try:
created, failed = process_issue_creation(
api_base=api_base,
project=project,
api_token=api_token,
count=args.count,
dry_run=args.dry_run,
delay=args.delay,
)
except requests.exceptions.HTTPError as e:
print(f"\nAPI Error: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\n\nInterrupted by user")
sys.exit(130)
print(f"\n{'=' * 40}")
print("Summary:")
print(f" Created: {created}")
print(f" Failed: {failed}")
if failed > 0:
sys.exit(1)
if __name__ == "__main__":
main()