258 lines
7 KiB
Python
Executable file
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()
|
|
|