1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
#!/usr/bin/env python
"""Download artifacts from GitHub Actions workflow runs for given run IDs."""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
def get_artifacts_for_run(run_id, repo, token):
"""
Get list of artifacts for a workflow run.
Args:
run_id: Workflow run ID
repo: Repository in format 'owner/repo'
token: GitHub token for API authentication
Returns:
List of artifact dictionaries with 'name' and 'id'
"""
print(f"Fetching artifacts for run ID {run_id}", file=sys.stderr)
env = os.environ.copy()
env['GH_TOKEN'] = token
try:
result = subprocess.run(
[
"gh", "api",
"-H", "Accept: application/vnd.github+json",
"-H", "X-GitHub-Api-Version: 2022-11-28",
f"/repos/{repo}/actions/runs/{run_id}/artifacts",
"--jq", '.artifacts[] | {name: .name, id: .id}'
],
capture_output=True,
text=True,
check=True,
env=env
)
artifacts = []
for line in result.stdout.strip().split('\n'):
if line:
artifacts.append(json.loads(line))
print(f"Found {len(artifacts)} artifacts for run {run_id}", file=sys.stderr)
return artifacts
except subprocess.CalledProcessError as e:
print(f"ERROR: Failed to fetch artifacts: {e}", file=sys.stderr)
print(f"stderr: {e.stderr}", file=sys.stderr)
return []
def download_artifact(artifact_name, artifact_id, repo, token, output_dir):
"""
Download a single artifact and extract directly to output_dir.
Args:
artifact_name: Name of the artifact
artifact_id: Artifact ID
repo: Repository in format 'owner/repo'
token: GitHub token for API authentication
output_dir: Directory to download artifact to
Returns:
True if successful, False otherwise
"""
print(f"Downloading artifact: {artifact_name}", file=sys.stderr)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env['GH_TOKEN'] = token
try:
# Use gh CLI to download artifact
zip_path = output_path / f"{artifact_name}.zip"
result = subprocess.run(
[
"gh", "api",
"-H", "Accept: application/vnd.github+json",
"-H", "X-GitHub-Api-Version: 2022-11-28",
f"/repos/{repo}/actions/artifacts/{artifact_id}/zip"
],
check=True,
env=env,
capture_output=True
)
# Write the binary output to file
with open(zip_path, 'wb') as f:
f.write(result.stdout)
# Unzip artifact directly to output_dir (flat structure)
subprocess.run(
["unzip", "-q", "-o", str(zip_path), "-d", str(output_path)],
check=True
)
# Remove the zip file
zip_path.unlink()
print(f"Successfully downloaded: {artifact_name}", file=sys.stderr)
return True
except subprocess.CalledProcessError as e:
print(f"ERROR: Failed to download {artifact_name}: {e}", file=sys.stderr)
if e.stderr:
print(f"stderr: {e.stderr}", file=sys.stderr)
if e.stdout:
print(f"stdout: {e.stdout}", file=sys.stderr)
return False
def main():
parser = argparse.ArgumentParser(
description='Download artifacts from workflow runs'
)
parser.add_argument(
'--run-ids',
nargs='+',
required=True,
help='List of workflow run IDs to download artifacts from'
)
parser.add_argument(
'--repo',
default=os.environ.get('GITHUB_REPOSITORY', ''),
help='Repository in format owner/repo (default: GITHUB_REPOSITORY env var)'
)
parser.add_argument(
'--token',
default=os.environ.get('GH_TOKEN', os.environ.get('GITHUB_TOKEN', '')),
help='GitHub token for API authentication (default: GH_TOKEN or GITHUB_TOKEN env var)'
)
parser.add_argument(
'--output-dir',
required=True,
help='Output directory for artifacts'
)
args = parser.parse_args()
if not args.repo:
parser.error("Repository is required (via --repo or GITHUB_REPOSITORY env var)")
if not args.token:
parser.error("GitHub token is required (via --token or GH_TOKEN/GITHUB_TOKEN env var)")
print(f"=== Downloading artifacts from {len(args.run_ids)} workflow runs ===", file=sys.stderr)
for run_id in args.run_ids:
print(f"\n--- Processing run ID: {run_id} ---", file=sys.stderr)
artifacts = get_artifacts_for_run(run_id, args.repo, args.token)
if not artifacts:
print(f"\nERROR: No artifacts found for run ID {run_id}", file=sys.stderr)
sys.exit(1)
for artifact in artifacts:
success = download_artifact(
artifact['name'],
artifact['id'],
args.repo,
args.token,
args.output_dir
)
if not success:
print(f"\nERROR: Failed to download artifact '{artifact['name']}'", file=sys.stderr)
sys.exit(1)
print(f"\n All artifacts downloaded successfully", file=sys.stderr)
if __name__ == "__main__":
main()
|