Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Bulk download attachments in Jira Cloud using Python

Hi Atlassian Community!

I'm now sharing this simple Python script, which is designed to help you efficiently download all attachments from multiple Jira Cloud issues.

This script streamlines the process of retrieving attachments, organizing them into a structured folder, and providing a comprehensive report of the download activity. This is particularly useful for archiving, migrating data, or performing bulk analysis of attached files.

(This might play together with this other script to bulk attach files to Jira Issues in Jira Cloud)

The Solution

Using the Jira Cloud REST API, this script retrieves attachments from a list of Jira issues provided in a CSV or TXT file. The script downloads all attachments for the specified issues and saves them into a newly created directory. It also generates a CSV report detailing successful downloads and logs any failed attempts for easy troubleshooting.

Input File Formats

The script accepts two input file formats:

  • CSV (.csv): Must contain a column named "Issue Key".
  • Text (.txt): Must contain one issue key per line.

Example CSV:

Issue Key
PROJ-001
PROJ-002
PROJ-003

Example TXT:

PROJ-001
PROJ-002
PROJ-003

Key Features

  • Bulk download
    Download all attachments from multiple Jira issues
  • Organized storage
    Saves attachments into a timestamped folder
  • CSV and TXT sSupport
    Flexible input options for issue keys
  • Comprehensive reporting
    Generates a CSV report of downloaded attachments and a log file for failed downloads
  • Error handling
    Handles issues with invalid file paths, incorrect issue keys, and API request errors
  • Rate Limiting handling
    Includes delays to respect Jira API rate limits
  • Logging
    Provides detailed logging to track progress and identify any issues

Preparation

  1. Ensure the user running the script has "Browse Projects" and "View Issues" permissions in the relevant Jira projects
  2. Install the requests library: pip install requests
  3. Prepare your Jira Cloud domain (your-domain.atlassian.net), email address, and API token.
  4. Create your CSV or TXT file containing the list of Jira issue keys, you can use the examples shared above

The Script

import requests
from requests.auth import HTTPBasicAuth
from getpass import getpass
import logging
import os
import csv
from datetime import datetime
import time

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
log_filename = f"jira_attachment_download_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
file_handler = logging.FileHandler(log_filename)
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(file_handler)

def get_jira_auth():
    jira_domain = input("Enter your Jira Cloud domain (e.g., your-domain.atlassian.net): ")
    email = input("Enter your Jira email: ")
    api_token = getpass("Enter your Jira API token: ")
    return jira_domain, HTTPBasicAuth(email, api_token)

def read_issue_keys(file_path):
    issue_keys = []
    if file_path.lower().endswith('.csv'):
        with open(file_path, 'r', newline='', encoding='utf-8-sig') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                issue_keys.append(row['Issue Key'])
    elif file_path.lower().endswith('.txt'):
        with open(file_path, 'r', encoding='utf-8-sig') as txtfile:
            for line in txtfile:
                issue_keys.append(line.strip())
    return issue_keys

def download_attachments(jira_domain, auth, file_path):
    base_url = f"https://{jira_domain}/rest/api/3/issue/"
    attachments_folder = f"Bulk Attachments {datetime.now().strftime('%Y%m%d_%H%M%S')}"
    os.makedirs(attachments_folder, exist_ok=True)
    logging.info(f"Attachments will be saved in: {os.path.abspath(attachments_folder)}")
    
    output_csv_path = os.path.join(attachments_folder, "attachments_report.csv")
    logging.info(f"Attachment report will be saved as: {os.path.abspath(output_csv_path)}")
    
    failed_downloads = []
    total_attachments = 0
    failed_attachments = 0

    issue_keys = read_issue_keys(file_path)
    
    with open(output_csv_path, 'w', newline='', encoding='utf-8') as output_csvfile:
        writer = csv.writer(output_csvfile)
        writer.writerow(['Issue Key', 'Filename'])

        for issue_key in issue_keys:
            try:
                response = requests.get(f"{base_url}{issue_key}", auth=auth)
                response.raise_for_status()
                issue_data = response.json()
                attachments = issue_data.get('fields', {}).get('attachment', [])
                
                for attachment in attachments:
                    total_attachments += 1
                    try:
                        attachment_url = attachment['content']
                        attachment_filename = attachment['filename']
                        attachment_response = requests.get(attachment_url, auth=auth, stream=True)
                        attachment_response.raise_for_status()
                        
                        attachment_path = os.path.join(attachments_folder, attachment_filename)
                        with open(attachment_path, 'wb') as f:
                            for chunk in attachment_response.iter_content(chunk_size=8192):
                                f.write(chunk)
                        
                        writer.writerow([issue_key, attachment_filename])
                        
                    except requests.exceptions.RequestException as e:
                        failed_attachments += 1
                        failed_downloads.append((issue_key, attachment_filename, str(e)))
                        logging.error(f"Failed to download {attachment_filename} from issue {issue_key}: {e}")
                    
                    if total_attachments % 5 == 0:
                        logging.info(f"Successfully downloaded {total_attachments} attachments so far...")
                        time.sleep(2)  # Delay to handle rate limiting
            
            except requests.exceptions.RequestException as e:
                logging.error(f"Failed to retrieve issue {issue_key}: {e}")
                failed_downloads.append((issue_key, 'N/A', str(e)))
    
    logging.info(f"Download completed: {total_attachments - failed_attachments} attachments downloaded successfully.")
    logging.info(f"{failed_attachments} attachments failed to download from {len(failed_downloads)} issues.")
    if failed_downloads:
        with open(os.path.join(attachments_folder, 'failed_downloads.log'), 'w', encoding='utf-8') as log_file:
            for issue_key, filename, error in failed_downloads:
                log_file.write(f"Issue Key: {issue_key}, Filename: {filename}, Error: {error}\n")
        logging.info(f"Details of failed downloads logged in: {os.path.join(attachments_folder, 'failed_downloads.log')}")

def main():
    jira_domain, auth = get_jira_auth()

    while True:
        file_path = input("""
Enter the path to your CSV or TXT file. The file must contain a list of issue keys.
- CSV files must have a column named "Issue Key".
- TXT files must have one issue key per line.

Examples:

1. CSV in the same directory as the script:
   issues.csv

2. TXT in a subdirectory called "data":
   data/issues.txt

3. Full path to the file:
   /path/to/your/issues.csv or /path/to/your/issues.txt

File Path: """)
        if file_path.lower().endswith(('.csv', '.txt')):
            break
        else:
            print("Invalid file type. Please enter a CSV or TXT file.")
    
    download_attachments(jira_domain, auth, file_path)

if __name__ == "__main__":
    main()

Disclaimer

This script is provided "as is" and "as available" without warranties and is not officially supported or endorsed by Atlassian. Use at your own risk. Jira API changes may impact functionality.

Cheers!

0 comments

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events