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

Add token auth and enhance build attributes #102

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
101 changes: 98 additions & 3 deletions pyteamcity/future/build.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from six.moves.urllib.parse import quote
import tempfile
import os

from . import exceptions
from .core.parameter import Parameter
@@ -8,7 +10,10 @@
from .agent import Agent
from .artifact import Artifact
from .build_type import BuildTypeQuerySet
from .project import ProjectQuerySet
from .user import User
from .build_statistics import BuildStatistics
from .test_occurrences import TestOccurrences


class Build(object):
@@ -33,22 +38,47 @@ def __init__(self, id, number,
self.teamcity = self.build_query_set.teamcity
self._data_dict = data_dict

@property
def data(self):
return self._data_dict

@property
def user(self):
if 'user' in self._data_dict.get('triggered', {}):
return User.from_dict(self._data_dict['triggered']['user'])

@property
def dependencies(self):
if 'build' in self._data_dict.get('snapshot-dependencies', {}):
return [Build.from_dict(b) for b in self._data_dict['snapshot-dependencies']['build']]
return []

@property
def queued_date(self):
return parse_date_string(self.queued_date_string)

@property
def start_date(self):
if self.state == "queued":
return parse_date_string(self._data_dict.get("startEstimate", None))
return parse_date_string(self.start_date_string)

@property
def finish_date(self):
return parse_date_string(self.finish_date_string)
#running build there will not be finish date
return parse_date_string(self.finish_date_string) if self.finish_date_string else None

@property
def status_text(self):
return self._data_dict.get('statusText')

@property
def wait_reason(self):
return self._data_dict.get('waitReason', "")

@property
def test_occurances(self):
return TestOccurrences.from_dict(self.id, self._data_dict.get('testOccurrences', {}))

@property
def agent(self):
@@ -61,6 +91,13 @@ def build_type(self):
build_type = BuildTypeQuerySet(teamcity).get(id=build_type_id)

return build_type

@property
def project(self):
teamcity = self.build_query_set.teamcity
project_id = self._data_dict.get('buildType', {}).get('projectId')
return ProjectQuerySet(teamcity).get(id=project_id) if project_id else None


def __repr__(self):
return '<%s.%s: id=%r build_type_id=%r number=%r>' % (
@@ -70,6 +107,19 @@ def __repr__(self):
self.build_type_id,
self.number)

def _update_dict(self, d):
self.id=d.get('id', self.id)
self.number=d.get('number', self.number)
self.queued_date_string=d.get('queuedDate', self.queued_date_string)
self.start_date_string=d.get('startDate', self.start_date_string)
self.finish_date_string=d.get('finishDate', self.finish_date_string)
self.build_type_id=d.get('buildTypeId', self.build_type_id)
self.state=d.get('state', self.state)
self.status=d.get('status', self.status)
self.branch_name=d.get('branchName', self.branch_name)
self.href=d.get('href', self.href)
self._data_dict=d if d else self._data_dict

@classmethod
def from_dict(cls, d, build_query_set=None, teamcity=None):
return Build(
@@ -136,7 +186,40 @@ def get_build_log(self, archived=False, content_length=None):
res = self.teamcity.session.get(url)
raise_on_status(res)
return res.text


def get_build_log_file(self, archived=False):
url = '/downloadBuildLog.html?buildId=%s' % self.id
url = self.teamcity.base_url + url
with tempfile.TemporaryDirectory() as tmp:
tmp_dir = tmp

os.makedirs(tmp_dir)
log_file = os.path.join(tmp_dir, "tc_log_" + str(self.id))

if archived:
url = url + '&archived=true'
with self.teamcity.session.get(url, stream=True) as res:
raise_on_status(res)
with open(log_file, 'wb') as f:
for chunk in res.iter_content(chunk_size=8192):
# If you have chunk encoded response uncomment if
# and set chunk_size parameter to None.
f.write(chunk)

return log_file

def refresh(self):
res = self.teamcity.session.get(self.api_url)
raise_on_status(res)
data = res.json()
return self._update_dict(data)

def statistics(self):
res = self.teamcity.session.get(self.api_url+"/statistics")
raise_on_status(res)
data = res.json()
return BuildStatistics.from_dict(self.id, data)

@property
def pinned(self):
url = self.teamcity.base_base_url + self.href + '/pin'
@@ -167,9 +250,11 @@ def filter(self,
build_type=None, number=None, branch=None, user=None,
tags=None, pinned=None,
since_build=None, since_date=None, status=None,
start_date_from=None, finish_date_from=None,
agent_name=None, personal=None,
canceled=None, failed_to_start=None, running=None,
start=None, count=None, lookup_limit=None):
start=None, count=None, lookup_limit=None,
default_filter=None, snapshots_from=None, snapshots_to=None):
if id is not None:
self._add_pred('id', id)
if project is not None:
@@ -213,6 +298,16 @@ def filter(self,
self._add_pred('count', count)
if lookup_limit is not None:
self._add_pred('lookupLimit', lookup_limit)
if default_filter is not None:
self._add_pred('defaultFilter', default_filter)
if snapshots_from is not None:
self._add_pred('snapshotDependency', "(from:%s)" % snapshots_from)
if snapshots_to is not None:
self._add_pred('snapshotDependency', "(to:%s)" % snapshots_to)
if start_date_from is not None:
self._add_pred('startDate', "(date:%s,condition:after)" % self._get_since_date(start_date_from))
if finish_date_from is not None:
self._add_pred('finishDate', "(date:%s,condition:after)" % self._get_since_date(finish_date_from))
return self

def _get_since_date(self, since_date):
3 changes: 3 additions & 0 deletions pyteamcity/future/core/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import dateutil.parser
from datetime import timezone

from .. import exceptions


def parse_date_string(date_string):
if date_string is None:
return None
return dateutil.parser.parse(date_string)


29 changes: 26 additions & 3 deletions pyteamcity/future/teamcity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

import requests

from requests.auth import AuthBase
from .core.manager import Manager
from .core.utils import parse_date_string, raise_on_status

@@ -16,6 +16,24 @@
from .user_group import UserGroupQuerySet
from .vcs_root import VCSRootQuerySet

class TokenAuth(AuthBase):
"""Token Authentication to the given Request object."""

def __init__(self, token):
self.token = token

def __eq__(self, other):
return all([
self.token == getattr(other, 'token', None)
])

def __ne__(self, other):
return not self == other

def __call__(self, r):
r.headers['Authorization'] = f"Bearer {self.token}"
return r


class Plugin(object):
def __init__(self, name, display_name, version, load_path):
@@ -37,14 +55,15 @@ def __repr__(self):
class TeamCity(object):
username = None
password = None
token = None
server = None
port = None
protocol = None
session = None
projects = None

def __init__(self,
username=None, password=None,
username=None, password=None, token=None,
protocol='http', server='127.0.0.1', port=None,
session=None):
self.username = username
@@ -54,6 +73,7 @@ def __init__(self,
self.port = port or (443 if protocol == 'https' else 80)
self.session = session or requests.Session()
self.session.auth = (username, password)
self.token = token
self.session.headers['Accept'] = 'application/json'
self.projects = Manager(
teamcity=self,
@@ -93,7 +113,10 @@ def __init__(self,
if self.protocol == 'https' and self.port != 443:
self.base_base_url += ':%d' % self.port

if self.username and self.password:
if token:
self.base_url = self.base_base_url
self.session.auth = TokenAuth(self.token)
elif self.username and self.password:
self.base_url = self.base_base_url + '/httpAuth'
self.auth = (self.username, self.password)
else: