From 924306a11cd6ebba2ffba790f51f505601ce467e Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Wed, 22 Oct 2014 12:43:48 -0600 Subject: [PATCH 1/7] Basic Teamcity agent - only supports guest authentication, default branch. --- checks.d/teamcity.py | 80 ++++++++++++++++++++++++++++++++++++ conf.d/teamcity.yaml.example | 29 +++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 checks.d/teamcity.py create mode 100644 conf.d/teamcity.yaml.example diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py new file mode 100644 index 0000000000..a167c1ef88 --- /dev/null +++ b/checks.d/teamcity.py @@ -0,0 +1,80 @@ +# stdlib +import requests +import time + +# project +from checks import AgentCheck + + +class TeamCity(AgentCheck): + headers = {'Accept': 'application/json'} + server = None + + def __init__(self, name, init_config, agentConfig): + AgentCheck.__init__(self, name, init_config, agentConfig) + self.last_build_ids = {} + if self.init_config.get("server", None) is None: + raise Exception("You must specify a server in teamcity.yaml") + self.server = self.init_config["server"] + + def _initialize_if_required(self, instance_name, build_configuration): + if self.last_build_ids.get(instance_name, None) is None: + self.log.info("Initializing {}".format(instance_name)) + last_build_id = requests.get( + "http://{}/guestAuth/app/rest/builds/?locator=buildType:{},count:1".format(self.server, + build_configuration), + timeout=3000, headers=self.headers).json()["build"][0]["id"] + self.log.info("Last build id for {} is {}.".format(instance_name, last_build_id)) + self.last_build_ids[instance_name] = last_build_id + + def _build_and_send_event(self, new_build, instance_name, is_deployment, host, tags): + self.log.info("Found new build with id {}, saving and alerting.".format(new_build["id"])) + self.last_build_ids[instance_name] = new_build["id"] + + output = { + "timestamp": int(time.time()), + "tags": ["teamcity"], + "alert_type": "info" + } + if is_deployment: + output["event_type"] = "deployment" + output["msg_title"] = "{} deployed to {}".format(instance_name, host) + output["msg_text"] = "Build Number: {}\n\nMore Info: {}".format(new_build["number"], new_build["webUrl"]) + output["tags"].append("deployment") + else: + output["event_type"] = "build" + output["msg_title"] = "Build for {} successful".format(instance_name) + output["msg_text"] = "Build Number: {}\nDeployed To: {}\n\nMore Info: {}".format(new_build["number"], host, + new_build["webUrl"]) + output["tags"].append("build") + + if tags is not None: + output["tags"].extend(tags) + if host is not None: + output["host"] = host + + self.event(output) + + def check(self, instance): + instance_name = instance.get("name") + if instance_name is None: + raise Exception("Each instance must have a name") + build_configuration = instance.get("buildConfiguration") + if build_configuration is None: + raise Exception("Each instance must have a build configuration") + host = instance.get("hostAffected") + tags = instance.get("tags") + is_deployment = instance.get("isDeployment") + + self._initialize_if_required(instance_name, build_configuration) + + new_builds = requests.get( + "http://{}/guestAuth/app/rest/builds/?locator=buildType:{},sinceBuild:id:{},status:SUCCESS".format( + self.server, build_configuration, self.last_build_ids[instance_name]), timeout=3000, + headers=self.headers).json() + + if new_builds["count"] == 0: + self.log.info("No new builds found.") + else: + self._build_and_send_event(new_builds["build"][0], instance_name, is_deployment, host, tags) + diff --git a/conf.d/teamcity.yaml.example b/conf.d/teamcity.yaml.example new file mode 100644 index 0000000000..ae608f46da --- /dev/null +++ b/conf.d/teamcity.yaml.example @@ -0,0 +1,29 @@ +init_config: + +# +# Specify the server name of your teamcity instance here +# +# server: teamcity.mycompany.com + +instances: +# - name: My Website +# buildConfiguration: MyWebsite_Deploy + + # This is the internal build ID of the build configuration you wish to track. + # You can find it labelled as "Build configuration ID" when editing the configuration in question. + +# hostAffected: msicalweb6 + + # Optional, if you wish to override the host that is affected by this build configuration. + # Defaults to the host that the agent is running on. + +# isDeployment: true + + # Optional, this changes the event message slightly to specify that TeamCity was used to deploy something + # rather than just that a successful build happened + +# tags: +# - test + + # Optional, any additional tags you'd like to add to the event + From 692d9e26407a113c1fa175fe6cf0ad9cc0a16bbc Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Thu, 23 Oct 2014 14:47:28 -0600 Subject: [PATCH 2/7] * Correcting usage of is_deployment to support string parsing to bool * Reduced timeouts to 30s * Checking status code before parsing JSON from TeamCity * Utilizing snake_case for Pythonic config file parameters * Removing 'teamcity' tag --- checks.d/teamcity.py | 30 ++++++++++++++++++++---------- conf.d/teamcity.yaml.example | 6 +++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py index a167c1ef88..1203807f63 100644 --- a/checks.d/teamcity.py +++ b/checks.d/teamcity.py @@ -1,6 +1,7 @@ # stdlib import requests import time +import distutils # project from checks import AgentCheck @@ -13,17 +14,20 @@ class TeamCity(AgentCheck): def __init__(self, name, init_config, agentConfig): AgentCheck.__init__(self, name, init_config, agentConfig) self.last_build_ids = {} - if self.init_config.get("server", None) is None: + if self.init_config.get("server") is None: raise Exception("You must specify a server in teamcity.yaml") self.server = self.init_config["server"] def _initialize_if_required(self, instance_name, build_configuration): if self.last_build_ids.get(instance_name, None) is None: self.log.info("Initializing {}".format(instance_name)) - last_build_id = requests.get( + request = requests.get( "http://{}/guestAuth/app/rest/builds/?locator=buildType:{},count:1".format(self.server, build_configuration), - timeout=3000, headers=self.headers).json()["build"][0]["id"] + timeout=30, headers=self.headers) + if request.status_code != requests.codes.ok: + raise Exception("TeamCity reported error on initialization. Status code: {}".format(request.status_code)) + last_build_id = request.json()["build"][0]["id"] self.log.info("Last build id for {} is {}.".format(instance_name, last_build_id)) self.last_build_ids[instance_name] = last_build_id @@ -33,7 +37,6 @@ def _build_and_send_event(self, new_build, instance_name, is_deployment, host, t output = { "timestamp": int(time.time()), - "tags": ["teamcity"], "alert_type": "info" } if is_deployment: @@ -59,19 +62,26 @@ def check(self, instance): instance_name = instance.get("name") if instance_name is None: raise Exception("Each instance must have a name") - build_configuration = instance.get("buildConfiguration") + build_configuration = instance.get("build_configuration") if build_configuration is None: raise Exception("Each instance must have a build configuration") - host = instance.get("hostAffected") + host = instance.get("host_affected") tags = instance.get("tags") - is_deployment = instance.get("isDeployment") + is_deployment = instance.get("is_deployment") + if type(is_deployment) is not bool: + is_deployment = distutils.util.strtobool(instance.get("is_deployment")) self._initialize_if_required(instance_name, build_configuration) - new_builds = requests.get( + request = requests.get( "http://{}/guestAuth/app/rest/builds/?locator=buildType:{},sinceBuild:id:{},status:SUCCESS".format( - self.server, build_configuration, self.last_build_ids[instance_name]), timeout=3000, - headers=self.headers).json() + self.server, build_configuration, self.last_build_ids[instance_name]), timeout=30, + headers=self.headers) + + if request.status_code != requests.codes.ok: + raise Exception("TeamCity reported error on check. Status code: {}".format(request.status_code)) + + new_builds = request.json() if new_builds["count"] == 0: self.log.info("No new builds found.") diff --git a/conf.d/teamcity.yaml.example b/conf.d/teamcity.yaml.example index ae608f46da..0577c7e96b 100644 --- a/conf.d/teamcity.yaml.example +++ b/conf.d/teamcity.yaml.example @@ -7,17 +7,17 @@ init_config: instances: # - name: My Website -# buildConfiguration: MyWebsite_Deploy +# build_configuration: MyWebsite_Deploy # This is the internal build ID of the build configuration you wish to track. # You can find it labelled as "Build configuration ID" when editing the configuration in question. -# hostAffected: msicalweb6 +# host_affected: msicalweb6 # Optional, if you wish to override the host that is affected by this build configuration. # Defaults to the host that the agent is running on. -# isDeployment: true +# is_deployment: true # Optional, this changes the event message slightly to specify that TeamCity was used to deploy something # rather than just that a successful build happened From e9247650d6e125ee92a1e9b0b6972d458695de5a Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Fri, 24 Oct 2014 14:19:01 -0600 Subject: [PATCH 3/7] Falling back to simple string check as a number of python libraries appear unavailable on the windows agent. --- checks.d/teamcity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py index 1203807f63..8441f53fb1 100644 --- a/checks.d/teamcity.py +++ b/checks.d/teamcity.py @@ -1,7 +1,6 @@ # stdlib import requests import time -import distutils # project from checks import AgentCheck @@ -69,7 +68,7 @@ def check(self, instance): tags = instance.get("tags") is_deployment = instance.get("is_deployment") if type(is_deployment) is not bool: - is_deployment = distutils.util.strtobool(instance.get("is_deployment")) + is_deployment = instance.get("is_deployment").lower() == "true" self._initialize_if_required(instance_name, build_configuration) From b63ca005516fa0c1059967400c019adfa12a765b Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Mon, 27 Oct 2014 12:15:17 -0600 Subject: [PATCH 4/7] Fixing problem with appending tags. --- checks.d/teamcity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py index 8441f53fb1..da492a0da0 100644 --- a/checks.d/teamcity.py +++ b/checks.d/teamcity.py @@ -36,7 +36,8 @@ def _build_and_send_event(self, new_build, instance_name, is_deployment, host, t output = { "timestamp": int(time.time()), - "alert_type": "info" + "alert_type": "info", + "tags": [] } if is_deployment: output["event_type"] = "deployment" From b262f2098cb1a79e7146f0790800a0c77b5b0c36 Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Fri, 16 Jan 2015 08:45:57 -0700 Subject: [PATCH 5/7] Fixing string formatting syntax for Python 2.6 compatibility. --- checks.d/teamcity.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py index da492a0da0..7ea633a91b 100644 --- a/checks.d/teamcity.py +++ b/checks.d/teamcity.py @@ -21,17 +21,17 @@ def _initialize_if_required(self, instance_name, build_configuration): if self.last_build_ids.get(instance_name, None) is None: self.log.info("Initializing {}".format(instance_name)) request = requests.get( - "http://{}/guestAuth/app/rest/builds/?locator=buildType:{},count:1".format(self.server, + "http://{0}/guestAuth/app/rest/builds/?locator=buildType:{1},count:1".format(self.server, build_configuration), timeout=30, headers=self.headers) if request.status_code != requests.codes.ok: raise Exception("TeamCity reported error on initialization. Status code: {}".format(request.status_code)) last_build_id = request.json()["build"][0]["id"] - self.log.info("Last build id for {} is {}.".format(instance_name, last_build_id)) + self.log.info("Last build id for {0} is {1}.".format(instance_name, last_build_id)) self.last_build_ids[instance_name] = last_build_id def _build_and_send_event(self, new_build, instance_name, is_deployment, host, tags): - self.log.info("Found new build with id {}, saving and alerting.".format(new_build["id"])) + self.log.info("Found new build with id {0}, saving and alerting.".format(new_build["id"])) self.last_build_ids[instance_name] = new_build["id"] output = { @@ -41,13 +41,13 @@ def _build_and_send_event(self, new_build, instance_name, is_deployment, host, t } if is_deployment: output["event_type"] = "deployment" - output["msg_title"] = "{} deployed to {}".format(instance_name, host) - output["msg_text"] = "Build Number: {}\n\nMore Info: {}".format(new_build["number"], new_build["webUrl"]) + output["msg_title"] = "{0} deployed to {1}".format(instance_name, host) + output["msg_text"] = "Build Number: {0}\n\nMore Info: {1}".format(new_build["number"], new_build["webUrl"]) output["tags"].append("deployment") else: output["event_type"] = "build" - output["msg_title"] = "Build for {} successful".format(instance_name) - output["msg_text"] = "Build Number: {}\nDeployed To: {}\n\nMore Info: {}".format(new_build["number"], host, + output["msg_title"] = "Build for {0} successful".format(instance_name) + output["msg_text"] = "Build Number: {0}\nDeployed To: {1}\n\nMore Info: {2}".format(new_build["number"], host, new_build["webUrl"]) output["tags"].append("build") @@ -74,12 +74,12 @@ def check(self, instance): self._initialize_if_required(instance_name, build_configuration) request = requests.get( - "http://{}/guestAuth/app/rest/builds/?locator=buildType:{},sinceBuild:id:{},status:SUCCESS".format( + "http://{0}/guestAuth/app/rest/builds/?locator=buildType:{1},sinceBuild:id:{2},status:SUCCESS".format( self.server, build_configuration, self.last_build_ids[instance_name]), timeout=30, headers=self.headers) if request.status_code != requests.codes.ok: - raise Exception("TeamCity reported error on check. Status code: {}".format(request.status_code)) + raise Exception("TeamCity reported error on check. Status code: {0}".format(request.status_code)) new_builds = request.json() From 64bd0088f9717f247ed51f73298b1962e0eb6bcf Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Fri, 16 Jan 2015 08:47:06 -0700 Subject: [PATCH 6/7] Missed one spot for string formatting corrections. --- checks.d/teamcity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py index 7ea633a91b..a320731aa4 100644 --- a/checks.d/teamcity.py +++ b/checks.d/teamcity.py @@ -25,7 +25,7 @@ def _initialize_if_required(self, instance_name, build_configuration): build_configuration), timeout=30, headers=self.headers) if request.status_code != requests.codes.ok: - raise Exception("TeamCity reported error on initialization. Status code: {}".format(request.status_code)) + raise Exception("TeamCity reported error on initialization. Status code: {0}".format(request.status_code)) last_build_id = request.json()["build"][0]["id"] self.log.info("Last build id for {0} is {1}.".format(instance_name, last_build_id)) self.last_build_ids[instance_name] = last_build_id From d2e0beb506c3f73c4de343efb015ff16f573b68b Mon Sep 17 00:00:00 2001 From: Paul Kirby Date: Fri, 16 Jan 2015 08:48:20 -0700 Subject: [PATCH 7/7] Missed another spot. Sigh mornings. --- checks.d/teamcity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks.d/teamcity.py b/checks.d/teamcity.py index a320731aa4..4a5eb40582 100644 --- a/checks.d/teamcity.py +++ b/checks.d/teamcity.py @@ -19,7 +19,7 @@ def __init__(self, name, init_config, agentConfig): def _initialize_if_required(self, instance_name, build_configuration): if self.last_build_ids.get(instance_name, None) is None: - self.log.info("Initializing {}".format(instance_name)) + self.log.info("Initializing {0}".format(instance_name)) request = requests.get( "http://{0}/guestAuth/app/rest/builds/?locator=buildType:{1},count:1".format(self.server, build_configuration),