diff --git a/pyteamcity/future/build.py b/pyteamcity/future/build.py index 78de907..5cbed35 100644 --- a/pyteamcity/future/build.py +++ b/pyteamcity/future/build.py @@ -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): diff --git a/pyteamcity/future/core/utils.py b/pyteamcity/future/core/utils.py index 923391a..a143205 100644 --- a/pyteamcity/future/core/utils.py +++ b/pyteamcity/future/core/utils.py @@ -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) diff --git a/pyteamcity/future/teamcity.py b/pyteamcity/future/teamcity.py index 997db92..07c9e36 100644 --- a/pyteamcity/future/teamcity.py +++ b/pyteamcity/future/teamcity.py @@ -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,6 +55,7 @@ def __repr__(self): class TeamCity(object): username = None password = None + token = None server = None port = None protocol = None @@ -44,7 +63,7 @@ class TeamCity(object): 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: