3
0

Merge branch 'jenkins-build-21' into 'master'

Auto-merge for build 21

See merge request puppet/puppet_cd!18
This commit is contained in:
2025-10-23 18:23:53 +00:00
7 changed files with 28 additions and 404 deletions

View File

@@ -1670,17 +1670,7 @@
238
239
240
241
242
243
244
245
246
247
248
249
250
251</pre>
241</pre>
</td>
<td>
<pre class="code"><span class="info file"># File 'manifests/params.pp', line 88</span>
@@ -1801,9 +1791,6 @@ class puppet_cd::params (
$pt_puppetdb_ssl = &quot;${pt_puppetdb_main}/ssl&quot;
$pt_puppetdb_log = &#39;/var/log/puppetlabs/puppetdb&#39;
$pt_puppetdb_var_dir = &#39;/opt/puppetlabs/server/data/puppetdb&#39;
## r10k
$pt_r10k_dir = &quot;${pt_main_dir}/r10k&quot;
$pt_r10k_webhook_dir = &#39;/etc/r10k-webhook&#39;
# files
## puppet
@@ -1830,14 +1817,7 @@ class puppet_cd::params (
$pt_puppetdb_repl_ini = &quot;${pt_puppetdb_conf_d}/repl.ini&quot;
$pt_puppetdb_repl_erb = &#39;puppet_cd/puppetdb/repl.ini.erb&#39;
## r10k
$pt_r10k_file = &quot;${pt_r10k_dir}/r10k.yaml&quot;
$pt_r10k_erb = &#39;puppet_cd/r10k/r10k.yaml.erb&#39;
$pt_r10k_webhook_file = &quot;${pt_r10k_webhook_dir}/webhook_server.py&quot;
$pt_r10k_webhook_erb = &#39;puppet_cd/r10k/webhook.py.erb&#39;
$pt_r10k_req_file = &quot;${pt_r10k_webhook_dir}/requirements.txt&quot;
$pt_r10k_req_erb = &#39;puppet_cd/r10k/requirements.txt.erb&#39;
$pt_r10k_wh_config_file = &quot;${pt_r10k_webhook_dir}/config.json&quot;
$pt_r10k_wh_config_erb = &#39;puppet_cd/r10k/r10k_webhook_config.erb&#39;
$pt_r10k_hook_file = &#39;/usr/local/bin/webhook&#39;
# service
$pt_server_service = &#39;puppetserver&#39;

View File

@@ -125,55 +125,7 @@
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78</pre>
30</pre>
</td>
<td>
<pre class="code"><span class="info file"># File 'manifests/r10k/webhook.pp', line 6</span>
@@ -182,73 +134,25 @@ class puppet_cd::r10k::webhook (
) inherits puppet_cd::params {
if ($pt_pm_fqdn == $fqdn) and ($pt_use_r10k_webhook == true) {
# install packages
package { $pt_r10k_webhook_pkg:
ensure =&gt; $pt_pkg_ensure,
}
# create the webhook dir
file { $pt_r10k_webhook_dir:
ensure =&gt; directory,
# create the webhook binary
file { $pt_r10k_hook_file:
ensure =&gt; file,
owner =&gt; &#39;root&#39;,
group =&gt; &#39;root&#39;,
mode =&gt; &#39;0755&#39;,
selrange =&gt; s0,
selrole =&gt; object_r,
seltype =&gt; etc_t,
seluser =&gt; system_u,
seltype =&gt; bin_t,
seluser =&gt; unconfined_u,
source =&gt; &#39;puppet:///module/puppet_cd/webhook&#39;,
}
# create the requirements file
file { $pt_r10k_req_file:
ensure =&gt; file,
owner =&gt; &#39;puppet&#39;,
group =&gt; &#39;puppet&#39;,
mode =&gt; &#39;0644&#39;,
selrange =&gt; s0,
selrole =&gt; object_r,
seltype =&gt; etc_t,
seluser =&gt; system_u,
content =&gt; template($pt_r10k_req_erb),
}
# create the webhook config file
file { $pt_r10k_wh_config_file:
ensure =&gt; file,
owner =&gt; &#39;root&#39;,
group =&gt; &#39;root&#39;,
mode =&gt; &#39;0644&#39;,
selrange =&gt; s0,
selrole =&gt; object_r,
seltype =&gt; etc_t,
seluser =&gt; system_u,
content =&gt; template($pt_r10k_wh_config_erb),
require =&gt; File[$pt_r10k_webhook_dir],
}
# install pip dependencies
exec { &#39;pip_install_r10k_webhook&#39;:
command =&gt; &#39;pip3 install --user -r /opt/r10k-webhook/requirements.txt&#39;,
user =&gt; &#39;puppet&#39;,
require =&gt; [Package[$pt_r10k_webhook_pkg],File[$pt_r10k_req_file]],
unless =&gt; &#39;pip3 show fastapi&#39;, # Idempotent check
}
# establish exec systemd reload
exec { &#39;systemctl_daemon_reload&#39;:
command =&gt; &#39;systemctl daemon-reload&#39;,
path =&gt; [&#39;/bin&#39;, &#39;/usr/bin&#39;],
require =&gt; Exec[&#39;pip_install_r10k_webhook&#39;],
refreshonly =&gt; true,
}
# manage service
service { &#39;r10k-webhook&#39;:
ensure =&gt; &#39;running&#39;,
enable =&gt; true,
subscribe =&gt; File[$pt_r10k_wh_config_file],
}
# # manage service
# service { &#39;r10k-webhook&#39;:
# ensure =&gt; &#39;running&#39;,
# enable =&gt; true,
# subscribe =&gt; File[$pt_r10k_wh_config_file],
# }
}
}</pre>
</td>

BIN
files/webhook Normal file

Binary file not shown.

View File

@@ -201,9 +201,6 @@ class puppet_cd::params (
$pt_puppetdb_ssl = "${pt_puppetdb_main}/ssl"
$pt_puppetdb_log = '/var/log/puppetlabs/puppetdb'
$pt_puppetdb_var_dir = '/opt/puppetlabs/server/data/puppetdb'
## r10k
$pt_r10k_dir = "${pt_main_dir}/r10k"
$pt_r10k_webhook_dir = '/etc/r10k-webhook'
# files
## puppet
@@ -230,14 +227,7 @@ class puppet_cd::params (
$pt_puppetdb_repl_ini = "${pt_puppetdb_conf_d}/repl.ini"
$pt_puppetdb_repl_erb = 'puppet_cd/puppetdb/repl.ini.erb'
## r10k
$pt_r10k_file = "${pt_r10k_dir}/r10k.yaml"
$pt_r10k_erb = 'puppet_cd/r10k/r10k.yaml.erb'
$pt_r10k_webhook_file = "${pt_r10k_webhook_dir}/webhook_server.py"
$pt_r10k_webhook_erb = 'puppet_cd/r10k/webhook.py.erb'
$pt_r10k_req_file = "${pt_r10k_webhook_dir}/requirements.txt"
$pt_r10k_req_erb = 'puppet_cd/r10k/requirements.txt.erb'
$pt_r10k_wh_config_file = "${pt_r10k_webhook_dir}/config.json"
$pt_r10k_wh_config_erb = 'puppet_cd/r10k/r10k_webhook_config.erb'
$pt_r10k_hook_file = '/usr/local/bin/webhook'
# service
$pt_server_service = 'puppetserver'

View File

@@ -7,72 +7,24 @@ class puppet_cd::r10k::webhook (
) inherits puppet_cd::params {
if ($pt_pm_fqdn == $fqdn) and ($pt_use_r10k_webhook == true) {
# install packages
package { $pt_r10k_webhook_pkg:
ensure => $pt_pkg_ensure,
}
# create the webhook dir
file { $pt_r10k_webhook_dir:
ensure => directory,
# create the webhook binary
file { $pt_r10k_hook_file:
ensure => file,
owner => 'root',
group => 'root',
mode => '0755',
selrange => s0,
selrole => object_r,
seltype => etc_t,
seluser => system_u,
seltype => bin_t,
seluser => unconfined_u,
source => 'puppet:///module/puppet_cd/webhook',
}
# create the requirements file
file { $pt_r10k_req_file:
ensure => file,
owner => 'puppet',
group => 'puppet',
mode => '0644',
selrange => s0,
selrole => object_r,
seltype => etc_t,
seluser => system_u,
content => template($pt_r10k_req_erb),
}
# create the webhook config file
file { $pt_r10k_wh_config_file:
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
selrange => s0,
selrole => object_r,
seltype => etc_t,
seluser => system_u,
content => template($pt_r10k_wh_config_erb),
require => File[$pt_r10k_webhook_dir],
}
# install pip dependencies
exec { 'pip_install_r10k_webhook':
command => 'pip3 install --user -r /opt/r10k-webhook/requirements.txt',
user => 'puppet',
require => [Package[$pt_r10k_webhook_pkg],File[$pt_r10k_req_file]],
unless => 'pip3 show fastapi', # Idempotent check
}
# establish exec systemd reload
exec { 'systemctl_daemon_reload':
command => 'systemctl daemon-reload',
path => ['/bin', '/usr/bin'],
require => Exec['pip_install_r10k_webhook'],
refreshonly => true,
}
# manage service
service { 'r10k-webhook':
ensure => 'running',
enable => true,
subscribe => File[$pt_r10k_wh_config_file],
}
# # manage service
# service { 'r10k-webhook':
# ensure => 'running',
# enable => true,
# subscribe => File[$pt_r10k_wh_config_file],
# }
}
}

View File

@@ -1,4 +0,0 @@
#fastapi==0.115.0
#uvicorn==0.30.6
#pydantic==2.8.2
r10k-webhook

View File

@@ -1,198 +0,0 @@
#!/usr/bin/env python3
"""
Custom r10k Webhook Server for Puppet Control Repo
"""
from datetime import datetime
import os
import subprocess
import logging
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
from fastapi.responses import JSONResponse
import uvicorn
from pydantic import BaseModel
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/r10k-webhook.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
app = FastAPI(title="r10k Webhook Server")
class WebhookPayload(BaseModel):
"""Data model for webhook payload"""
ref: str
project: dict
commits: list
def run_r10k_deploy() -> bool:
"""Run r10k deploy command"""
try:
cmd = [
'/usr/bin/r10k', 'deploy',
'-v',
'-c', '/etc/puppetlabs/r10k/r10k.conf'
]
logger.info("Starting r10k deploy...")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
logger.info("r10k deploy successful!")
logger.debug("r10k stdout: %s", result.stdout)
if result.stderr:
logger.warning("r10k stderr: %s", result.stderr)
return True
except subprocess.CalledProcessError as e:
logger.error("r10k deploy failed: %s", e)
logger.error("stdout: %s", e.stdout)
logger.error("stderr: %s", e.stderr)
return False
except FileNotFoundError:
logger.error("r10k binary not found")
return False
except PermissionError:
logger.error("Permission denied running r10k")
return False
def validate_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Validate webhook signature"""
if not secret:
return True
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
if signature.startswith('sha256='):
return hmac.compare_digest(signature, f'sha256={expected}')
return hmac.compare_digest(signature, expected)
@app.post("/webhook")
async def webhook_handler(
request: Request,
background_tasks: BackgroundTasks
):
"""Handle incoming webhook requests"""
body = await request.body()
headers = dict(request.headers)
event_type = headers.get(
'x-gitlab-event',
headers.get('x-github-event', 'unknown')
)
signature = headers.get(
'x-gitlab-token',
headers.get('x-hub-signature-256', '')
)
print(
f"DEBUG: Received webhook: event_type={event_type}, "
f"headers={headers}"
)
logger.info(
"Received webhook: event_type=%s, headers=%s",
event_type,
headers
)
webhook_secret = os.getenv('R10K_WEBHOOK_SECRET', '')
is_valid = validate_signature(body, signature, webhook_secret)
if webhook_secret and not is_valid:
logger.warning("Invalid webhook signature")
raise HTTPException(status_code=403, detail="Invalid signature")
try:
payload = await request.json()
ref = payload.get('ref', '')
branch = ref.split('/')[-1] if '/' in ref else ref
print(
f"DEBUG: Parsed payload: ref={ref}, "
f"branch={branch}"
)
logger.info("Parsed payload: ref=%s, branch=%s", ref, branch)
if branch not in ['main', 'master']:
logger.info("Ignoring non-main branch: %s", branch)
return JSONResponse({
"status": "ignored",
"branch": branch
})
# Match GitLab event types explicitly
valid_events = [
'push hook', 'merge request hook',
'push', 'Push', 'Push Hook'
]
normalized_event = event_type.lower().strip()
print(f"DEBUG: Normalized event: {normalized_event}")
logger.info("Normalized event: %s", normalized_event)
if normalized_event in valid_events:
logger.info("Triggering r10k for %s on %s", event_type, branch)
background_tasks.add_task(run_r10k_deploy)
return JSONResponse({
"status": "accepted",
"message": "r10k deploy triggered",
"timestamp": datetime.utcnow().isoformat(),
"branch": branch
})
logger.info("Ignoring event type: %s", event_type)
return JSONResponse({
"status": "ignored",
"event": event_type
})
except ValueError as e:
logger.error("Webhook processing error: %s", e)
raise HTTPException(
status_code=400,
detail="Invalid payload"
) from e
@app.get("/health")
async def health_check():
"""Health check endpoint"""
result = subprocess.run(
['r10k', '--version'],
capture_output=True,
text=True,
check=True
)
return {
"status": "healthy",
"r10k_version": result.stdout.strip()
}
if __name__ == "__main__":
uvicorn.run(
"webhook_server:app",
host="0.0.0.0",
port=8080,
log_level="info"
)