Skip to content

Feature/jenkins/last parameterized build query #4

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
__pycache__
*.egg-info
build/
*.pyc
42 changes: 42 additions & 0 deletions bin/last_deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import os
from datetime import datetime, timedelta

from cad.datasources.jenkins import api
from cad.datasources.jenkins.queries import JenkinsLastSuccessfulParameterizedBuildQuery
from cad.engine.executor import Executor
from cad.heuristics.deploy import Deploy
from cad.heuristics.evaulators import SingleValueThresholdEvaluator

deploy_playbook = Deploy(
last_deploy=SingleValueThresholdEvaluator(
name='LastDeploy < 24 hours!',
query=JenkinsLastSuccessfulParameterizedBuildQuery(
client=api.Client(
username=os.environ['JENKINS_USERNAME'],
password=os.environ['JENKINS_PASSWORD'],
job_build_url=os.environ['JENKINS_JOB_BUILD_URL']
),
match={
'ENV': os.environ['DEPLOY_ENV'],
'REGION': os.environ['DEPLOY_REGION'],
'SERVICE': os.environ['DEPLOY_SERVICE']
},
),
comparator=lambda x: x >= datetime.now() - timedelta(hours=24)
)
)

if __name__ == '__main__':
import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)

Executor(deploy_playbook).run()
Empty file added cad/datasources/__init__.py
Empty file.
Empty file.
45 changes: 45 additions & 0 deletions cad/datasources/jenkins/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from datetime import datetime

import requests


BUILD_SUCCESS = 'SUCCESS'
PARAMETERS_ACTION = 'hudson.model.ParametersAction'


class Build:
def __init__(self, data):
self.data = data

def is_success(self):
return self.data['result'] == BUILD_SUCCESS

def params(self):
# can a job have multiple params????
for action in self.data.get('actions', []):
if action['_class'] == PARAMETERS_ACTION:
return {p['name']:p['value'] for p in action.get('parameters', [])}
return {}

def datetime(self):
return datetime.fromtimestamp(self.data['timestamp'] / 1000)


class Client:
def __init__(self, username, password, job_build_url, requests=None):
self.username = username
self.password = password
self.job_build_url = job_build_url
self.requests = requests if requests is None else requests

def builds(self):
query = {
'tree': 'builds[actions[parameters[name,value]],number,status,timestamp,id,result,parameter]'
}
resp = requests.get(
self.job_build_url,
params=query,
auth=(self.username, self.password)
)
assert resp.status_code == 200, resp.status_code
return [Build(b) for b in resp.json().get('builds')]
27 changes: 27 additions & 0 deletions cad/datasources/jenkins/queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import logging

logger = logging.getLogger(__name__)


class JenkinsLastSuccessfulParameterizedBuildQuery:
def __init__(self, client, match={}):
self.c = client
self.match = match

def result(self):
builds = self.c.builds()
result = []
for build in builds:
if build.is_success():
# check to see if this build matches the match dict
# get all keys from match
build_params = build.params()
params_to_check = {k:build_params[k] for k in self.match.keys()}
if self.match == params_to_check:
logger.debug({'match': params_to_check})
result = [build.datetime()]
break

logger.debug({'result': result})
return result

2 changes: 1 addition & 1 deletion cad/engine/executor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from cad.heuristics.nodes import End
from cad.heuristics.nodes import End, Alert

ROOT_NODE = 0
EDGE_TO = 1
Expand Down
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)
root.addHandler(handler)
Empty file added tests/datasources/__init__.py
Empty file.
Empty file.
75 changes: 75 additions & 0 deletions tests/datasources/jenkins/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import unittest

from cad.datasources.jenkins.api import Build


class APIClientTestCase(unittest.TestCase):

def test_client_invalid_status_code(self):
self.fail()

def test_client_returns_builds(self):
self.fail()


class BuildTestCase(unittest.TestCase):

def test_is_success_success(self):
self.assertTrue(
Build(
data={
'id': '34',
'number': 34,
'result': 'SUCCESS',
'timestamp': 1536267817556
}
)
)

def test_build_params_no_actions(self):
self.fail()

def test_build_params_no_parameters(self):
self.fail()

def test_build_params_has_params(self):
self.assertEqual(
{
'AMI': '',
'ENV': 'staging',
'REGION': 'us-west-2',
'SERVICE': 'my-service'
},
Build(
data={
'_class': 'org.jenkinsci.plugins.workflow.job.WorkflowRun',
'actions': [
{
'_class': 'hudson.model.ParametersAction',
'parameters': [
{
'_class': 'hudson.model.StringParameterValue',
'name': 'ENV',
'value': 'staging'
},
{
'_class': 'hudson.model.StringParameterValue',
'name': 'REGION',
'value': 'us-west-2'
},
{
'_class': 'hudson.model.StringParameterValue',
'name': 'SERVICE',
'value': 'my-service'
},
{
'_class': 'hudson.model.StringParameterValue',
'name': 'AMI',
'value': ''
}
]
}
]
}
).params()
)
50 changes: 50 additions & 0 deletions tests/datasources/jenkins/test_queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest
from datetime import datetime
from unittest.mock import MagicMock

from cad.datasources.jenkins.api import Build
from cad.datasources.jenkins.queries import JenkinsLastSuccessfulParameterizedBuildQuery


class JenkinsLastSuccessfulParameterizedBuildQueryTestCase(unittest.TestCase):
def test_result_builds_no_success(self):
self.fail()

def test_result_builds_no_match(self):
self.fail()

def test_result_builds_match(self):
client = MagicMock()
client.builds.return_value = [
Build(
data={
'id': '34',
'number': 34,
'result': 'SUCCESS',
'timestamp': 1536267817556,
'_class': 'org.jenkinsci.plugins.workflow.job.WorkflowRun',
'actions': [
{
'_class': 'hudson.model.ParametersAction',
'parameters': [
{
'_class': 'hudson.model.StringParameterValue',
'name': 'SERVICE',
'value': 'test-service'
},
]
}
]

}
)
]
self.assertEqual(
[datetime(2018, 9, 6, 17, 3, 37, 556000)],
JenkinsLastSuccessfulParameterizedBuildQuery(
client=client,
match={
'SERVICE': 'test-service'
}
).result()
)