2025-01-24 14:59:06 +03:00
|
|
|
import json
|
|
|
|
import hashlib
|
|
|
|
import hmac
|
|
|
|
import logging
|
2025-01-25 22:07:39 +03:00
|
|
|
import configparser
|
2025-01-26 03:00:50 +03:00
|
|
|
import subprocess
|
2025-01-30 00:10:18 +03:00
|
|
|
from flask import Flask, request, abort, jsonify
|
2025-01-25 05:56:19 +03:00
|
|
|
import koji
|
|
|
|
|
2025-01-25 22:07:39 +03:00
|
|
|
# Load configuration from .ini file
|
|
|
|
config = configparser.ConfigParser()
|
2025-01-30 00:10:18 +03:00
|
|
|
config.read('/opt/koji-forgejo-webhook/config.ini')
|
2025-01-24 14:59:06 +03:00
|
|
|
|
2025-01-26 03:00:50 +03:00
|
|
|
app = Flask(__name__, instance_relative_config=True)
|
|
|
|
# app.config.from_mapping(config['DEFAULT'])
|
2025-01-30 00:10:18 +03:00
|
|
|
app.config['DEBUG'] = True
|
2025-01-26 03:00:50 +03:00
|
|
|
app.config['SECRET_KEY'] = config.get('DEFAULT', 'SECRET_KEY')
|
|
|
|
app.config['KOJI_SERVER'] = config.get('DEFAULT', 'KOJI_SERVER')
|
|
|
|
app.config['KOJI_TARGET'] = config.get('DEFAULT', 'KOJI_TARGET')
|
|
|
|
app.config['KOJI_USE_SHELL'] = config.getboolean('DEFAULT', 'KOJI_USE_SHELL')
|
2025-01-30 00:10:18 +03:00
|
|
|
app.config['KOJI_NOWAIT'] = config.getboolean('DEFAULT', 'KOJI_NOWAIT')
|
|
|
|
app.config['KOJI_SCRATCH'] = config.getboolean('DEFAULT', 'KOJI_SCRATCH')
|
2025-01-26 03:00:50 +03:00
|
|
|
app.config['WEBHOOK_SECRET_KEY'] = config.get('DEFAULT', 'WEBHOOK_SECRET_KEY')
|
2025-01-25 05:56:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
class KojiProcessor:
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
|
2025-02-07 01:48:03 +03:00
|
|
|
def koji_build_shell(self, build_target: str, git_url: str,
|
|
|
|
scratch: bool = True, nowait: bool = True) -> str:
|
2025-01-30 00:10:18 +03:00
|
|
|
result = {"out": "", "err": ""}
|
|
|
|
|
|
|
|
# Construct the koji build command
|
|
|
|
nw = '--nowait' if nowait else ''
|
|
|
|
sc = '--scratch' if scratch else ''
|
|
|
|
command = ['sudo', 'koji', 'build', sc, nw, build_target, git_url]
|
2025-01-26 03:00:50 +03:00
|
|
|
try:
|
|
|
|
# Start the build process
|
2025-01-30 00:10:18 +03:00
|
|
|
exec = subprocess.run(' '.join(command), shell=True, capture_output=True, text=True)
|
|
|
|
result['err'] = exec.stderr
|
|
|
|
result['out'] = exec.stdout
|
|
|
|
|
2025-01-26 03:00:50 +03:00
|
|
|
# Print output from the command
|
|
|
|
logging.info(f"Build started successfully!")
|
2025-01-30 00:10:18 +03:00
|
|
|
logging.info(exec.stdout)
|
|
|
|
# except subprocess.CalledProcessError as e:
|
|
|
|
# logging.error("Error starting build:")
|
|
|
|
except Exception as e:
|
|
|
|
result['err'] = f"{e}"
|
|
|
|
|
|
|
|
return result
|
2025-01-26 03:00:50 +03:00
|
|
|
|
2025-01-26 08:47:21 +03:00
|
|
|
def koji_build(self, server_url, build_target, source_url, nowait=True):
|
2025-01-25 05:56:19 +03:00
|
|
|
"""
|
|
|
|
Perform a Koji build
|
|
|
|
|
|
|
|
:param server_url: Koji hub server URL
|
|
|
|
:param build_target: Target for the build (e.g., 'f40-candidate')
|
|
|
|
:param source_url: Git repository URL to build from
|
|
|
|
"""
|
|
|
|
# Create a Koji client session
|
|
|
|
session = koji.ClientSession(server_url)
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Authenticate (if required)
|
|
|
|
# Uncomment if authentication is needed
|
|
|
|
session.login()
|
|
|
|
|
|
|
|
# Submit the build task
|
|
|
|
build_id = session.build(
|
|
|
|
source_url, # Source URL
|
|
|
|
build_target, # Build target
|
|
|
|
opts={
|
|
|
|
'scratch': False, # Set to True for scratch build
|
2025-01-26 08:47:21 +03:00
|
|
|
'nowait': nowait # Wait for build completion
|
2025-01-25 05:56:19 +03:00
|
|
|
}
|
|
|
|
)
|
|
|
|
return build_id
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
print(f"Build failed: {e}")
|
|
|
|
return None
|
|
|
|
|
2025-01-24 14:59:06 +03:00
|
|
|
|
|
|
|
class ForgejoWebhookProcessor:
|
|
|
|
def __init__(self, secret_key):
|
|
|
|
self.secret_key = secret_key
|
|
|
|
|
|
|
|
def validate_webhook(self, payload, signature):
|
|
|
|
"""Validate webhook signature"""
|
|
|
|
computed_signature = hmac.new(
|
|
|
|
self.secret_key.encode('utf-8'),
|
|
|
|
payload.encode('utf-8'),
|
|
|
|
hashlib.sha256
|
|
|
|
).hexdigest()
|
|
|
|
|
|
|
|
return hmac.compare_digest(computed_signature, signature)
|
|
|
|
|
|
|
|
def process_webhook(self, payload):
|
|
|
|
"""Process webhook payload"""
|
2025-01-30 00:10:18 +03:00
|
|
|
result = dict()
|
2025-01-24 14:59:06 +03:00
|
|
|
try:
|
|
|
|
data = json.loads(payload)
|
|
|
|
|
|
|
|
# Extract key webhook information
|
|
|
|
repository = data.get('repository', {})
|
|
|
|
commits = data.get('commits', [])
|
2025-01-25 05:56:19 +03:00
|
|
|
head_commit = data.get('head_commit')
|
2025-01-24 14:59:06 +03:00
|
|
|
ref = data.get('ref', '')
|
|
|
|
|
|
|
|
# Log basic webhook details
|
|
|
|
logging.info(f"Repository: {repository.get('full_name')}")
|
|
|
|
logging.info(f"Branch: {ref}")
|
|
|
|
logging.info(f"Number of Commits: {len(commits)}")
|
|
|
|
|
2025-01-30 00:10:18 +03:00
|
|
|
# result = head_commit.get("id")
|
2025-01-24 14:59:06 +03:00
|
|
|
# Custom processing logic here
|
2025-01-25 05:56:19 +03:00
|
|
|
# for commit in commits:
|
|
|
|
# logging.info(f"Commit: {commit.get('id')}")
|
|
|
|
# logging.info(f"Message: {commit.get('message')}")
|
|
|
|
|
|
|
|
commit_id = head_commit.get("id")
|
|
|
|
clone_url = repository.get("clone_url")
|
|
|
|
|
|
|
|
git_url = f"git+{clone_url}#{commit_id}"
|
|
|
|
|
2025-02-07 01:48:03 +03:00
|
|
|
scratch = True
|
|
|
|
if 'release' in ref.lower():
|
|
|
|
scratch = False
|
|
|
|
|
2025-01-30 00:10:18 +03:00
|
|
|
if app.config.get('KOJI_USE_SHELL'):
|
2025-02-07 01:48:03 +03:00
|
|
|
result = KojiProcessor().koji_build_shell(app.config['KOJI_TARGET'], git_url, scratch=scratch)
|
2025-01-26 03:00:50 +03:00
|
|
|
else:
|
2025-01-30 00:10:18 +03:00
|
|
|
result = KojiProcessor().koji_build(app.config['KOJI_SERVER'], app.config['KOJI_TARGET'], git_url)
|
2025-01-24 14:59:06 +03:00
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
logging.error("Invalid JSON payload")
|
|
|
|
abort(400)
|
2025-01-30 00:10:18 +03:00
|
|
|
return result
|
|
|
|
|
2025-01-24 14:59:06 +03:00
|
|
|
|
2025-01-30 00:10:18 +03:00
|
|
|
@app.route('/ping', methods=['GET'])
|
|
|
|
def ping():
|
|
|
|
result = subprocess.run('sudo koji moshimoshi', shell=True, capture_output=True, text=True)
|
|
|
|
stdout = result.stdout
|
|
|
|
stderr = result.stderr
|
|
|
|
return jsonify({
|
|
|
|
"out": f"{stdout}",
|
|
|
|
"err": f"{stderr}",
|
|
|
|
})
|
2025-01-24 14:59:06 +03:00
|
|
|
|
2025-01-30 00:10:18 +03:00
|
|
|
|
|
|
|
@app.route('/forgejo', methods=['POST'])
|
2025-01-24 14:59:06 +03:00
|
|
|
def webhook():
|
2025-01-30 00:10:18 +03:00
|
|
|
processor = ForgejoWebhookProcessor(app.config.get('WEBHOOK_SECRET_KEY'))
|
2025-01-24 14:59:06 +03:00
|
|
|
|
|
|
|
# Get payload and signature
|
|
|
|
payload = request.get_data(as_text=True)
|
|
|
|
signature = request.headers.get('X-Forgejo-Signature', '')
|
|
|
|
|
|
|
|
# Validate webhook
|
|
|
|
if not processor.validate_webhook(payload, signature):
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
# Process webhook
|
2025-01-30 00:10:18 +03:00
|
|
|
return jsonify(processor.process_webhook(payload))
|
2025-01-24 14:59:06 +03:00
|
|
|
|
2025-01-25 22:07:39 +03:00
|
|
|
|
2025-01-24 14:59:06 +03:00
|
|
|
if __name__ == '__main__':
|
2025-01-30 00:10:18 +03:00
|
|
|
logging.basicConfig(filename='/opt/koji-forgejo-webhook/error.log', level=logging.DEBUG)
|
2025-01-25 22:41:37 +03:00
|
|
|
app.run(host="0.0.0.0", port=5001)
|