Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ config.py
*.un~
*.pyc
.coverage
env/
*.orig
.noseids
2 changes: 2 additions & 0 deletions ansible_hosts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[localhost]
127.0.0.1
59 changes: 44 additions & 15 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import hashlib
import json
import requests
import ansible.playbook
from ansible import callbacks
from ansible import utils
from ansible.errors import AnsibleError

app = Flask(__name__)

Expand Down Expand Up @@ -52,23 +56,51 @@ def event_handler():
else:
return abort(403)

def run_ansible_playbook(ansible_hosts, playbook):
"""
Run Ansible like ansible-playbook command. Similar to:
ansible-playbook -i ansible_hosts playbook.yml
"""
stats = callbacks.AggregateStats()
playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY)
inventory = ansible.inventory.Inventory(ansible_hosts)
runner_cb = callbacks.PlaybookRunnerCallbacks(stats,
verbose=utils.VERBOSITY)
pb = ansible.playbook.PlayBook(playbook=playbook,
callbacks=playbook_cb,
runner_callbacks=runner_cb,
stats=stats, inventory=inventory)
pb.run()

def process_deployment(deployment):
"""Process deployment."""
try:
repo = config.REPOS.get(deployment['repository']['full_name'])
if repo:
update_deployment(deployment, status='pending')
for command in repo['commands']:
p = Popen(command, cwd=repo['folder'], stderr=PIPE)
return_code = p.wait()
if return_code != 0:
raise CalledProcessError(return_code,
command,
output=p.communicate())
update_deployment(deployment, status='success')
return True
# update_deployment(deployment, status='error')
# ansible_hosts and Playbook defined? Then run only Ansible.
if 'ansible_hosts' in repo and 'ansible_playbook' in repo:
run_ansible_playbook(repo['ansible_hosts'],
repo['ansible_playbook'])
update_deployment(deployment, status='success')
return True
else:
for command in repo['commands']:
p = Popen(command, cwd=repo['folder'], stderr=PIPE)
return_code = p.wait()
if return_code != 0:
raise CalledProcessError(return_code,
command,
output=p.communicate())
update_deployment(deployment, status='success')
return True
except KeyError as e:
message = "ansible playbook or host file is missing in config file."
update_deployment(deployment, status='error', message=message)
return False
except AnsibleError as e:
update_deployment(deployment, status='error', message=str(e))
return False
except CalledProcessError as e:
message = "command: %s ERROR: %s" % (e.cmd, e.output[1])
update_deployment(deployment, status='error', message=message)
Expand All @@ -81,19 +113,16 @@ def process_deployment(deployment):

def create_deployment(pull_request, token):
"""Create a deployment."""
repo = config.REPOS.get(pull_request['head']['repo']['full_name'])
user = pull_request['user']['login']
# owner = pull_request['head']['repo']['owner']['login']
repo_name = pull_request['head']['repo']['full_name']
repo = pull_request['head']['repo']['full_name']
payload = {'environment': 'production', 'deploy_user': user}
url = 'https://api.github.com/repos/%s/deployments' % (repo_name)
url = 'https://api.github.com/repos/%s/deployments' % (repo)
headers = {'Content-type': 'application/json'}
auth = (token, '')
data = {'ref': pull_request['head']['ref'],
'payload': payload,
'description': 'mydesc'}
if repo.get('required_contexts'):
data['required_contexts'] = repo.get('required_contexts')
deployment = requests.post(url, data=json.dumps(data), headers=headers,
auth=auth)
# print deployment
Expand Down
7 changes: 7 additions & 0 deletions config.py.ansible-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DEBUG = False
SECRET = 'yoursecret'
REPOS = [{'repo': 'user/repo',
ansible_hosts: 'ansible_hosts',
ansible_playbook: 'playbook.yml'
}
]
6 changes: 6 additions & 0 deletions playbook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- hosts: 127.0.0.1
connection: local
tasks:
- name: Hello World
debug: msg="Hello World!"
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
flask
requests
ansible
145 changes: 145 additions & 0 deletions test/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,151 @@
}
}

