Skip to content
This repository has been archived by the owner on Jun 29, 2019. It is now read-only.

Commit

Permalink
TestSuite for /deployment/github route (+ required refactoring)
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 committed Jan 1, 2017
1 parent 0188e2b commit fdc59c5
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 54 deletions.
20 changes: 12 additions & 8 deletions api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from flask_oauthlib.contrib.oauth2 import bind_cache_grant
from flask_oauthlib.provider import OAuth2Provider

from api.deployment.deployment_manager import DeploymentManager
from api.error import ApiException
from api.jwt_user import JwtUser
from api.user import User
Expand Down Expand Up @@ -113,9 +114,15 @@ def api_init():
"""

faf.db.init_db(app.config)
app.github = api.deployment.github.make_session(app.config['GITHUB_USER'],
app.config['GITHUB_TOKEN'])
app.slack = slack.make_session(app.config['SLACK_HOOK_URL'])
github = api.deployment.github.make_session(app.config['GITHUB_USER'],
app.config['GITHUB_TOKEN'])
slack = api.deployment.slack.make_session(app.config['SLACK_HOOK_URL'])

app.deployment_manager = DeploymentManager(app.config['ENVIRONMENT'], app.config['GITHUB_SECRET'],
app.config['GIT_OWNER'], github, slack)
for deploy_configuration in app.config['DEPLOYMENTS']:
app.deployment_manager.add(deploy_configuration)
app.github = github

app.secret_key = app.config['FLASK_LOGIN_SECRET_KEY']
flask_jwt.init_app(app)
Expand Down Expand Up @@ -154,16 +161,14 @@ def get_current_user():
# ======== Import (initialize) oauth2 handlers =====
import api.oauth_handlers
# ======== Import (initialize) routes =========
import api.deployment.routes
import api.auth
import api.avatars
import api.bugreports
import api.mods
import api.maps
import api.deployment.github
import api.oauth_client
import api.oauth_token
import api.slack
import api.deployment.slack
import api.achievements
import api.events
import api.query_commons
Expand All @@ -173,5 +178,4 @@ def get_current_user():
import api.players
import api.users_route
import api.featured_mods
import api.deployment.deployment_manager
import api.deployment.deployment_configurations
import api.deployment.routes
27 changes: 10 additions & 17 deletions api/deployment/deployment_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@
from faf.tools.fa.build_mod import build_mod
from faf.tools.fa.mods import parse_mod_info
from faf.tools.fa.update_version import update_exe_version
from flask import app
from pymysql.cursors import Cursor

from api import app
from api.deployment.deployment_manager import DeploymentManager
from api.deployment.git import checkout_repo, GitRepository
from api.error import ApiException, Error, ErrorCode

Expand All @@ -29,7 +26,8 @@ def __init__(self, repo: GitRepository, branch: str, autodeploy: bool):

@abstractmethod
def deploy(self, deploy_id: str, commit_signature: str,
callback_on_finished: Callable[[DeploymentManager, str, str, 'DeploymentConfiguration'], None]) -> None:
callback_on_finished: Callable[
['DeploymentManager', str, str, 'DeploymentConfiguration'], None]) -> None:
pass

def matches(self, repo_url: str, repo_name: str, branch: str, force_deploy: bool) -> bool:
Expand All @@ -53,7 +51,7 @@ def deploy(self, deploy_id: str, commit_signature: str,
restart_file = Path(self.repo.path, 'tmp/restart.txt')
restart_file.touch()

app.github.create_deployment_status(
self._github.create_deployment_status(
owner='FAForever',
repo=self.repo.name,
id=deploy_id,
Expand All @@ -70,9 +68,12 @@ class GameDeploymentConfiguration(DeploymentConfiguration):
GameDeploymentConfiguration is able to build a new game version and deploy it
"""

def __init__(self, repo: GitRepository, branch: str, autodeploy: bool, featured_mod: str,
file_extension: str, allow_override: bool):
def __init__(self, repo: GitRepository, branch: str, autodeploy: bool, git_owner: str, deploy_path: str,
base_game_exe: str, featured_mod: str, file_extension: str, allow_override: bool):
super().__init__(repo, branch, autodeploy)
self._git_owner = git_owner
self._deploy_path = deploy_path
self._base_game_exe = base_game_exe
self._featured_mod = featured_mod
self._file_extension = file_extension
self._allow_override = allow_override
Expand All @@ -81,14 +82,6 @@ def deploy(self, deploy_id: str, commit_signature: str,
callback_on_finished: Callable[[str, str, 'DeploymentConfiguration'], None]) -> None:
logger.info('Game-deployment started (repo=%s, branch=%s)', self.repo.name, self._branch)

