mirror of
https://github.com/EeeeKa/webhook-catcher.git
synced 2025-08-03 16:07:22 +05:00
Release v1.0
This commit is contained in:
commit
a8e8438301
45
README.md
Normal file
45
README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# webhook-catcher
|
||||||
|
#### Simple HTTP-server for processing webhooks from Github or Gogs portals.
|
||||||
|
|
||||||
|
You may define one repo by arguments:
|
||||||
|
|
||||||
|
`--dir, --secret, --branch, --remote, --action, --full_name, --clone_proto`
|
||||||
|
|
||||||
|
define "wild" dir, using:
|
||||||
|
|
||||||
|
`--wild_dir, --wild_secret, --wild_proto`
|
||||||
|
|
||||||
|
specify configuration file in .json format:
|
||||||
|
|
||||||
|
`-c|--config`
|
||||||
|
|
||||||
|
or mix them all.
|
||||||
|
|
||||||
|
"wild" directory will be used to pull all repos which not defined in config or arguments. No actions allowed for wild repos.
|
||||||
|
|
||||||
|
If repo directory does not exists, it will be cloned at first hook.
|
||||||
|
|
||||||
|
Repos identifyed by "full_name", so if more then one repo defined, you must define "full_name" to all of them.
|
||||||
|
|
||||||
|
You may send SIGHUP to restart server with same arguments and re-read config if it supplied.
|
||||||
|
|
||||||
|
Also, server will be restarted after successfull pulling repo which defined as "self_update_repo" in config file.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
webhook-catcher [-h] [-c CONFIG_PATH] [-a LISTEN_ADDRESS]
|
||||||
|
[-p LISTEN_PORT] [-q] [--debug]
|
||||||
|
[--wild_dir PATH_TO_DIR] [--wild_secret SECRET]
|
||||||
|
[--wild_proto <HTTP|SSH>] [--dir PATH_TO_REPO]
|
||||||
|
[--secret SECRET] [--branch BRANCH_NAME]
|
||||||
|
[--remote REMOTE_NAME] [--action ACTION]
|
||||||
|
[--full_name FULL_NAME] [--clone_proto <HTTP|SSH>]
|
||||||
|
[--version]
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Good luck!
|
36
config.json.sample
Normal file
36
config.json.sample
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"address": "0.0.0.0",
|
||||||
|
"port": 8888,
|
||||||
|
"quiet": false,
|
||||||
|
"debug": false,
|
||||||
|
"wild_dir": "",
|
||||||
|
"wild_proto": "",
|
||||||
|
"wild_secret": "",
|
||||||
|
"self_update_repo": "webhook-catcher"
|
||||||
|
},
|
||||||
|
|
||||||
|
"repos": [
|
||||||
|
{
|
||||||
|
"name": "MyRepo",
|
||||||
|
"branch": "release",
|
||||||
|
"remote": "origin",
|
||||||
|
"dir": "/opt/myprog",
|
||||||
|
"action": "/opt/myprog-install.sh",
|
||||||
|
"clone_proto": "http",
|
||||||
|
"full_name": "Fat_and_Showels/myprog",
|
||||||
|
"secret": "SupP@sEckreTPhR@$e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MinimalRepo",
|
||||||
|
"dir": "/opt/minrepo",
|
||||||
|
"full_name": "USERNAME/min-rep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "webhook-catcher",
|
||||||
|
"dir": "./",
|
||||||
|
"full_name": "EeeeKa/webhook-catcher",
|
||||||
|
"secret": "UpdateSecret&#!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
646
webhook-catcher
Executable file
646
webhook-catcher
Executable file
@ -0,0 +1,646 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
#
|
||||||
|
# Webhook-catcher v1.0
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from http.server import BaseHTTPRequestHandler
|
||||||
|
|
||||||
|
# Python v3.7+
|
||||||
|
if sys.version_info.major == 3 and sys.version_info[1] > 7:
|
||||||
|
from http.server import ThreadingHTTPServer as _HTTPServer
|
||||||
|
# Python v3.5+
|
||||||
|
elif sys.version_info.major == 3 and sys.version_info[1] > 5:
|
||||||
|
from http.server import HTTPServer as _HTTPServer
|
||||||
|
# Fallback
|
||||||
|
else:
|
||||||
|
from http.server import HTTPServer as _HTTPServer
|
||||||
|
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from signal import signal, SIGHUP, SIGTERM
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
PROG_NAME = "Webhook-catcher"
|
||||||
|
PROG_VERSION = "v1.0"
|
||||||
|
PROG_DESCRIPTION = '\
|
||||||
|
Simple HTTP-server for processing webhooks from Github or Gogs portals.\n\
|
||||||
|
\n\
|
||||||
|
You may define one repo by arguments:\n\
|
||||||
|
--dir, --secret, --branch, --remote, --action, --full_name, --clone_proto\n\
|
||||||
|
define "wild" dir, using:\n\
|
||||||
|
--wild_dir, --wild_secret, --wild_proto\n\
|
||||||
|
specify configuration file in .json format:\n\
|
||||||
|
-c|--config\n\
|
||||||
|
or mix them all.\n\
|
||||||
|
\n\
|
||||||
|
"wild" directory will be used to pull all repos which not defined in config\n\
|
||||||
|
or arguments. No actions allowed for wild repos.\n\
|
||||||
|
\n\
|
||||||
|
If repo directory does not exists, it will be cloned at first hook.\n\
|
||||||
|
\n\
|
||||||
|
Repos identifyed by "full_name", so if more then one repo defined, you must\n\
|
||||||
|
define "full_name" to all of them.\n\
|
||||||
|
\n\
|
||||||
|
You may send SIGHUP to restart server with same arguments and re-read config\n\
|
||||||
|
if it supplied.\n\
|
||||||
|
\n\
|
||||||
|
Also, server will be restarted after successfull pulling repo which defined\n\
|
||||||
|
as "self_update_repo" in config file.'
|
||||||
|
|
||||||
|
PROG_EPILOG='Good luck!'
|
||||||
|
|
||||||
|
# DEFAULTS
|
||||||
|
DEFAULT_ADDRESS = "0.0.0.0"
|
||||||
|
DEFAULT_PORT = 8888
|
||||||
|
DEFAULT_REMOTE = "origin"
|
||||||
|
DEFAULT_BRANCH = "master"
|
||||||
|
DEFAULT_PROTO = "http"
|
||||||
|
|
||||||
|
class WebHookServer(_HTTPServer):
|
||||||
|
|
||||||
|
def __init__(self, server_address, request_handler_class, repos, self_update_repo, wild_dir, wild_proto, wild_secret):
|
||||||
|
repos_count = 0
|
||||||
|
if repos != []:
|
||||||
|
for repo in repos:
|
||||||
|
repos_count += 1
|
||||||
|
|
||||||
|
self.repos = repos
|
||||||
|
self.self_update_repo = self_update_repo
|
||||||
|
self.repos_count = repos_count
|
||||||
|
self.wild_dir = wild_dir
|
||||||
|
self.wild_proto = wild_proto
|
||||||
|
self.wild_secret = wild_secret
|
||||||
|
|
||||||
|
super().__init__(server_address, request_handler_class)
|
||||||
|
|
||||||
|
|
||||||
|
class WebHookHandler(BaseHTTPRequestHandler):
|
||||||
|
def __init__(self, request, client_address, server):
|
||||||
|
self.json_response = {
|
||||||
|
"webhook": {
|
||||||
|
"status": "",
|
||||||
|
"message": "",
|
||||||
|
"signature_status": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"pull": {
|
||||||
|
"return_code": -1,
|
||||||
|
"status": "",
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"action": {
|
||||||
|
"return_code": -1,
|
||||||
|
"status": "ND",
|
||||||
|
"stdout": "NA",
|
||||||
|
"stderr": "NA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super().__init__(request, client_address, server)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(401)
|
||||||
|
self.send_header('Content-type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write('Webhook Catcher supports POST-queries only. Bye!'.encode('UTF-8'))
|
||||||
|
|
||||||
|
def _send_json_response(self, code):
|
||||||
|
self.send_response(code)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(self.json_response, indent=2, sort_keys=True).encode('UTF-8', 'replace'))
|
||||||
|
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
# GET SIGNATURE
|
||||||
|
signature = None
|
||||||
|
signature_type = ''
|
||||||
|
|
||||||
|
signature = self.headers['X-Hub-Signature']
|
||||||
|
if signature is not None:
|
||||||
|
signature_type = 'Hub'
|
||||||
|
else:
|
||||||
|
signature = self.headers['X-Gogs-Signature']
|
||||||
|
if signature is not None:
|
||||||
|
signature_type = 'Gogs'
|
||||||
|
|
||||||
|
if signature is None:
|
||||||
|
self.json_response.update({
|
||||||
|
"webhook": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"message": "No signature found.",
|
||||||
|
"signature_status": "NOT FOUND"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self._send_json_response(400)
|
||||||
|
return
|
||||||
|
|
||||||
|
# GET DATA
|
||||||
|
data_raw = self.rfile.read1(10240)
|
||||||
|
|
||||||
|
## Process data
|
||||||
|
data = json.loads(data_raw.decode('UTF-8'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
requested_full_name = data['repository']['full_name']
|
||||||
|
http_clone_url = data['repository']['clone_url']
|
||||||
|
ssh_clone_url = data['repository']['ssh_url']
|
||||||
|
except:
|
||||||
|
self.json_response.update({
|
||||||
|
"webhook": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"message": "No valid data found.",
|
||||||
|
"signature_status": "NOT_VALIDATED"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self._send_json_response(401)
|
||||||
|
return
|
||||||
|
|
||||||
|
current_repo = {}
|
||||||
|
|
||||||
|
# Try to find suitable repo definition
|
||||||
|
if self.server.repos_count > 1:
|
||||||
|
for repo in self.server.repos:
|
||||||
|
if repo['full_name'] == requested_full_name:
|
||||||
|
current_repo = repo
|
||||||
|
elif self.server.repos != []:
|
||||||
|
current_repo = self.server.repos[0]
|
||||||
|
|
||||||
|
# Use wild dir if no defined repo found
|
||||||
|
if current_repo == {} and self.server.wild_dir != '':
|
||||||
|
current_repo = {
|
||||||
|
'name': data['repository']['name'],
|
||||||
|
'branch': data['repository']['default_branch'],
|
||||||
|
'remote': DEFAULT_REMOTE,
|
||||||
|
'dir': os.path.join(self.server.wild_dir, data['repository']['name']),
|
||||||
|
'action': '',
|
||||||
|
'clone_proto': DEFAULT_PROTO,
|
||||||
|
'full_name': data['repository']['full_name'],
|
||||||
|
'secret': self.server.wild_secret
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_repo != {}:
|
||||||
|
# SET URL
|
||||||
|
if current_repo['clone_proto'].upper() == 'HTTP':
|
||||||
|
current_repo['url'] = http_clone_url
|
||||||
|
elif current_repo['clone_proto'].upper() == 'SSH':
|
||||||
|
current_repo['url'] = ssh_clone_url
|
||||||
|
else:
|
||||||
|
current_repo['url'] = ''
|
||||||
|
|
||||||
|
pdm('Current repo:')
|
||||||
|
pdm(current_repo)
|
||||||
|
|
||||||
|
# CHECK SIGNATURE
|
||||||
|
if current_repo['secret'] != '':
|
||||||
|
if signature_type == 'Hub':
|
||||||
|
data_hash = hmac.new(current_repo['secret'].encode(), data_raw, hashlib.sha1).hexdigest()
|
||||||
|
elif signature_type == 'Gogs':
|
||||||
|
data_hash = hmac.new(current_repo['secret'].encode(), data_raw, hashlib.sha256).hexdigest()
|
||||||
|
|
||||||
|
if signature != data_hash:
|
||||||
|
self.json_response.update({
|
||||||
|
"webhook": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"message": "Invalid Signature. Check secret phrase.",
|
||||||
|
"signature_status": "INVALID"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self._send_json_response(401)
|
||||||
|
return
|
||||||
|
|
||||||
|
# CHECK SUCCESSFULL
|
||||||
|
self.json_response.update({
|
||||||
|
"webhook": {
|
||||||
|
"status": "OK",
|
||||||
|
"message": "WebHook captured successfully.",
|
||||||
|
"signature_status": "VALID"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
# CHECK SKIPPED
|
||||||
|
self.json_response.update({
|
||||||
|
"webhook": {
|
||||||
|
"status": "OK",
|
||||||
|
"message": "WebHook captured successfully.",
|
||||||
|
"signature_status": "CHECK_SKIPPED"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# DO WORK
|
||||||
|
|
||||||
|
code = 0
|
||||||
|
pull_status = ''
|
||||||
|
action_status = ''
|
||||||
|
|
||||||
|
## Pull
|
||||||
|
try:
|
||||||
|
pull_result = WebHookWorker.git_pull(current_repo)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
code = 500
|
||||||
|
self.json_response.update({
|
||||||
|
"pull": {
|
||||||
|
"return_code": -1,
|
||||||
|
"status": str(e),
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
pdm(pull_result)
|
||||||
|
|
||||||
|
if pull_result.returncode == 0:
|
||||||
|
code = 200
|
||||||
|
pull_status = "DONE"
|
||||||
|
else:
|
||||||
|
code = 500
|
||||||
|
pull_status = "ERROR"
|
||||||
|
|
||||||
|
self.json_response.update({
|
||||||
|
"pull": {
|
||||||
|
"return_code": pull_result.returncode,
|
||||||
|
"status": pull_status,
|
||||||
|
"stdout": pull_result.stdout,
|
||||||
|
"stderr": pull_result.stderr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
## Action
|
||||||
|
if current_repo['action'] != '' and pull_result.returncode == 0:
|
||||||
|
try:
|
||||||
|
action_result = WebHookWorker.do_action(current_repo)
|
||||||
|
except Exception as e:
|
||||||
|
code = 500
|
||||||
|
self.json_response.update({
|
||||||
|
"action": {
|
||||||
|
"return_code": -1,
|
||||||
|
"status": str(e),
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
pdm(action_result)
|
||||||
|
|
||||||
|
if action_result.returncode == 0:
|
||||||
|
code = 200
|
||||||
|
action_status = "DONE"
|
||||||
|
else:
|
||||||
|
code = 500
|
||||||
|
action_status = "ERROR"
|
||||||
|
|
||||||
|
self.json_response.update({
|
||||||
|
"action": {
|
||||||
|
"return_code": action_result.returncode,
|
||||||
|
"status": action_status,
|
||||||
|
"stdout": action_result.stdout,
|
||||||
|
"stderr": action_result.stderr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
### NOTHING TO DO PREPARE EMPTY RESPONSE
|
||||||
|
code = 500
|
||||||
|
pull_status = "NO SUITABLE REPO DEFINED. NOTHING TO DO."
|
||||||
|
self.json_response.update({
|
||||||
|
"webhook": {
|
||||||
|
"status": "OK",
|
||||||
|
"message": "WebHook captured successfully.",
|
||||||
|
"signature_status": "CHECK_SKIPPED"
|
||||||
|
},
|
||||||
|
"pull": {
|
||||||
|
"return_code": "NA",
|
||||||
|
"status": pull_status,
|
||||||
|
"stdout": "NA",
|
||||||
|
"stderr": "NA"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# SEND RESPONSE
|
||||||
|
self._send_json_response(code)
|
||||||
|
|
||||||
|
# SELF-UPDATE
|
||||||
|
if code == 200 and current_repo['name'] == self.server.self_update_repo:
|
||||||
|
pm('Self update complete.')
|
||||||
|
os.kill(os.getpid(), SIGHUP)
|
||||||
|
|
||||||
|
|
||||||
|
class WebHookWorker(object):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def git_pull(cls, repo):
|
||||||
|
if os.path.isdir(repo['dir']):
|
||||||
|
result = subprocess.run([
|
||||||
|
"git", "--git-dir", os.path.join(repo['dir'], '.git'), "pull",
|
||||||
|
repo['remote'], repo['branch'],
|
||||||
|
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
||||||
|
else:
|
||||||
|
result = subprocess.run([
|
||||||
|
"git", "clone", "--origin", repo['remote'], "--branch", repo['branch'], repo['url'], repo['dir']
|
||||||
|
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def do_action(cls, repo):
|
||||||
|
result = subprocess.run(repo['action'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def create_arg_parser():
|
||||||
|
arg_parser = argparse.ArgumentParser(description=PROG_DESCRIPTION, epilog=PROG_EPILOG, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
|
||||||
|
arg_parser.add_argument('-c', '--config', default={}, metavar='CONFIG_PATH', type=load_config, help='path to configuration file')
|
||||||
|
|
||||||
|
arg_parser.add_argument('-a', '--address', metavar='LISTEN_ADDRESS', help='listen address, default: ' + DEFAULT_ADDRESS)
|
||||||
|
arg_parser.add_argument('-p', '--port', metavar='LISTEN_PORT', help='port, default: ' + str(DEFAULT_PORT))
|
||||||
|
|
||||||
|
arg_parser.add_argument('-q', '--quiet', action='store_const', const=True, help='don''t show any messages')
|
||||||
|
arg_parser.add_argument('--debug', action='store_const', const=True, help='show debug messages')
|
||||||
|
|
||||||
|
arg_parser.add_argument('--wild_dir', metavar='PATH_TO_DIR', help='clone/pull all unhandled requests into this dir')
|
||||||
|
arg_parser.add_argument('--wild_secret', metavar='SECRET', help='secret phrase')
|
||||||
|
arg_parser.add_argument('--wild_proto', metavar='<HTTP|SSH>', choices=['http', 'ssh', 'HTTP', 'SSH'], help='which protocol will be used to clone wild repos, default: ' + DEFAULT_PROTO)
|
||||||
|
|
||||||
|
arg_parser.add_argument('--dir', metavar='PATH_TO_REPO', help='path to git repo')
|
||||||
|
arg_parser.add_argument('--secret', default='', metavar='SECRET', help='secret phrase')
|
||||||
|
arg_parser.add_argument('--branch', default=DEFAULT_BRANCH, metavar='BRANCH_NAME', help='git branch name, default: ' + DEFAULT_BRANCH)
|
||||||
|
arg_parser.add_argument('--remote', default=DEFAULT_REMOTE, metavar='REMOTE_NAME', help='git remote name, default: ' + DEFAULT_REMOTE)
|
||||||
|
arg_parser.add_argument('--action', default='', metavar='ACTION', help='execute additional actions after "git pull"')
|
||||||
|
arg_parser.add_argument('--full_name', default='', metavar='FULL_NAME', help='repo fullname')
|
||||||
|
arg_parser.add_argument('--clone_proto', default='http', metavar='<HTTP|SSH>', choices=['http', 'ssh', 'HTTP', 'SSH'], help='which protocol will be used to clone repo, default: ' + DEFAULT_PROTO)
|
||||||
|
|
||||||
|
arg_parser.add_argument('--version', action='version', version=PROG_NAME + ' ' + PROG_VERSION)
|
||||||
|
|
||||||
|
return(arg_parser)
|
||||||
|
|
||||||
|
class MessagePrinter:
|
||||||
|
def __init__(self, quiet, debug):
|
||||||
|
self.quiet = quiet
|
||||||
|
self.debug = debug
|
||||||
|
|
||||||
|
def pm(self, message):
|
||||||
|
if not self.quiet:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
def pdm(self, debug_message):
|
||||||
|
if self.debug:
|
||||||
|
pprint(debug_message)
|
||||||
|
|
||||||
|
def load_config(path):
|
||||||
|
if path != '':
|
||||||
|
try:
|
||||||
|
f = open(path, 'r')
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise argparse.ArgumentTypeError(str(e))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
config = json.loads(f.read())
|
||||||
|
except Exception as e:
|
||||||
|
raise argparse.ArgumentTypeError(str(e))
|
||||||
|
else:
|
||||||
|
return config
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def prepare_repos(repos, self_update_repo):
|
||||||
|
# FILL AND CHECK
|
||||||
|
count = 0
|
||||||
|
no_full_name_count = 0
|
||||||
|
self_update_repo_found = False
|
||||||
|
names = []
|
||||||
|
full_names = []
|
||||||
|
|
||||||
|
for repo in repos:
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# self_update_repo
|
||||||
|
if (self_update_repo != '') and (repo['name'] == self_update_repo):
|
||||||
|
self_update_repo_found = True
|
||||||
|
|
||||||
|
# name
|
||||||
|
if not 'name' in repo:
|
||||||
|
repo['name'] = 'Repo ' + count
|
||||||
|
|
||||||
|
# branch
|
||||||
|
if not 'branch' in repo:
|
||||||
|
repo['branch'] = DEFAULT_BRANCH
|
||||||
|
|
||||||
|
# remote
|
||||||
|
if not 'remote' in repo:
|
||||||
|
repo['remote'] = DEFAULT_REMOTE
|
||||||
|
|
||||||
|
# dir
|
||||||
|
if (not 'dir' in repo) or (repo['dir'] == ''):
|
||||||
|
pm('ERROR: Destination directory not defined in repo "{}".'.format(repo['name']))
|
||||||
|
exit(5)
|
||||||
|
elif (not os.path.isdir(repo['dir']) and not os.path.isdir(os.path.dirname(repo['dir']))):
|
||||||
|
pm('ERROR: Destination directory "{}" or parent directory "{}" defined in repo "{}" not found.'.format(repo['dir'], os.path.dirname(repo['dir']), repo['name']))
|
||||||
|
exit(6)
|
||||||
|
elif os.path.isfile(repo['dir']):
|
||||||
|
pm('ERROR: Destination path "{}" is not a directory in repo "{}".'.format(repo['dir'], repo['name']))
|
||||||
|
exit(7)
|
||||||
|
|
||||||
|
# action
|
||||||
|
if (not 'action' in repo) or (repo['action'] == ''):
|
||||||
|
repo['action'] = ''
|
||||||
|
else:
|
||||||
|
action_file = repo['action'].split(' -')[0]
|
||||||
|
if not (os.path.isfile(action_file) and os.access(action_file, os.X_OK)):
|
||||||
|
pm('ERROR: Defined action file "{}" in repo "{}" not found or not executable.'.format(action_file, repo['name']))
|
||||||
|
exit(4)
|
||||||
|
|
||||||
|
# ACTION FOR SELF UPDATE IS OPTIONAL
|
||||||
|
#if (self_update_repo != '') and (repo['name'] == self_update_repo) and (repo['action'] == ''):
|
||||||
|
# pm('ERROR: No action defined in self update repo "{}".'.format(repo['name']))
|
||||||
|
# exit(11)
|
||||||
|
|
||||||
|
# clone_proto
|
||||||
|
if not 'clone_proto' in repo:
|
||||||
|
repo['clone_proto'] = DEFAULT_PROTO
|
||||||
|
elif not repo['clone_proto'].upper() in ['HTTP', 'SSH']:
|
||||||
|
pm('ERROR: Wrong protocol "{}" in repo "{}". "HTTP" or "SSH" expected.'.format(repo['clone_proto'], repo['name']))
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
# full_name
|
||||||
|
if (not 'full_name' in repo) or (repo['full_name'] == ''):
|
||||||
|
repo['full_name'] = ''
|
||||||
|
no_full_name_count += 1
|
||||||
|
if (count > 1) and (no_full_name_count > 0):
|
||||||
|
pm('ERROR: Count of repos without "full_name" more then one. "full_name" must be set for all repos.')
|
||||||
|
exit(3)
|
||||||
|
|
||||||
|
# secret
|
||||||
|
if not 'secret' in repo:
|
||||||
|
repo['secret'] = ''
|
||||||
|
|
||||||
|
# unique names
|
||||||
|
if repo['name'] in names:
|
||||||
|
pm('ERROR: Not unique repo name: "{}".'.format(repo['name']))
|
||||||
|
exit(9)
|
||||||
|
else:
|
||||||
|
names.append(repo['name'])
|
||||||
|
|
||||||
|
if repo['full_name'] in full_names:
|
||||||
|
pm('ERROR: Not unique full_name "{}" in repo "{}".'.format(repo['full_name'], repo['name']))
|
||||||
|
exit(10)
|
||||||
|
else:
|
||||||
|
full_names.append(repo['full_name'])
|
||||||
|
|
||||||
|
|
||||||
|
if self_update_repo != '' and not self_update_repo_found:
|
||||||
|
pm('ERROR: Repo for self update "{}" defined but not found!'.format(self_update_repo))
|
||||||
|
exit(8)
|
||||||
|
|
||||||
|
def restart_catcher(signalNumber, frame):
|
||||||
|
pm('Restart...')
|
||||||
|
os.execl(sys.executable, sys.executable, *sys.argv)
|
||||||
|
|
||||||
|
def stop_catcher(signalNumber, frame):
|
||||||
|
pm('Terminating...')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
# MAIN
|
||||||
|
def main(port=DEFAULT_PORT, key=''):
|
||||||
|
# INIT
|
||||||
|
global pm
|
||||||
|
global pdm
|
||||||
|
|
||||||
|
arg_parser = create_arg_parser()
|
||||||
|
args = arg_parser.parse_args()
|
||||||
|
|
||||||
|
signal(SIGHUP, restart_catcher)
|
||||||
|
signal(SIGTERM, stop_catcher)
|
||||||
|
|
||||||
|
# SERVER SETTINGS
|
||||||
|
address = ''
|
||||||
|
port = ''
|
||||||
|
quiet = False
|
||||||
|
debug = False
|
||||||
|
wild_dir = ''
|
||||||
|
wild_proto = ''
|
||||||
|
wild_secret = ''
|
||||||
|
self_update_repo = ''
|
||||||
|
|
||||||
|
repos = []
|
||||||
|
|
||||||
|
# LOAD FROM CONFIG
|
||||||
|
if 'server' in args.config:
|
||||||
|
if 'address' in args.config['server']: address = args.config['server']['address']
|
||||||
|
if 'port' in args.config['server']: port = args.config['server']['port']
|
||||||
|
if 'quiet' in args.config['server']: quiet = args.config['server']['quiet']
|
||||||
|
if 'debug' in args.config['server']: debug = args.config['server']['debug']
|
||||||
|
if 'wild_dir' in args.config['server']: wild_dir = args.config['server']['wild_dir']
|
||||||
|
if 'wild_proto' in args.config['server']: wild_proto = args.config['server']['wild_proto']
|
||||||
|
if 'wild_secret' in args.config['server']: wild_secret = args.config['server']['wild_secret']
|
||||||
|
if 'self_update_repo' in args.config['server']: self_update_repo = args.config['server']['self_update_repo']
|
||||||
|
|
||||||
|
# OVERRIDE BY ARGS
|
||||||
|
if args.address is not None: address = args.address
|
||||||
|
if args.port is not None: port = args.port
|
||||||
|
if args.quiet is not None: quiet = args.quiet
|
||||||
|
if args.debug is not None: debug = args.debug
|
||||||
|
if args.wild_dir is not None: wild_dir = args.wild_dir
|
||||||
|
if args.wild_proto is not None: wild_proto = args.wild_proto
|
||||||
|
if args.wild_secret is not None: wild_secret = args.wild_secret
|
||||||
|
|
||||||
|
# SET DEFAULT SERVER SETTINGS
|
||||||
|
if address == '': address = DEFAULT_ADDRESS
|
||||||
|
if port == '': port = DEFAULT_PORT
|
||||||
|
|
||||||
|
mp = MessagePrinter(args.quiet, args.debug)
|
||||||
|
pm = mp.pm
|
||||||
|
pdm = mp.pdm
|
||||||
|
|
||||||
|
# PRINT VERSION
|
||||||
|
pm(PROG_NAME + ' ' + PROG_VERSION)
|
||||||
|
|
||||||
|
# REPOS
|
||||||
|
if args.dir is not None:
|
||||||
|
repos.append({
|
||||||
|
'name': '**args_repo**',
|
||||||
|
'branch': args.branch,
|
||||||
|
'remote': args.remote,
|
||||||
|
'dir': args.dir,
|
||||||
|
'action': args.action,
|
||||||
|
'clone_proto': args.clone_proto,
|
||||||
|
'full_name': args.full_name,
|
||||||
|
'secret': args.secret
|
||||||
|
})
|
||||||
|
|
||||||
|
if 'repos' in args.config:
|
||||||
|
repos += args.config['repos']
|
||||||
|
|
||||||
|
# PREPARE REPOS
|
||||||
|
if repos != []:
|
||||||
|
prepare_repos(repos, self_update_repo)
|
||||||
|
|
||||||
|
# CHECK WILD DIR
|
||||||
|
if wild_dir != '':
|
||||||
|
if not os.path.isdir(wild_dir):
|
||||||
|
pm('ERROR: Wild dir "{}" not exists.'.format(wild_dir))
|
||||||
|
exit(12)
|
||||||
|
|
||||||
|
pm('\nWild dir: ' + wild_dir)
|
||||||
|
|
||||||
|
if wild_secret == '':
|
||||||
|
pm('WARNING: No secret given for wild repos! Authentication disabled.')
|
||||||
|
|
||||||
|
# EXIT IF NO REPO OR WILD_DIR DEFINED
|
||||||
|
if repos == [] and wild_dir == '':
|
||||||
|
pm('ERROR: No any repo, dir or wild_dir is defined. Nothing to do.')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# PREPARE SERVER
|
||||||
|
socket = (address, int(port))
|
||||||
|
httpd = WebHookServer(socket, WebHookHandler, repos, self_update_repo, wild_dir, wild_proto, wild_secret)
|
||||||
|
|
||||||
|
# PRINT REPOS INFO
|
||||||
|
if repos != []: pm('\nDefined repos:')
|
||||||
|
for repo in repos:
|
||||||
|
pm('---=== ' + repo['name'] + ' ===---')
|
||||||
|
pm('Repository directory: {}'.format(repo['dir']))
|
||||||
|
pm('Will pull from branch "{}" of remote "{}" '.format(repo['branch'], repo['remote']))
|
||||||
|
if repo['action'] != '':
|
||||||
|
pm('Action: "{}"'.format(repo['action']))
|
||||||
|
if (not 'full_name' in repo) or (repo['full_name'] == ''):
|
||||||
|
pm('WARNING: Repo full name not set. Name check disabled')
|
||||||
|
if (not 'secret' in repo) or (repo['secret'] == ''):
|
||||||
|
pm('WARNING: No secret given! Authentication disabled.')
|
||||||
|
|
||||||
|
if self_update_repo != '':
|
||||||
|
pm('\nSelf update repo: {}'.format(self_update_repo))
|
||||||
|
|
||||||
|
# START SERVER
|
||||||
|
pm('\nPID: ' + str(os.getpid()))
|
||||||
|
pm('Listen: {}:{}'.format(address, port))
|
||||||
|
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
# ----
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user