deployment_ansible ={
"deployment": {
"url": "https://api.github.com/repos/user/ansible/repo/176855",
"id": 176855,
"sha": "8914f746f71c6bc5d749613563a16c01edc95aa6",
"ref": "nuevo",
"task": "deploy",
"payload": {
"environment": "production",
"deploy_user": "user"
},
"environment": "production",
"description": "mydesc",
"creator": {
"login": "user",
"id": 131838,
"avatar_url": "https://avatars.githubusercontent.com/u/131838?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/user",
"html_url": "https://github.com/user",
"followers_url": "https://api.github.com/users/user/followers",
"following_url": "https://api.github.com/users/user/following{/other_user}",
"gists_url": "https://api.github.com/users/user/gists{/gist_id}",
"starred_url": "https://api.github.com/users/user/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/user/subscriptions",
"organizations_url": "https://api.github.com/users/user/orgs",
"repos_url": "https://api.github.com/users/user/repos",
"events_url": "https://api.github.com/users/user/events{/privacy}",
"received_events_url": "https://api.github.com/users/user/received_events",
"type": "User",
"site_admin": False
},
"created_at": "2015-02-02T12:49:05Z",
"updated_at": "2015-02-02T12:49:05Z",
"statuses_url": "https://api.github.com/repos/user/ansible/repo/176855/statuses",
"repository_url": "https://api.github.com/repos/user/repo"
},
"repository": {
"id": 30065224,
"name": "repo",
"full_name": "user/ansible",
"owner": {
"login": "user",
"id": 131838,
"avatar_url": "https://avatars.githubusercontent.com/u/131838?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/user",
"html_url": "https://github.com/user",
"followers_url": "https://api.github.com/users/user/followers",
"following_url": "https://api.github.com/users/user/following{/other_user}",
"gists_url": "https://api.github.com/users/user/gists{/gist_id}",
"starred_url": "https://api.github.com/users/user/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/user/subscriptions",
"organizations_url": "https://api.github.com/users/user/orgs",
"repos_url": "https://api.github.com/users/user/repos",
"events_url": "https://api.github.com/users/user/events{/privacy}",
"received_events_url": "https://api.github.com/users/user/received_events",
"type": "User",
"site_admin": False
},
"private": False,
"html_url": "https://github.com/user/repo",
"description": "",
"fork": False,
"url": "https://api.github.com/repos/user/repo",
"forks_url": "https://api.github.com/repos/user/ansible/forks",
"keys_url": "https://api.github.com/repos/user/ansible/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/user/ansible/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/user/ansible/teams",
"hooks_url": "https://api.github.com/repos/user/ansible/hooks",
"issue_events_url": "https://api.github.com/repos/user/ansible/issues/events{/number}",
"events_url": "https://api.github.com/repos/user/ansible/events",
"assignees_url": "https://api.github.com/repos/user/ansible/assignees{/user}",
"branches_url": "https://api.github.com/repos/user/ansible/branches{/branch}",
"tags_url": "https://api.github.com/repos/user/ansible/tags",
"blobs_url": "https://api.github.com/repos/user/ansible/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/user/ansible/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/user/ansible/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/user/ansible/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/user/ansible/statuses/{sha}",
"languages_url": "https://api.github.com/repos/user/ansible/languages",
"stargazers_url": "https://api.github.com/repos/user/ansible/stargazers",
"contributors_url": "https://api.github.com/repos/user/ansible/contributors",
"subscribers_url": "https://api.github.com/repos/user/ansible/subscribers",
"subscription_url": "https://api.github.com/repos/user/ansible/subscription",
"commits_url": "https://api.github.com/repos/user/ansible/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/user/ansible/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/user/ansible/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/user/ansible/issues/comments/{number}",
"contents_url": "https://api.github.com/repos/user/ansible/contents/{+path}",
"compare_url": "https://api.github.com/repos/user/ansible/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/user/ansible/merges",
"archive_url": "https://api.github.com/repos/user/ansible/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/user/ansible/downloads",
"issues_url": "https://api.github.com/repos/user/ansible/issues{/number}",
"pulls_url": "https://api.github.com/repos/user/ansible/pulls{/number}",
"milestones_url": "https://api.github.com/repos/user/ansible/milestones{/number}",
"notifications_url": "https://api.github.com/repos/user/ansible/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/user/ansible/labels{/name}",
"releases_url": "https://api.github.com/repos/user/ansible/releases{/id}",
"created_at": "2015-01-30T09:26:06Z",
"updated_at": "2015-01-30T09:26:06Z",
"pushed_at": "2015-02-02T12:43:47Z",
"git_url": "git://github.com/user/repo.git",
"ssh_url": "git@github.com:user/repo.git",
"clone_url": "https://github.com/user/repo.git",
"svn_url": "https://github.com/user/repo",
"homepage": None,
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": None,
"has_issues": True,
"has_downloads": True,
"has_wiki": True,
"has_pages": False,
"forks_count": 0,
"mirror_url": None,
"open_issues_count": 1,
"forks": 0,
"open_issues": 1,
"watchers": 0,
"default_branch": "master"
},
"sender": {
"login": "user",
"id": 131838,
"avatar_url": "https://avatars.githubusercontent.com/u/131838?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/user",
"html_url": "https://github.com/user",
"followers_url": "https://api.github.com/users/user/followers",
"following_url": "https://api.github.com/users/user/following{/other_user}",
"gists_url": "https://api.github.com/users/user/gists{/gist_id}",
"starred_url": "https://api.github.com/users/user/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/user/subscriptions",
"organizations_url": "https://api.github.com/users/user/orgs",
"repos_url": "https://api.github.com/users/user/repos",
"events_url": "https://api.github.com/users/user/events{/privacy}",
"received_events_url": "https://api.github.com/users/user/received_events",
"type": "User",
"site_admin": False
}
}

