Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
chelnak committed Jul 18, 2019
0 parents commit 01d8fe4
Show file tree
Hide file tree
Showing 16 changed files with 408 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# https://editorconfig.org/

root = true

[*.py]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
129 changes: 129 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
env/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.pyc

exporter/*/__pycache__/

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"python.pythonPath": "env/bin/python3",
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true
}
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.7.4-alpine3.10

ADD exporter exporter/
add requirements.txt exporter/requirements.txt

WORKDIR exporter

RUN pip install -r requirements.txt

CMD ["python", "app.py"]
Empty file added LICENCE
Empty file.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Status Cake Exporter
53 changes: 53 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: 0.1.$(rev:r)

trigger:
batch: true
branches:
include:
- "*"

pr: none

pool:
vmImage: 'Ubuntu-16.04'

variables:
IMAGE_NAME: chelnak/status-cake-exporter

steps:

- task: Docker@1
displayName: Build image
inputs:
command: Build an image
imageName: $(IMAGE_NAME)
dockerFile: Dockerfile
addDefaultLabels: false

- task: Docker@1
displayName: Tag image with current build number $(Build.BuildNumber)
inputs:
command: Tag image
imageName: $(IMAGE_NAME)
arguments: $(IMAGE_NAME):$(Build.BuildNumber)

- task: Docker@1
displayName: Docker Hub login
inputs:
command: login
containerregistrytype: Container Registry
dockerRegistryEndpoint: Docker Hub

- task: Docker@1
displayName: Push tagged image
inputs:
command: Push an image
imageName: $(IMAGE_NAME):$(Build.BuildNumber)

- task: Docker@1
displayName: Push tagged image (latest) if master
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
command: Push an image
imageName: '$(IMAGE_NAME):latest'

25 changes: 25 additions & 0 deletions exporter/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python3

import time
import logging

from prometheus_client import start_http_server, REGISTRY
from collectors import test_collector
from utilities import logs, arguments

if __name__ == "__main__":

args = arguments.get_args()

logs.configure_logging(args.log_level)
logger = logging.getLogger(__name__)

logger.info("Starting web server")
start_http_server(8000)

logger.info("Registering collectors")
REGISTRY.register(test_collector.TestCollector(
args.username, args.api_key, args.tags))

while True:
time.sleep(1)
1 change: 1 addition & 0 deletions exporter/collectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/usr/bin/env python3
62 changes: 62 additions & 0 deletions exporter/collectors/test_collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3

import sys
import logging
from prometheus_client.core import GaugeMetricFamily
from status_cake_client import tests

logger = logging.getLogger("test_collector")


def parse_test_response(r):
tests = []
for i in r.json():
tests.append(
{
"test_id": str(i['TestID']),
"test_type": i['TestType'],
"test_name": i['WebsiteName'],
"test_url": i['WebsiteURL'],
"test_status": i['Status'],
"test_uptime": str(i['Uptime'])
}
)

return tests


class TestCollector(object):

def __init__(self, username, api_key, tags):
self.username = username
self.api_key = api_key
self.tags = tags

def collect(self):

logger.info("Collector started")

try:

response = tests.get_tests(self.api_key, self.username, self.tags)

test_results = parse_test_response(response)

label_names = test_results[0].keys()

gauge = GaugeMetricFamily(
"status_cake_tests",
"A basic listing of the tests under the current account.",
labels=label_names)

for i in test_results:
status = 1 if (i["test_status"] == "Up") else 0
gauge.add_metric(i.values(), status)

yield gauge

except Exception as e:
logger.error(e)
sys.exit(1)

logger.info("Collector finished")
3 changes: 3 additions & 0 deletions exporter/status_cake_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env python3

STATUS_CAKE_BASE_URL = "https://app.statuscake.com/API/"
31 changes: 31 additions & 0 deletions exporter/status_cake_client/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3

import requests
import logging
from status_cake_client import STATUS_CAKE_BASE_URL


logger = logging.getLogger(__name__)


def get_tests(apikey, username, tags=""):
endpoint = "Tests"
request_url = "{base}{endpoint}".format(
base=STATUS_CAKE_BASE_URL, endpoint=endpoint)

logger.info("Sending request to {request_url}".format(
request_url=request_url))

headers = {
"API": apikey,
"Username": username
}

params = {
"tags": tags
}

response = requests.get(url=request_url, params=params, headers=headers)
response.raise_for_status()

return response
1 change: 1 addition & 0 deletions exporter/utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/usr/bin/env python3
45 changes: 45 additions & 0 deletions exporter/utilities/arguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python3

import sys
import configargparse


def get_args():

parser = configargparse.ArgParser()
parser.add("--username",
dest="username",
env_var="USERNAME",
help='Username for the account')

parser.add("--api-key",
dest="api_key",
env_var="API_KEY",
help="API key for the account")

parser.add("--tests.tags",
dest="tags",
env_var="TAGS",
help="A comma separated list of tags used to filter tests "
"returned from the api")

parser.add("--logging.level",
dest="log_level",
env_var="LOG_LEVEL",
default="info",
choices={'debug', 'info', 'warn', 'error'},
help="Set a log level for the application")

args = parser.parse_args()

if args.username is None:
print("Required argument --username is missing")
print(parser.print_help())
sys.exit(1)

if args.api_key is None:
print("Required argument --username is missing")
print(parser.print_help())
sys.exit(1)

return args
Loading

0 comments on commit 01d8fe4

Please sign in to comment.