Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rerwote RT stuff with REST 2.0 API #241

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 73 additions & 36 deletions ocflib/infra/rt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from urllib.parse import urlencode

import requests
import json
import copy


class RtTicket(namedtuple('RtTicket', ('number', 'owner', 'subject', 'queue', 'status'))):
Expand All @@ -16,72 +18,99 @@ def __str__(self):
).format(self=self)

@classmethod
def from_number(cls, connection, num):
resp = connection.get('https://rt.ocf.berkeley.edu/REST/1.0/ticket/{}/view'.format(num))
assert resp.status_code == 200, resp.status_code
def from_number(cls, _, num, auth):
"""
Second argument left for compatibility purposes, alghough I think no programme
ever used this function when I wrote this, lol
"""
resp = requests.get("https://rt.ocf.berkeley.edu/REST/2.0/ticket/{0}".format(num), **auth)
assert resp.ok, resp.status_code
assert '200 Ok' in resp.text

lines = resp.text.splitlines()
jsonified = resp.json()

def find(header):
for line in lines:
if line.startswith(header + ': '):
return line.split(': ', 1)[1]
return cls(
number=jsonified["id"],
owner=jsonified["Owner"],
subject=jsonified["Subject"],
queue=jsonified["Queue"],
status=jsonified["Status"],
)

@classmethod
def get_latest(cls, _, queue, auth):
"""
Second argument left for compatibility purposes, alghough I think no programme
ever used this function when I wrote this, lol
"""
resp = requests.get("https://rt.ocf.berkeley.edu/REST/2.0/tickets?query=Queue='{}'&orderby=Created&order=DESC".format(queue), **auth)
assert resp.ok, resp.status_code
assert '200 Ok' in resp.text


jsonified = resp.json()
assert "items" in jsonified
assert len(jsonified["items"]) > 0

jsonified = jsonified["items"][0]

return cls(
number=num,
owner=find('Owner'),
subject=find('Subject'),
queue=find('Queue'),
status=find('Status'),
number=jsonified["id"],
owner=jsonified["Owner"],
subject=jsonified["Subject"],
queue=jsonified["Queue"],
status=jsonified["Status"],
)

@classmethod
def create(cls, connection, queue, requestor, subject, text, **kwargs):
def create(cls, _, queue, requestor, subject, text, auth, **kwargs):
"""Create an RT ticket and returns an instance of the result"""
# RT prefixes multiline strings by a blank space
text = text.replace('\n', '\n ')

data = {
'id': 'ticket/new',
'Queue': queue,
'Requestor': requestor,
'Subject': subject,
'Text': text,
**kwargs,
}

# RT's incoming data format has the form key: value, but these aren't HTTP Headers
body = ''
for k, v in data.items():
body += '{}: {}\n'.format(k, v)
# Look like crap but it works?
# There must be some more elegant way but idk
additional_kwargs = copy.deepcopy(auth)
if "headers" not in additional_kwargs:
additional_kwargs["headers"] = {}
additional_kwargs["headers"]["content-type"] = "application/json"

# RT requires the POST content to be sent with no filename hence 'content': (None, body)
resp = connection.post('https://rt.ocf.berkeley.edu/REST/1.0/ticket/new', files={'content': (None, body)})
resp = requests.post('https://rt.ocf.berkeley.edu/REST/2.0/ticket', json=data, **additional_kwargs)
resp.raise_for_status()
assert '200 Ok' in resp.text

match = re.search(r'Ticket ([0-9]+) created.', resp.text)
assert match, '200 response but no ticket number found in RT response'
assert "id" in resp.json(), '200 response but no ticket number found in RT response'

return resp.json()["id"]

ticket_number = int(match.group(1))
return ticket_number

def rt_connection_auth(*args):
"""Return the authentication required to access REST API.

def rt_connection(user, password):
"""Return a requests Session object authenticated against RT.
Accepts either a RtCredentials, a RtAuthenticationToken, a string (as token),
or two strings (as username and password)

Currently, we only enable access to the REST API from a few select IPs.
"""
s = requests.Session()
resp = s.post(
'https://rt.ocf.berkeley.edu/REST/1.0/',
data=urlencode({'user': user, 'pass': password}),
timeout=20,
)
assert resp.status_code == 200, resp.status_code
assert '200 Ok' in resp.text
return s
if len(args) == 1:
if isinstance(args[0], RtCredentials):
return {"auth": (args[0]["username"], args[0]["password"])}
elif isinstance(args[0], RtAuthenticationToken):
return {"headers": {"Authorization": "token {}".format(args[0]["token"])}}
elif isinstance(args[0], str):
return {"headers": {"Authorization": "token {}".format(args[0])}}
elif len(args) == 2 and all(map(lambda e: isinstance(e, str), args)):
return {"auth": (args[0], args[1])}
raise ValueError("Auth credential provided is not valid")



class RtCredentials(namedtuple('RtCredentials', [
Expand All @@ -93,3 +122,11 @@ class RtCredentials(namedtuple('RtCredentials', [
:param username: str
:param password: str
"""

class RtAuthenticationToken(namedtuple('RtAuthenticationToken', [
'token'
])):
"""Token for programmatically accessing RT.

:param token: str
"""