pythonazure-devopsazure-devops-rest-apiazure-rest-apiazure-service-principal

How to upload an HTML file to DevOps work item using bearer token in Python?


I’m trying to upload an HTML file as an attachment to a work item in Azure DevOps using Azure DevOps REST API. I’m using Microsoft Entra ID (Azure AD) for authentication, and I’ve managed to get the bearer token working in Python, but I’m not sure about the next steps for uploading the HTML file.

I’ve already found this API documentation on how to upload text files, but I’m wondering if uploading an HTML file is any different from other types of files, or if I need to handle it in a specific way.

Here’s what I’ve got so far to obtain the Bearer token:

import requests

tenant_id = 'tenant-id'
client_id = 'client-id'
client_secret = 'client-secret'

token_url = f'https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token'

token_data = {
    'client_id': client_id,
    'scope': '499b84ac-1321-427f-aa17-267ca6975798/.default', 
    'client_secret': client_secret,
    'grant_type': 'client_credentials'
}

response = requests.post(token_url, data=token_data)

if response.status_code == 200:
    bearer_token = response.json().get('access_token')
    print(f"Token obtained successfully: {bearer_token}")
else:
    print(f"Failed to get token. Status code: {response.status_code}")
    print(response.text)

The problem:

I’m able to get the token just fine, but I’m a little unsure about how to upload the HTML file as an attachment to a work item. Specifically:

Is the process for uploading an HTML file the same as uploading other types of files, or are there any special considerations I need to keep in mind?


Solution

  • To upload HTML file as an attachment to work item in Azure DevOps with bearer token, you can make use of below sample python code:

    import requests
    
    tenant_id = 'tenantId'
    client_id = 'appId'
    client_secret = 'secret'
    organization = 'devopsorgname'
    project = 'projname'
    file_path = r'C:\\local\\pathTo\\sample.html'
    work_item_id = '3'
    
    def get_bearer_token():
        token_url = f'https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token'
        token_data = {
            'client_id': client_id,
            'scope': '499b84ac-1321-427f-aa17-267ca6975798/.default',
            'client_secret': client_secret,
            'grant_type': 'client_credentials'
        }
        response = requests.post(token_url, data=token_data)
        if response.status_code == 200:
            return response.json().get('access_token')
        else:
            print(f"Failed to get token. Status code: {response.status_code}")
            print(response.text)
            exit(1)
    
    def upload_attachment(bearer_token):
        upload_url = f'https://dev.azure.com/{organization}/{project}/_apis/wit/attachments?fileName=sample.html&api-version=7.1-preview'
        headers = {
            'Authorization': f'Bearer {bearer_token}',
            'Content-Type': 'application/octet-stream'
        }
        with open(file_path, 'rb') as file:
            response = requests.post(upload_url, headers=headers, data=file)
        if response.status_code == 201:
            return response.json()['url']
        else:
            print(f"Failed to upload attachment. Status: {response.status_code}")
            print(response.text)
            exit(1)
    
    def link_attachment_to_work_item(bearer_token, attachment_url):
        work_item_url = f'https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/{work_item_id}?api-version=7.1-preview.3'
        json_payload = [
            {
                "op": "add",
                "path": "/relations/-",
                "value": {
                    "rel": "AttachedFile",
                    "url": attachment_url,
                    "attributes": {
                        "comment": "Uploading HTML attachment"
                    }
                }
            }
        ]
        headers = {
            'Authorization': f'Bearer {bearer_token}',
            'Content-Type': 'application/json-patch+json'
        }
        response = requests.patch(work_item_url, headers=headers, json=json_payload)
        if response.status_code == 200:
            print("HTML file successfully linked to the work item.")
        else:
            print(f"Failed to link attachment. Status: {response.status_code}")
            print(response.text)
            exit(1)
    
    if __name__ == "__main__":
        bearer_token = get_bearer_token()
        print(f"Token obtained successfully")
        attachment_url = upload_attachment(bearer_token)
        print(f"Attachment uploaded successfully: {attachment_url}")
        link_attachment_to_work_item(bearer_token, attachment_url)
    

    Response:

    enter image description here

    To confirm that, I checked the same in Azure DevOps Portal where attachment added successfully to work item like this:

    enter image description here