microsoft / microsoft/azure-devops-python-api

Deleting Pull Request Attachments Prevents Reading Attachments

Open
#499 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
684
Forks
218
Avg merge
8d 10h
Merged PRs (30d)
1

Description

Hello,

I recently ran in to this issue while attempting to manage pull request attachments. It could be a bug with the AZDO API, but I'm specifically using azure-devops-python-api.

Steps to Reproduce

  1. Create multiple pull request attachments via GitClient, and delete at least one. The specific order doesn't matter; you can create two attachments then delete one of them, or create a single attachment, delete it, and create a second attachment.
  2. Attempting to access any non-deleted attachment (via the attachment URL) results in the following error:
{
    "$id": "1",
    "innerException": null,
    "message": "TF400714: File ID 123456789 not found.",
    "typeName": "Microsoft.TeamFoundation.Framework.Server.FileIdNotFoundException, Microsoft.TeamFoundation.Framework.Server",
    "typeKey": "FileIdNotFoundException",
    "errorCode": 0,
    "eventId": 4005
}

Example Code

delete_attachment_bug.py

from azure.devops.v7_0.git.models import *
from azure.devops.v7_0.git.git_client import GitClient
from msrest.authentication import BasicAuthentication
from azure.devops.connection import Connection
from random import randint

import os

def fix_create_attachment(client: GitClient):
    """
    See https://github.com/microsoft/azure-devops-python-api/issues/322 for documentation of this issue.
    """

    def create_attachment(upload_stream, file_name, repository_id, pull_request_id, project=None):
        route_values = {}
        if project is not None:
            route_values['project'] = client._serialize.url('project', project, 'str')
        if file_name is not None:
            route_values['fileName'] = client._serialize.url('file_name', file_name, 'str')
        if repository_id is not None:
            route_values['repositoryId'] = client._serialize.url('repository_id', repository_id, 'str')
        if pull_request_id is not None:
            route_values['pullRequestId'] = client._serialize.url('pull_request_id', pull_request_id, 'int')
        # content = self._client.stream_upload(upload_stream, callback=callback)
        response = client._send(http_method='POST',
                                location_id='965d9361-878b-413b-a494-45d5b5fd8ab7',
                                version='7.0',
                                route_values=route_values,
                                content=upload_stream.read(),
                                media_type='application/octet-stream')
        return client._deserialize('Attachment', response)

    client.create_attachment = create_attachment

pat = os.environ.get("mypersonalaccesstoken")
auth = BasicAuthentication("", pat)
connection = Connection("https://dev.azure.com/someorganization/", auth)
client: GitClient = connection.clients.get_git_client()
fix_create_attachment(client)
pr_id = 123456
project_id = "MyProject"
repo_id = "MyRepo"

def create_attachment():
    with open("cool_pic.png", "rb") as file:
        # Create random name for attachment
        name = f"{randint(0,10000)}.png"
        attachment: Attachment = client.create_attachment(file, name, repo_id, pr_id, project_id)
        print(f"Created attachment: {name}\n(URL: {attachment.url})")

def delete_attachment(name):
    client.delete_attachment(name, repo_id, pr_id, project_id)
    print(f"Deleted attachment: {name}")

This code can be used along with the Python shell to reproduce the issue:

python -i delete_attachment_bug.py
>>> create_attachment()
>>> delete_attachment("nameFromPreviousCall")
>>> create_attachment()

Trying to access the most recently created attachment (via the attachment URL) results in the above error.

I'm using Python 3.7.4 and azure-devops==7.1.0b4.

I haven't been able to find a workaround yet. Note the example code includes a workaround for #322.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with delete_attachment_bug.py and the GitClient create_attachment and delete_attachment calls, then reproduce the sequence in the Python shell. Compare the requests and returned attachment URLs after deletion to determine whether the client or Azure DevOps API causes the FileIdNotFoundException. Done means the cause is established and non-deleted attachments remain readable after the sequence.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, devops, python
Domain
api, devops
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.