app.github.create_deployment_status(
owner=app.config['GIT_OWNER'],
repo=self.repo.name,
id=deploy_id,
state='pending',
description='Game-deployment started (repo=%s, branch=%s, featured_mod=%s)' % (
self.repo.name, self._branch, self._featured_mod))

# zipping the game files takes much io resources, but few cpu resources
# putting it into a separate python thread should be enough to unblock api
work_thread = threading.Thread(target=self._perform_deploy, name='deploy_worker',
Expand Down Expand Up @@ -123,12 +116,12 @@ def _perform_deploy(self, deploy_id: str, commit_signature: str,
logger.debug('Build result: {}'.format(files))

# Create the storage path for the version files. This is where the zips will be moved to from temp
deploy_path = Path(app.config['GAME_DEPLOY_PATH'], 'updates_%s_files' % self._featured_mod)
deploy_path = Path(self._deploy_path, 'updates_%s_files' % self._featured_mod)
deploy_path.mkdir(parents=True, exist_ok=True)

# Create a new ForgedAlliance.exe compatible with the new version
logger.debug('Create version of ForgedAlliance.exe')
base_game_exe = Path(app.config['BASE_GAME_EXE'])
base_game_exe = Path(self._base_game_exe)
update_exe_version(base_game_exe, deploy_path, version)

logger.debug('Deploying %s to %s', self._featured_mod, deploy_path)
Expand Down
39 changes: 23 additions & 16 deletions api/deployment/deployment_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
from flask import Request

from api.deployment.deployment_configurations import *
from api.deployment.github import validate_github_request
from api.deployment.github import validate_github_request, Github
from api.deployment.slack import Slack

logger = logging.getLogger(__name__)


class DeploymentManager(object):
def __init__(self):
def __init__(self, environment: str, github_secret: bytes, git_owner: str, github: Github, slack: Slack):
self._environment = environment
self._github_secret = github_secret
self._git_owner = git_owner
self._github = github
self._slack = slack
self._configurations = [] # list[DeploymentConfiguration]

def add(self, deployment_conf: DeploymentConfiguration) -> None:
Expand All @@ -21,8 +27,8 @@ def handle_request(self, request: Request):

body = request.get_json()

if len(app.config['GITHUB_SECRET']) > 0 \
and not validate_github_request(app.config['GITHUB_SECRET'],
if len(self._github_secret) > 0 \
and not validate_github_request(self._github_secret,
request.data, request.headers[
'X-Hub-Signature'].split("sha1=")[
1]):
Expand All @@ -47,7 +53,7 @@ def handle_request(self, request: Request):
return dict(status='Github request ignored (commit already known)'), 200

manual_deploy = re.search('Deploy: ([\w\W]+)', head_commit['message'])
environment = manual_deploy.group(1) if manual_deploy else app.config['ENVIRONMENT']
environment = manual_deploy.group(1) if manual_deploy else self._environment

# find a list of valid configurations
configurations = list(filter(lambda conf: conf.matches(repo_url, repo_name, branch, manual_deploy),
Expand All @@ -58,11 +64,11 @@ def handle_request(self, request: Request):
branch)
return dict(status='Github request ignored (no matching configuration)'), 200
elif len(configurations) == 1:
response = app.github.create_deployment(owner=app.config['GIT_OWNER'],
repo=repo_name,
ref=body['ref'],
environment=environment,
description=head_commit['message'])
response = self._github.create_deployment(owner=self._git_owner,
repo=repo_name,
ref=body['ref'],
environment=environment,
description=head_commit['message'])
if response.status_code == 201:
logger.info('Github-deployment invoked (repo=%s, branch=%s)', repo_name, branch)
return dict(status='deployment invoked'), 201
Expand All @@ -89,11 +95,12 @@ def handle_request(self, request: Request):
repo_url)
return dict(status='Github request ignored (no matching configuration'), 200
elif len(configurations) == 1:
if deploy_info['environment'] == app.config['ENVIRONMENT']:
if deploy_info['environment'] == self._environment:
configurations[0].deploy(deploy_info['id'], deploy_info['sha'], self.on_deployment_finished)
return dict(status='deployment started'), 201
else:
logger.debug('Skip deployment due to wrong environment')
logger.debug('Github request skipped due to wrong environment')
return dict(status='Github request skipped due to wrong environment'), 200
else:
logger.error("Invalid deployment configuration for (repo=%s, branch=%s)", repo_name, branch)
raise ApiException([Error(ErrorCode.DEPLOYMENT_ERROR, "Invalid deployment configuration")])
Expand All @@ -105,12 +112,12 @@ def handle_request(self, request: Request):
def on_deployment_finished(self, deploy_id: str, message: str, configuration: DeploymentConfiguration) -> None:
logger.debug("performing post-deployment activities")

deploy_message = "[%s] %s" % (app.config['ENVIRONMENT'], message)
deploy_message = "[%s] %s" % (self._environment, message)

app.slack.send_message(username='deploybot', text=deploy_message)
self._slack.send_message(username='deploybot', text=deploy_message)

app.github.create_deployment_status(
owner=app.config['GIT_OWNER'],
self._github.create_deployment_status(
owner=self._git_owner,
repo=configuration.repo.name,
id=deploy_id,
state='success',
Expand Down
3 changes: 2 additions & 1 deletion api/deployment/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from api import app, request


@app.route('/deployment/<repo>/<int:id>', methods=['GET'])
def deployment(repo, id):
return app.github.deployment(owner=app.config['GIT_OWNER'], repo=repo, id=id).json()
Expand All @@ -22,4 +23,4 @@ def github_hook():
Generic github hook suitable for receiving github status events.
:return:
"""
return app.config['DEPLOYMENTS'].handle_request(request)
return app.deployment_manager.handle_request(request)
2 changes: 2 additions & 0 deletions api/slack.py → api/deployment/slack.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json

import requests


def make_session(hook_url):
s = requests.Session()
return Slack(hook_url, s)
Expand Down
22 changes: 10 additions & 12 deletions config.example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pathlib import Path

from api.deployment.deployment_configurations import WebDeploymentConfiguration, GameDeploymentConfiguration
from api.deployment.deployment_manager import DeploymentManager
from api.deployment.git import GitRepository

DATABASE = dict(
Expand Down Expand Up @@ -39,17 +38,16 @@
Path('/content/faf/patchnotes'))
GAME_REPO = GitRepository('https://github.com/%s/fa.git' % GIT_OWNER, 'fa', Path('/content/faf/repos/fa'))

DEPLOYMENTS = DeploymentManager() # type: DeploymentManager
DEPLOYMENTS.add(WebDeploymentConfiguration(repo=API_REPO, branch='master', autodeploy=False))
DEPLOYMENTS.add(WebDeploymentConfiguration(repo=PATCHNOTES_REPO, branch='master', autodeploy=False))
DEPLOYMENTS.add(GameDeploymentConfiguration(repo=GAME_REPO, branch='master', autodeploy=False, featured_mod='faf',
allow_override=False, file_extension='.nx2'))
DEPLOYMENTS.add(
GameDeploymentConfiguration(repo=GAME_REPO, branch='deploy/fafbeta', autodeploy=True, featured_mod='fafbeta',
allow_override=True, file_extension='.nx4'))
DEPLOYMENTS.add(
GameDeploymentConfiguration(repo=GAME_REPO, branch='deploy/fafdevelop', autodeploy=True, featured_mod='fafdevelop',
allow_override=True, file_extension='.nx5'))
DEPLOYMENTS = [
WebDeploymentConfiguration(API_REPO, 'master', False),
WebDeploymentConfiguration(PATCHNOTES_REPO, 'master', False),
GameDeploymentConfiguration(GAME_REPO, 'master', False, GIT_OWNER, GAME_DEPLOY_PATH, BASE_GAME_EXE, 'faf', '.nx2',
False),
GameDeploymentConfiguration(GAME_REPO, 'deploy/fafbeta', True, GIT_OWNER, GAME_DEPLOY_PATH, BASE_GAME_EXE,
'fafbeta', '.nx4', True),
GameDeploymentConfiguration(GAME_REPO, 'deploy/fafdevelop', True, GIT_OWNER, GAME_DEPLOY_PATH, BASE_GAME_EXE,
'fafdevelop', '.nx5', True)
]


FLASK_LOGIN_SECRET_KEY = os.getenv("FLASK_LOGIN_SECRET_KEY", '1234')
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def app():
importlib.reload(api.users_route)
importlib.reload(api.featured_mods)
importlib.reload(api.helpers)
importlib.reload(api.deployment.routes)

api.app.config.from_object('config')
api.app.debug = True
Expand Down
Loading

0 comments on commit fdc59c5

Please sign in to comment.