deployment = {
"deployment": {
"url": "https://api.github.com/repos/user/repo/repo/176855",
Expand Down
94 changes: 92 additions & 2 deletions test/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
import json
from base import Test, PseudoRequest
from app import app, process_deployment, create_deployment, update_deployment, \
communicate_deployment, authorize
communicate_deployment, authorize, run_ansible_playbook
from mock import patch, MagicMock
from nose.tools import assert_raises
from github import pull_request_opened, pull_request_closed, \
pull_request_closed_merged, deployment, deployment_status
pull_request_closed_merged, deployment, deployment_status, \
deployment_ansible
from subprocess import CalledProcessError


Expand Down Expand Up @@ -195,6 +196,61 @@ def test_process_deployment_oserror(self, popen, update_deployment):
assert update_deployment.called_with(deployment, status='error',
message=str(e))

@patch('app.run_ansible_playbook')
@patch('app.update_deployment')
def test_process_deployment_ansible(self, update_deployment,
run_ansible_playbook):
"""Test process_deployment ansible method."""
repo = {'user/ansible': {
'ansible_hosts': 'ansible_hosts',
'ansible_playbook': 'playbook.yml'}}

with patch('config.REPOS', repo):
res = process_deployment(deployment_ansible)
assert res, res
assert run_ansible_playbook.called_with(repo['user/ansible']['ansible_hosts'],
repo['user/ansible']['ansible_playbook'])
assert update_deployment.called_with(deployment_ansible,
status='success')

@patch('app.run_ansible_playbook')
@patch('app.update_deployment')
def test_process_deployment_ansible_key_error(self, update_deployment,
run_ansible_playbook):
"""Test process_deployment ansible key_error method."""
repo = {'user/ansible': {
'nsible_hosts': 'ansible_hosts',
'nsible_playbook': 'playbook.yml'}}

with patch('config.REPOS', repo):
run_ansible_playbook.side_effect = KeyError
res = process_deployment(deployment_ansible)
message = "ansible playbook or host file is missing in config file."
assert update_deployment.called_with(deployment_ansible,
status='error',
message=message)
assert res is False

@patch('app.run_ansible_playbook')
@patch('app.update_deployment')
def test_process_deployment_ansible_error(self, update_deployment,
run_ansible_playbook):
"""Test process_deployment ansible error method."""
repo = {'user/ansible': {
'ansible_hosts': 'wrong',
'ansible_playbook': 'playook.yml'}}

with patch('config.REPOS', repo):
from ansible.errors import AnsibleError
run_ansible_playbook.side_effect = AnsibleError('error')
res = process_deployment(deployment_ansible)
msg = str(AnsibleError('error'))
assert update_deployment.called_with(deployment_ansible,
status='error',
message=msg)
assert res is False, res


@patch('app.requests')
def test_create_deployment(self, requests):
"""Test create_deployment works."""
Expand Down Expand Up @@ -316,3 +372,37 @@ def test_authorize_case_6(self):
res = authorize(request, config)
assert res is False, res

@patch('app.ansible', autospec=True)
@patch('app.callbacks', autospec=True)
def test_run_ansible_playbook(self, callbacks, ansible):
"""Test run ansible playbook works."""
ansible_hosts = 'ansible_hosts'
playbook = 'playbook.yml'

stats = MagicMock()
callbacks.AggregateStats.return_value = stats

playbook_cb = MagicMock()
callbacks.PlaybookCallbacks.return_value = playbook_cb

inventory = MagicMock()
ansible.inventory.Inventory.return_value = inventory

runner_cb = MagicMock()
callbacks.PlaybookRunnerCallbacks.return_value = runner_cb

pb = MagicMock()
ansible.playbook.PlayBook.return_value = pb

run_ansible_playbook(ansible_hosts, playbook)

callbacks.AggregateStats.assert_called_with()
callbacks.PlaybookCallbacks.assert_called_with(verbose=0)
ansible.inventory.Inventory.assert_called_with(ansible_hosts)
callbacks.PlaybookRunnerCallbacks.assert_called_with(stats, verbose=0)
ansible.playbook.PlayBook.assert_called_with(playbook=playbook,
callbacks=playbook_cb,
runner_callbacks=runner_cb,
stats=stats,
inventory=inventory)
pb.run.assert_called_with()