Forums

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

Automate Jira Attachment Deletion with a Python Script

Hi Atlassian Community!

I'm bringing a new custom Python script to automate the deletion of attachments in Jira issues. The idea is to facilitate this key process when managing attachments in specific projects or in the whole Jira instance.

The solution

This script utilizes Jira Cloud REST API endpoints (like this one) to identify and delete attachments associated with specified issues. It processes a list of issue keys from either a CSV or TXT file, ensuring a smooth and effective attachment management experience. The script logs all activities, including successful deletions and any errors encountered, providing a comprehensive overview of the operation.

Here's what the script focuses on:

  • Attachment deletion per issue: Identifies and deletes all attachments from each specified Jira issue
  • Error logging: Logs details of any failed deletion attempts for further analysis
  • CSV and TXT file support: Extracts issue keys from either a structured CSV file or a simple TXT file

Key Features

  • Processes multiple issues in a single run
  • Logs detailed information about each deletion attempt in a log file, including timestamps and error details if any
  • Prompts for your Jira Cloud domain, email, and API token for secure authentication
  • Handles errors gracefully, ensuring robust operation even when encountering issues with specific attachments.

Preparation

  1. Ensure the user running the script has the necessary permissions to delete attachments in the relevant Jira projects
    (Delete own attachments and Delete all attachments)
  2. Install the requests library: pip install requests
  3. Prepare your Jira Cloud domain (your-domain.atlassian.net), email address, and API token

The Script

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

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
log_filename = f"jira_attachment_delete_{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)
            lower_case_headers = {header.lower(): header for header in reader.fieldnames}
            issue_key_column = lower_case_headers.get('issue key')
            if not issue_key_column:
                raise KeyError("CSV file must contain a column named 'Issue Key' (case-insensitive).")
            for row in reader:
                issue_keys.append(row[issue_key_column])
    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 delete_attachments(jira_domain, auth, file_path):
    base_url = f"https://{jira_domain}/rest/api/3/issue/"
    
    failed_deletions = []
    total_attachments = 0
    failed_attachments = 0

    issue_keys = read_issue_keys(file_path)
    
    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
                attachment_id = attachment['id']
                attachment_filename = attachment['filename']
                try:
                    delete_url = f"https://{jira_domain}/rest/api/3/attachment/{attachment_id}"
                    delete_response = requests.delete(delete_url, auth=auth)
                    delete_response.raise_for_status()
                    logging.info(f"Successfully deleted {attachment_filename} from issue {issue_key}.")
                except requests.exceptions.RequestException as e:
                    failed_attachments += 1
                    failed_deletions.append((issue_key, attachment_filename, str(e)))
                    logging.error(f"Failed to delete {attachment_filename} from issue {issue_key}: {e}")
        
        except requests.exceptions.RequestException as e:
            logging.error(f"Failed to retrieve issue {issue_key}: {e}")
            failed_deletions.append((issue_key, 'N/A', str(e)))
    
    logging.info(f"Deletion completed: {total_attachments - failed_attachments} attachments deleted successfully.")
    logging.info(f"{failed_attachments} attachments failed to delete from {len(failed_deletions)} issues.")
    if failed_deletions:
        with open('failed_deletions.log', 'w', encoding='utf-8') as log_file:
            for issue_key, filename, error in failed_deletions:
                log_file.write(f"Issue Key: {issue_key}, Filename: {filename}, Error: {error}\n")
        logging.info(f"Details of failed deletions logged in: failed_deletions.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.")
    
    delete_attachments(jira_domain, auth, file_path)

if __name__ == "__main__":
    main()

Preview:

AttachmentDeletion.jpg

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!

2 comments

Mohammed Abdul
Contributor
July 21, 2025

Thanks for deploying this

Do you have any script for uploading attachments to Asset objects so in essence a reverse of your script where you delete attachment, we would like to import attachment to devices in Jira Asset management hope this makes sense.

 

Great work :) 

Like Morgan Watts likes this
Morgan Watts
Contributor
July 21, 2025

Thanks for this!

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events