system.net.httpClient file parameter for Jira attachment

Hi,
I'm trying to post attachment to Jira Sofware Cloud using system.net.httpClient

Jira documentation :
(https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-rest-api-3-issue-issueidorkey-attachments-post)

I'm using the following code :

def addAttachment(domain, filepath, issue, user, passwd):
baseurl	= "https://" + domain + "/" + str(issue) + "/attachments"

headers = {
   "Accept": "application/json",
   "X-Atlassian-Token": "no-check",
}

payload = filepath

client    = system.net.httpClient()
response = client.post(baseurl, username=user, password=passwd, file=payload, headers=headers)

if response.good:
	return str(response) + " | " + response.text
else :
	return str(response) + " | " + response.text

resulting in the following error :

..atlassian.net/rest/api/3/issue/10334/attachments' [400]> | {"message":"org.apache.commons.fileupload.FileUploadBase$InvalidContentTypeException: the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is application/octet-stream","status-code":500,"stack-trace":""}

It seems like the file parameter is not fully compliant with what Jira is waiting for.

Could you help me solving this issue ?

Thank you in advance

Guillaume

Tip: use the </> code formatting button to preserve code indentation and apply syntax highlighting. It's essential for Python and makes any code much easier to read. There's an edit button 🖉 below your post so you can fix it.

1 Like

httpClient doesn't currently have any builtin support for form data requests, unfortunately.

You'll have to manually create the data in the appropriate 'shape' - basically, write out the required header and then append the encoded attachment data, and set the Content-Type header manually.

Hi,
May i know if you were able to add attachments from ignition to a jira issue, I constructed the request body and was able to add attachments that were less than 50kb but attachments that are above 50kb, throws an error saying bad gateway 502.

Could you share your thoughts on this.

Hi @S_ANIL_KUMAR,

I was able to post attachments from Ignition.
My issue was for attachements that were over 64 Kb.

First, I changed the code of _socket.py (located in C:\Program Files\Inductive Automation\Ignition\user-lib\pylib on my server).
Changed the line 1207 :

    sendall = send   # FIXME see note above!

by :

def sendall(self, data, flags=0):
        chunk_size = 8192
        length = len(data)
        data_view = memoryview(data)
        idx = 0
        while idx < length:
            bytes_sent = self.send(data_view[idx:idx + chunk_size], flags=flags)
            idx += bytes_sent

Then, I built the code below (I used some classes but you can simplify it as you need) :

from abc import ABCMeta, abstractmethod

# Abstract class -- do not instanciate
class JiraObject():
	__metaclass__ = ABCMeta
	
	baseUrl 	= "https://*.atlassian.net/rest/api/3/"
	username 	= "username"
	token 		= "apitoken" 
	proxyUrl 	= "proxyUrl"
	
	@abstractmethod
	def __init__(self):
		self.id = None
		
	def __getAuthToken__(self):
		from requests.auth import HTTPBasicAuth
		
		return HTTPBasicAuth(self.username, self.token)
		
	def __getProxy__(self):
		return {"https" :self.proxyUrl}
		
	def get(self):
		pass
		
	def post(self):
		pass

# Abstract class -- do not instanciate
class Issue(JiraObject):
	
	@abstractmethod
	def __init__(self, summary, description, project, issueType):
		JiraObject.__init__(self)
		self.summary 		= summary
		self.description 	= description
		self.project 		= project
		self.issueType 		= issueType
	
	def toString(self):
		import json
		issue = {"fields": 
					{ 	"summary"	: self.summary,
				    	"project"	: {"key"	: self.project},
					    "issuetype"	: {"name"	: self.issueType}
				    }
				}
		return json.dumps(issue)
	
	def get(self):
		pass
	
	def post(self):
		pass

class Bug(Issue):

	def __init__(self, summary, description, project, issueType="Bug"):
		Issue.__init__(self, summary, description, project, issueType)
		
	def post(self, filePath=None):
		import requests
		import json
		
		url = self.baseUrl + "issue"
		
		headers = {"Accept": "application/json", "Content-Type": "application/json"}
		
		with requests.request("POST", url, headers=headers, proxies=self.__getProxy__(), auth=self.__getAuthToken__(), verify=False, data=self.toString()) as response :
			if response.ok:
				self.id = json.loads(response.text)["id"] 
				if filePath != None :
					attach = self.__addAttachment__(filePath)
					if attach != None :
						return self.id
					else:
						return "attach " + str(attach)
				else :
					return self.id
			else:
				return "pb >> " + response.text
	
	def __addAttachment__(self, filePath):
		import requests
		
		url = self.baseUrl + "issue/" + str(self.id) + "/attachments"
	
		headers = {'Accept': 'application/json', 'X-Atlassian-Token': 'no-check', "Accept-Encoding":"deflate"}
			
		with open(filePath, "rb") as f:
			files= {"file": ("capture.png", f, "application-type")}
			with requests.request("POST", url, headers=headers, files=files, proxies=self.__getProxy__(), auth=self.__getAuthToken__(), verify=False) as response
			if response.ok :
				return str(response), str(response.text)
			else :
				return "ko" + response.text

Let me know if it helps :slightly_smiling_face:

Hi @guillaume.voisin , I did change the line1207 in the _socket.py file. But I don't see any difference in the result. As I am using the httplib/urrlib2 module to send post requests.
I've constructed a request body to add the attachment for a particular jira issue. May I know what should be corrected to add attachments that are above 50kb.(mp4, jpeg etc.)

from base64 import b64encode
import os
import urllib2
from org.python.core import PyByteArray

jira_url = "https://domain-name/rest/api/2/issue/{issueID}/attachments"
username = "email.com"
api_token = "api_token"
auth =b64encode(username +":"+api_token)

file_path = system.file.openFile()
file_data =system.file.readFileAsBytes(file_path)

Prepare the request

request = urllib2.Request(jira_url)
boundary = "----" + "UniqueBoundaryArray"
request.add_header("Content-Type", "multipart/form-data; boundary={}".format(boundary))

Encode credentials for basic authentication

request.add_header("Authorization", "Basic {}".format(auth))
request.add_header("X-Atlassian-Token","no-check")

Build the request body

body = b''
body += b"--" + boundary + b"\r\n"
body += b"Content-Disposition: form-data; name="file"; filename="{}"\r\n".format(os.path.basename(file_path))
body += b"Content-Type: multipart/form-data;boundary:{}\r\n\r\n".format(boundary)
body += PyByteArray(file_data)
body += b"\r\n--" + boundary + b"--\r\n"

Set the request method and data

request.get_method = lambda: 'POST'
request.add_data(body)

Preformatted textSend the request

response = urllib2.urlopen(request)
print response.getcode()
print response.info()