gitwebhooks 2.4.1__py3-none-any.whl → 2.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitwebhooks/__init__.py +1 -1
- gitwebhooks/auth/custom.py +6 -0
- gitwebhooks/auth/factory.py +0 -25
- gitwebhooks/auth/gitee.py +6 -0
- gitwebhooks/auth/gitlab.py +6 -0
- gitwebhooks/cli/init_wizard.py +12 -6
- gitwebhooks/cli/prompts.py +9 -205
- gitwebhooks/cli/serve.py +4 -39
- gitwebhooks/cli/service.py +13 -17
- gitwebhooks/config/loader.py +28 -10
- gitwebhooks/config/models.py +0 -1
- gitwebhooks/config/registry.py +0 -38
- gitwebhooks/config/server.py +1 -1
- gitwebhooks/handlers/base.py +0 -10
- gitwebhooks/handlers/custom.py +4 -7
- gitwebhooks/handlers/factory.py +0 -37
- gitwebhooks/handlers/gitee.py +4 -8
- gitwebhooks/handlers/github.py +2 -6
- gitwebhooks/handlers/gitlab.py +2 -6
- gitwebhooks/handlers/request.py +1 -1
- gitwebhooks/main/version.py +0 -2
- gitwebhooks/models/request.py +0 -30
- gitwebhooks/server.py +0 -2
- gitwebhooks/utils/executor.py +80 -5
- gitwebhooks/utils/systemd.py +2 -46
- {gitwebhooks-2.4.1.dist-info → gitwebhooks-2.4.2.dist-info}/METADATA +20 -13
- gitwebhooks-2.4.2.dist-info/RECORD +51 -0
- gitwebhooks-2.4.1.dist-info/RECORD +0 -51
- {gitwebhooks-2.4.1.dist-info → gitwebhooks-2.4.2.dist-info}/WHEEL +0 -0
- {gitwebhooks-2.4.1.dist-info → gitwebhooks-2.4.2.dist-info}/entry_points.txt +0 -0
- {gitwebhooks-2.4.1.dist-info → gitwebhooks-2.4.2.dist-info}/licenses/LICENSE +0 -0
- {gitwebhooks-2.4.1.dist-info → gitwebhooks-2.4.2.dist-info}/top_level.txt +0 -0
gitwebhooks/__init__.py
CHANGED
gitwebhooks/auth/custom.py
CHANGED
|
@@ -34,6 +34,12 @@ class CustomTokenVerifier(SignatureVerifier):
|
|
|
34
34
|
if not self.verify_enabled:
|
|
35
35
|
return SignatureVerificationResult.success()
|
|
36
36
|
|
|
37
|
+
# An empty secret means verification is not actually configured.
|
|
38
|
+
# Without this guard an empty token would match and let an
|
|
39
|
+
# unauthenticated request through (which would run the deploy command).
|
|
40
|
+
if not secret:
|
|
41
|
+
return SignatureVerificationResult.failure('Secret not configured')
|
|
42
|
+
|
|
37
43
|
if signature == secret:
|
|
38
44
|
return SignatureVerificationResult.success()
|
|
39
45
|
else:
|
gitwebhooks/auth/factory.py
CHANGED
|
@@ -4,12 +4,10 @@ Creates corresponding verifier instances based on provider type.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from gitwebhooks.models.provider import Provider
|
|
7
|
-
from gitwebhooks.auth.verifier import SignatureVerifier
|
|
8
7
|
from gitwebhooks.auth.github import GithubSignatureVerifier
|
|
9
8
|
from gitwebhooks.auth.gitee import GiteeSignatureVerifier
|
|
10
9
|
from gitwebhooks.auth.gitlab import GitlabTokenVerifier
|
|
11
10
|
from gitwebhooks.auth.custom import CustomTokenVerifier
|
|
12
|
-
from gitwebhooks.utils.exceptions import UnsupportedProviderError
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class VerifierFactory:
|
|
@@ -25,29 +23,6 @@ class VerifierFactory:
|
|
|
25
23
|
Provider.CUSTOM: None, # Custom verifier created dynamically
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
@classmethod
|
|
29
|
-
def get_verifier(cls, provider: Provider,
|
|
30
|
-
verify_enabled: bool = True) -> SignatureVerifier:
|
|
31
|
-
"""Get verifier for the specified provider
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
provider: Provider type
|
|
35
|
-
verify_enabled: Whether verification is enabled (CUSTOM only)
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
SignatureVerifier instance
|
|
39
|
-
|
|
40
|
-
Raises:
|
|
41
|
-
UnsupportedProviderError: Unsupported provider
|
|
42
|
-
"""
|
|
43
|
-
if provider == Provider.CUSTOM:
|
|
44
|
-
return CustomTokenVerifier(verify_enabled=verify_enabled)
|
|
45
|
-
|
|
46
|
-
verifier = cls._verifiers.get(provider)
|
|
47
|
-
if verifier is None:
|
|
48
|
-
raise UnsupportedProviderError(f'No verifier for provider: {provider}')
|
|
49
|
-
return verifier
|
|
50
|
-
|
|
51
26
|
@classmethod
|
|
52
27
|
def create_github_verifier(cls) -> GithubSignatureVerifier:
|
|
53
28
|
"""Create GitHub verifier (convenience method)"""
|
gitwebhooks/auth/gitee.py
CHANGED
|
@@ -51,6 +51,12 @@ class GiteeSignatureVerifier(SignatureVerifier):
|
|
|
51
51
|
if signature is None:
|
|
52
52
|
return SignatureVerificationResult.failure('Missing signature or password')
|
|
53
53
|
|
|
54
|
+
# An empty secret means verification is not actually configured.
|
|
55
|
+
# Without this guard an empty token would match and let an
|
|
56
|
+
# unauthenticated request through (which would run the deploy command).
|
|
57
|
+
if not secret:
|
|
58
|
+
return SignatureVerificationResult.failure('Secret not configured')
|
|
59
|
+
|
|
54
60
|
# Step 1: Try password validation first (direct comparison)
|
|
55
61
|
if hmac.compare_digest(signature, secret):
|
|
56
62
|
return SignatureVerificationResult.success()
|
gitwebhooks/auth/gitlab.py
CHANGED
|
@@ -23,6 +23,12 @@ class GitlabTokenVerifier(SignatureVerifier):
|
|
|
23
23
|
Returns:
|
|
24
24
|
SignatureVerificationResult instance
|
|
25
25
|
"""
|
|
26
|
+
# An empty secret means verification is not actually configured.
|
|
27
|
+
# Without this guard an empty token would match and let an
|
|
28
|
+
# unauthenticated request through (which would run the deploy command).
|
|
29
|
+
if not secret:
|
|
30
|
+
return SignatureVerificationResult.failure('Secret not configured')
|
|
31
|
+
|
|
26
32
|
if signature == secret:
|
|
27
33
|
return SignatureVerificationResult.success()
|
|
28
34
|
else:
|
gitwebhooks/cli/init_wizard.py
CHANGED
|
@@ -16,7 +16,7 @@ Validation Functions:
|
|
|
16
16
|
- _get_platform_default_event: Returns platform-specific default event
|
|
17
17
|
|
|
18
18
|
TECHNICAL DEBT:
|
|
19
|
-
This module (
|
|
19
|
+
This module (785 lines) exceeds the Constitution's recommended 400 line limit.
|
|
20
20
|
This is acceptable because the module contains the complete interactive wizard
|
|
21
21
|
logic including platform selection, repository configuration, event configuration,
|
|
22
22
|
and file generation.
|
|
@@ -30,9 +30,15 @@ Refactoring Plan (future task):
|
|
|
30
30
|
import os
|
|
31
31
|
import re
|
|
32
32
|
from dataclasses import dataclass
|
|
33
|
-
from typing import
|
|
33
|
+
from typing import Dict, Optional, Tuple
|
|
34
34
|
import configparser
|
|
35
35
|
|
|
36
|
+
from gitwebhooks.utils.constants import (
|
|
37
|
+
CONFIG_PATH_LOCAL,
|
|
38
|
+
CONFIG_PATH_SYSTEM,
|
|
39
|
+
CONFIG_PATH_USER,
|
|
40
|
+
)
|
|
41
|
+
|
|
36
42
|
|
|
37
43
|
# =============================================================================
|
|
38
44
|
# Module Constants
|
|
@@ -40,15 +46,15 @@ import configparser
|
|
|
40
46
|
|
|
41
47
|
CONFIG_LEVELS = {
|
|
42
48
|
'system': {
|
|
43
|
-
'path':
|
|
49
|
+
'path': CONFIG_PATH_SYSTEM,
|
|
44
50
|
'requires_root': True
|
|
45
51
|
},
|
|
46
52
|
'local': {
|
|
47
|
-
'path':
|
|
53
|
+
'path': CONFIG_PATH_LOCAL,
|
|
48
54
|
'requires_root': True
|
|
49
55
|
},
|
|
50
56
|
'user': {
|
|
51
|
-
'path':
|
|
57
|
+
'path': CONFIG_PATH_USER,
|
|
52
58
|
'requires_root': False
|
|
53
59
|
}
|
|
54
60
|
}
|
|
@@ -201,7 +207,7 @@ def validate_port(value: str) -> bool:
|
|
|
201
207
|
return False
|
|
202
208
|
|
|
203
209
|
|
|
204
|
-
def validate_event_input(value: str) ->
|
|
210
|
+
def validate_event_input(value: str) -> Tuple[bool, str]:
|
|
205
211
|
"""
|
|
206
212
|
验证事件名称输入。
|
|
207
213
|
|
gitwebhooks/cli/prompts.py
CHANGED
|
@@ -4,143 +4,19 @@ Provides functions for interactive user input and validation.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import sys
|
|
7
|
-
from typing import Any, Callable, Optional, Dict, List, Tuple
|
|
8
7
|
|
|
9
|
-
from gitwebhooks.utils.constants import
|
|
8
|
+
from gitwebhooks.utils.constants import (
|
|
9
|
+
CONFIG_PATH_LOCAL,
|
|
10
|
+
CONFIG_PATH_SYSTEM,
|
|
11
|
+
CONFIG_PATH_USER,
|
|
12
|
+
ConfigLevel,
|
|
13
|
+
)
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
# Question template for configuration initialization
|
|
13
|
-
QUESTIONS = [
|
|
14
|
-
# Server configuration
|
|
15
|
-
{
|
|
16
|
-
'key': 'server_address',
|
|
17
|
-
'prompt': 'Server listen address [default: 0.0.0.0]: ',
|
|
18
|
-
'default': '0.0.0.0',
|
|
19
|
-
'validator': None,
|
|
20
|
-
'required': True
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
'key': 'server_port',
|
|
24
|
-
'prompt': 'Server port [default: 6789]: ',
|
|
25
|
-
'default': '6789',
|
|
26
|
-
'validator': 'validate_port',
|
|
27
|
-
'required': True
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
'key': 'log_file',
|
|
31
|
-
'prompt': 'Log file path [default: ~/.gitwebhook.log]: ',
|
|
32
|
-
'default': '~/.gitwebhook.log',
|
|
33
|
-
'validator': None,
|
|
34
|
-
'required': False
|
|
35
|
-
},
|
|
36
|
-
]
|
|
37
17
|
|
|
38
18
|
|
|
39
19
|
# Platform-specific questions
|
|
40
|
-
PLATFORM_QUESTIONS = {
|
|
41
|
-
'github': [
|
|
42
|
-
{
|
|
43
|
-
'section': 'github',
|
|
44
|
-
'key': 'verify',
|
|
45
|
-
'prompt': ' Enable signature verification? (Y/n) [default: Y]: ',
|
|
46
|
-
'default': 'true',
|
|
47
|
-
'validator': None,
|
|
48
|
-
'required': False
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
'section': 'github',
|
|
52
|
-
'key': 'secret',
|
|
53
|
-
'prompt': ' Webhook secret (leave empty to skip): ',
|
|
54
|
-
'default': '',
|
|
55
|
-
'validator': None,
|
|
56
|
-
'required': False
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
'gitee': [
|
|
60
|
-
{
|
|
61
|
-
'section': 'gitee',
|
|
62
|
-
'key': 'verify',
|
|
63
|
-
'prompt': ' Enable signature verification? (Y/n) [default: Y]: ',
|
|
64
|
-
'default': 'true',
|
|
65
|
-
'validator': None,
|
|
66
|
-
'required': False
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
'section': 'gitee',
|
|
70
|
-
'key': 'secret',
|
|
71
|
-
'prompt': ' Webhook secret (leave empty to skip): ',
|
|
72
|
-
'default': '',
|
|
73
|
-
'validator': None,
|
|
74
|
-
'required': False
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
'gitlab': [
|
|
78
|
-
{
|
|
79
|
-
'section': 'gitlab',
|
|
80
|
-
'key': 'verify',
|
|
81
|
-
'prompt': ' Enable token verification? (Y/n) [default: Y]: ',
|
|
82
|
-
'default': 'true',
|
|
83
|
-
'validator': None,
|
|
84
|
-
'required': False
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
'section': 'gitlab',
|
|
88
|
-
'key': 'secret',
|
|
89
|
-
'prompt': ' Webhook token (leave empty to skip): ',
|
|
90
|
-
'default': '',
|
|
91
|
-
'validator': None,
|
|
92
|
-
'required': False
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def ask_question(prompt: str,
|
|
99
|
-
validator: Optional[Callable[[str], Any]] = None,
|
|
100
|
-
default: Optional[Any] = None,
|
|
101
|
-
required: bool = True) -> Any:
|
|
102
|
-
"""Ask a question with optional validation and default value
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
prompt: Question prompt to display
|
|
106
|
-
validator: Optional validation function that raises ValueError on invalid input
|
|
107
|
-
default: Optional default value if user provides empty input
|
|
108
|
-
required: Whether a value is required (defaults to True)
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
Validated user input or default value
|
|
112
|
-
|
|
113
|
-
Raises:
|
|
114
|
-
KeyboardInterrupt: If user confirms exit on Ctrl+C
|
|
115
|
-
"""
|
|
116
|
-
while True:
|
|
117
|
-
try:
|
|
118
|
-
response = input(prompt).strip()
|
|
119
|
-
|
|
120
|
-
# Use default if empty input provided
|
|
121
|
-
if not response and default is not None:
|
|
122
|
-
return default
|
|
123
|
-
|
|
124
|
-
# Validate if validator provided
|
|
125
|
-
if validator:
|
|
126
|
-
if isinstance(validator, str):
|
|
127
|
-
# Validator is a string name, look up function
|
|
128
|
-
validator_func = globals().get(validator)
|
|
129
|
-
if validator_func:
|
|
130
|
-
return validator_func(response)
|
|
131
|
-
else:
|
|
132
|
-
return validator(response)
|
|
133
|
-
|
|
134
|
-
return response
|
|
135
|
-
|
|
136
|
-
except KeyboardInterrupt:
|
|
137
|
-
# Ask for confirmation before exiting
|
|
138
|
-
if ask_yes_no('Confirm exit? Unsaved changes will be lost (y/N) ', default=False):
|
|
139
|
-
print('Configuration cancelled')
|
|
140
|
-
sys.exit(0)
|
|
141
|
-
except ValueError as e:
|
|
142
|
-
print(f'Invalid input: {e}')
|
|
143
|
-
print('Please try again')
|
|
144
20
|
|
|
145
21
|
|
|
146
22
|
def ask_yes_no(prompt: str, default: bool = False) -> bool:
|
|
@@ -205,78 +81,6 @@ def validate_port(value: str) -> int:
|
|
|
205
81
|
raise ValueError('Port must be a number')
|
|
206
82
|
|
|
207
83
|
|
|
208
|
-
def validate_address(value: str) -> str:
|
|
209
|
-
"""Validate server address
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
value: String input to validate
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
Validated address string
|
|
216
|
-
|
|
217
|
-
Raises:
|
|
218
|
-
ValueError: If address is empty
|
|
219
|
-
"""
|
|
220
|
-
if not value:
|
|
221
|
-
raise ValueError('Server address cannot be empty')
|
|
222
|
-
return value
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def validate_path(value: str) -> str:
|
|
226
|
-
"""Validate file path
|
|
227
|
-
|
|
228
|
-
Args:
|
|
229
|
-
value: String input to validate
|
|
230
|
-
|
|
231
|
-
Returns:
|
|
232
|
-
Validated path string
|
|
233
|
-
|
|
234
|
-
Raises:
|
|
235
|
-
ValueError: If path is empty
|
|
236
|
-
"""
|
|
237
|
-
if not value:
|
|
238
|
-
raise ValueError('Path cannot be empty')
|
|
239
|
-
return value
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def validate_bool(value: str) -> bool:
|
|
243
|
-
"""Validate boolean input
|
|
244
|
-
|
|
245
|
-
Args:
|
|
246
|
-
value: String input to validate
|
|
247
|
-
|
|
248
|
-
Returns:
|
|
249
|
-
True or False
|
|
250
|
-
|
|
251
|
-
Raises:
|
|
252
|
-
ValueError: If input is not a boolean value
|
|
253
|
-
"""
|
|
254
|
-
value_lower = value.lower()
|
|
255
|
-
if value_lower in ('y', 'yes', 'true', '1'):
|
|
256
|
-
return True
|
|
257
|
-
elif value_lower in ('n', 'no', 'false', '0'):
|
|
258
|
-
return False
|
|
259
|
-
else:
|
|
260
|
-
raise ValueError('Please enter yes/no or y/n')
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def validate_text(value: str) -> str:
|
|
264
|
-
"""Validate text input (non-empty)
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
value: String input to validate
|
|
268
|
-
|
|
269
|
-
Returns:
|
|
270
|
-
Validated text string
|
|
271
|
-
|
|
272
|
-
Raises:
|
|
273
|
-
ValueError: If text is empty
|
|
274
|
-
"""
|
|
275
|
-
if not value:
|
|
276
|
-
raise ValueError('Input cannot be empty')
|
|
277
|
-
return value
|
|
278
|
-
|
|
279
|
-
|
|
280
84
|
def ask_config_level() -> ConfigLevel:
|
|
281
85
|
"""Ask user to select configuration file level
|
|
282
86
|
|
|
@@ -290,9 +94,9 @@ def ask_config_level() -> ConfigLevel:
|
|
|
290
94
|
KeyboardInterrupt: If user confirms exit on Ctrl+C
|
|
291
95
|
"""
|
|
292
96
|
print('Select configuration file level for the service:')
|
|
293
|
-
print(' 1. User level (
|
|
294
|
-
print(' 2. Local level (
|
|
295
|
-
print(' 3. System level (
|
|
97
|
+
print(f' 1. User level ({CONFIG_PATH_USER}) - Single user, highest priority')
|
|
98
|
+
print(f' 2. Local level ({CONFIG_PATH_LOCAL}) - Local system, medium priority')
|
|
99
|
+
print(f' 3. System level ({CONFIG_PATH_SYSTEM}) - Global system, lowest priority')
|
|
296
100
|
print()
|
|
297
101
|
|
|
298
102
|
choice_map = {
|
gitwebhooks/cli/serve.py
CHANGED
|
@@ -6,7 +6,6 @@ Provides server startup and systemd service management functionality.
|
|
|
6
6
|
import argparse
|
|
7
7
|
import sys
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Optional
|
|
10
9
|
|
|
11
10
|
from gitwebhooks.server import WebhookServer
|
|
12
11
|
from gitwebhooks.utils.exceptions import ConfigurationError
|
|
@@ -72,6 +71,10 @@ def start_server(args: argparse.Namespace) -> int:
|
|
|
72
71
|
# Get config path from args
|
|
73
72
|
config_file = getattr(args, 'config', None)
|
|
74
73
|
|
|
74
|
+
# Import here to avoid circular dependency (see gitwebhooks/cli/help.py).
|
|
75
|
+
# These live in gitwebhooks.main - do not re-implement them here.
|
|
76
|
+
from gitwebhooks.main import find_config_file, format_config_error
|
|
77
|
+
|
|
75
78
|
# Print version
|
|
76
79
|
print(f'gitwebhooks-cli {__version__}')
|
|
77
80
|
|
|
@@ -112,41 +115,3 @@ def start_server(args: argparse.Namespace) -> int:
|
|
|
112
115
|
print(f'Error: {e}', file=sys.stderr)
|
|
113
116
|
return 1
|
|
114
117
|
|
|
115
|
-
|
|
116
|
-
def find_config_file() -> Optional[str]:
|
|
117
|
-
"""Find configuration file by priority order.
|
|
118
|
-
|
|
119
|
-
Searches for configuration files in the following order:
|
|
120
|
-
1. User level: ~/.gitwebhooks.ini
|
|
121
|
-
2. Local level: /usr/local/etc/gitwebhooks.ini
|
|
122
|
-
3. System level: /etc/gitwebhooks.ini
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
Path string of the first existing configuration file, or None if not found.
|
|
126
|
-
"""
|
|
127
|
-
for config_path in CONFIG_SEARCH_PATHS:
|
|
128
|
-
expanded_path = Path(config_path).expanduser().resolve()
|
|
129
|
-
if expanded_path.exists():
|
|
130
|
-
return str(expanded_path)
|
|
131
|
-
return None
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def format_config_error(searched_paths: list) -> str:
|
|
135
|
-
"""Format user-friendly error message for missing configuration file.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
searched_paths: List of paths that were searched.
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Formatted error message string.
|
|
142
|
-
"""
|
|
143
|
-
lines = [
|
|
144
|
-
'Error: Configuration file not found.',
|
|
145
|
-
'Searched paths:'
|
|
146
|
-
]
|
|
147
|
-
for i, path in enumerate(searched_paths, 1):
|
|
148
|
-
lines.append(f' {i}. {path}')
|
|
149
|
-
lines.append('')
|
|
150
|
-
lines.append('You can create a configuration file using:')
|
|
151
|
-
lines.append(' gitwebhooks-cli config init')
|
|
152
|
-
return '\n'.join(lines)
|
gitwebhooks/cli/service.py
CHANGED
|
@@ -9,21 +9,23 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Optional
|
|
10
10
|
|
|
11
11
|
from gitwebhooks.utils.systemd import (
|
|
12
|
-
check_systemd,
|
|
13
12
|
check_root_permission,
|
|
14
13
|
generate_service_file,
|
|
15
14
|
get_service_path,
|
|
16
15
|
detect_installation_type,
|
|
17
16
|
InstallationType,
|
|
18
17
|
)
|
|
19
|
-
from gitwebhooks.utils.constants import
|
|
18
|
+
from gitwebhooks.utils.constants import (
|
|
19
|
+
CONFIG_PATH_LOCAL,
|
|
20
|
+
CONFIG_PATH_SYSTEM,
|
|
21
|
+
CONFIG_PATH_USER,
|
|
22
|
+
ConfigLevel,
|
|
23
|
+
)
|
|
20
24
|
from gitwebhooks.cli.prompts import ask_yes_no, ask_config_level
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
# Error codes
|
|
24
28
|
E_PERM = 1
|
|
25
|
-
E_SYSTEMD = 1
|
|
26
|
-
E_EXISTS = 1
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def install_service(
|
|
@@ -88,11 +90,11 @@ def install_service(
|
|
|
88
90
|
config_path = config_path_override
|
|
89
91
|
# Map path to config level for display purposes
|
|
90
92
|
expanded_path = Path(config_path).expanduser()
|
|
91
|
-
if str(expanded_path) == str(Path(
|
|
93
|
+
if str(expanded_path) == str(Path(CONFIG_PATH_USER).expanduser()):
|
|
92
94
|
config_level = ConfigLevel.USER
|
|
93
|
-
elif str(expanded_path) ==
|
|
95
|
+
elif str(expanded_path) == CONFIG_PATH_LOCAL:
|
|
94
96
|
config_level = ConfigLevel.LOCAL
|
|
95
|
-
elif str(expanded_path) ==
|
|
97
|
+
elif str(expanded_path) == CONFIG_PATH_SYSTEM:
|
|
96
98
|
config_level = ConfigLevel.SYSTEM
|
|
97
99
|
else:
|
|
98
100
|
# Custom path, default to USER for display
|
|
@@ -179,15 +181,6 @@ def check_service_exists() -> bool:
|
|
|
179
181
|
return get_service_path().exists()
|
|
180
182
|
|
|
181
183
|
|
|
182
|
-
def confirm_overwrite() -> bool:
|
|
183
|
-
"""Ask user to confirm overwrite
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
True if user confirms, False otherwise
|
|
187
|
-
"""
|
|
188
|
-
return ask_yes_no('Service already exists, overwrite? (y/N) ', default=False)
|
|
189
|
-
|
|
190
|
-
|
|
191
184
|
def enable_and_start_service() -> int:
|
|
192
185
|
"""Enable and start the service
|
|
193
186
|
|
|
@@ -338,7 +331,10 @@ def handle_config_cleanup(purge: bool) -> None:
|
|
|
338
331
|
Returns:
|
|
339
332
|
None
|
|
340
333
|
"""
|
|
341
|
-
|
|
334
|
+
# Single source of truth for the user-level config path (see constants.py).
|
|
335
|
+
# A hardcoded '~/.gitwebhook.ini' (missing "s") used to make
|
|
336
|
+
# `serve uninstall --purge` delete the wrong file - or nothing at all.
|
|
337
|
+
config_path = Path(CONFIG_PATH_USER).expanduser()
|
|
342
338
|
|
|
343
339
|
if not purge:
|
|
344
340
|
return
|
gitwebhooks/config/loader.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Dict, Optional
|
|
|
9
9
|
|
|
10
10
|
from gitwebhooks.config.models import ProviderConfig, RepositoryConfig
|
|
11
11
|
from gitwebhooks.models.provider import Provider
|
|
12
|
+
from gitwebhooks.utils.constants import RESERVED_SECTIONS
|
|
12
13
|
from gitwebhooks.utils.exceptions import ConfigurationError
|
|
13
14
|
|
|
14
15
|
|
|
@@ -122,7 +123,7 @@ class ConfigLoader:
|
|
|
122
123
|
Returns:
|
|
123
124
|
Set of reserved section names
|
|
124
125
|
"""
|
|
125
|
-
return
|
|
126
|
+
return RESERVED_SECTIONS
|
|
126
127
|
|
|
127
128
|
def get_server_config(self) -> Dict[str, any]:
|
|
128
129
|
"""Get server configuration
|
|
@@ -133,9 +134,16 @@ class ConfigLoader:
|
|
|
133
134
|
- port: Listen port
|
|
134
135
|
- log_file: Log file path
|
|
135
136
|
"""
|
|
137
|
+
try:
|
|
138
|
+
port = self.parser.getint('server', 'port', fallback=6789)
|
|
139
|
+
except ValueError as e:
|
|
140
|
+
raise ConfigurationError(
|
|
141
|
+
f"Invalid [server] port: {self.parser.get('server', 'port')!r} is not an integer"
|
|
142
|
+
) from e
|
|
143
|
+
|
|
136
144
|
return {
|
|
137
145
|
'address': self.parser.get('server', 'address', fallback='0.0.0.0'),
|
|
138
|
-
'port':
|
|
146
|
+
'port': port,
|
|
139
147
|
'log_file': self.parser.get('server', 'log_file', fallback='').strip(),
|
|
140
148
|
}
|
|
141
149
|
|
|
@@ -150,16 +158,26 @@ class ConfigLoader:
|
|
|
150
158
|
- cert_file: SSL certificate file path
|
|
151
159
|
"""
|
|
152
160
|
try:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
except (configparser.NoSectionError, ValueError):
|
|
161
|
+
ssl_enabled = self.parser.getboolean('ssl', 'enable')
|
|
162
|
+
except configparser.NoSectionError:
|
|
156
163
|
return None
|
|
164
|
+
except ValueError as e:
|
|
165
|
+
raise ConfigurationError(
|
|
166
|
+
f"Invalid [ssl] enable: {self.parser.get('ssl', 'enable')!r} is not a boolean"
|
|
167
|
+
) from e
|
|
157
168
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
if not ssl_enabled:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
ssl_config = {'enable': True}
|
|
173
|
+
for option in ('key_file', 'cert_file'):
|
|
174
|
+
value = self.parser.get('ssl', option, fallback='').strip()
|
|
175
|
+
if not value:
|
|
176
|
+
raise ConfigurationError(
|
|
177
|
+
f"SSL is enabled but '{option}' is missing in the [ssl] section"
|
|
178
|
+
)
|
|
179
|
+
ssl_config[option] = value
|
|
180
|
+
return ssl_config
|
|
163
181
|
|
|
164
182
|
def validate_repository_configs(self) -> None:
|
|
165
183
|
"""Validate all repository configurations
|
gitwebhooks/config/models.py
CHANGED
gitwebhooks/config/registry.py
CHANGED
|
@@ -64,41 +64,3 @@ class ConfigurationRegistry:
|
|
|
64
64
|
if self._repository_configs is None:
|
|
65
65
|
raise ConfigurationError('Repository configs not loaded')
|
|
66
66
|
return self._repository_configs
|
|
67
|
-
|
|
68
|
-
def get_provider_config(self, provider: Provider) -> ProviderConfig:
|
|
69
|
-
"""Get configuration for specified provider
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
provider: Provider type
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
ProviderConfig instance
|
|
76
|
-
"""
|
|
77
|
-
return self.provider_configs.get(provider, ProviderConfig(
|
|
78
|
-
provider=provider,
|
|
79
|
-
verify=False,
|
|
80
|
-
secret='',
|
|
81
|
-
handle_events=[]
|
|
82
|
-
))
|
|
83
|
-
|
|
84
|
-
def get_repository_config(self, name: str) -> RepositoryConfig:
|
|
85
|
-
"""Get configuration for specified repository
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
name: Repository name
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
RepositoryConfig instance, or None if not found
|
|
92
|
-
"""
|
|
93
|
-
return self.repository_configs.get(name)
|
|
94
|
-
|
|
95
|
-
def has_repository(self, name: str) -> bool:
|
|
96
|
-
"""Check if repository exists
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
name: Repository name
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
True if exists, False otherwise
|
|
103
|
-
"""
|
|
104
|
-
return name in self.repository_configs
|
gitwebhooks/config/server.py
CHANGED
gitwebhooks/handlers/base.py
CHANGED
|
@@ -6,7 +6,6 @@ Defines the abstract interface for webhook handlers.
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from typing import Optional
|
|
8
8
|
|
|
9
|
-
from gitwebhooks.models.provider import Provider
|
|
10
9
|
from gitwebhooks.models.request import WebhookRequest
|
|
11
10
|
from gitwebhooks.models.result import SignatureVerificationResult
|
|
12
11
|
from gitwebhooks.config.models import ProviderConfig
|
|
@@ -19,15 +18,6 @@ class WebhookHandler(ABC):
|
|
|
19
18
|
All platform-specific handlers must inherit from this class and implement all abstract methods.
|
|
20
19
|
"""
|
|
21
20
|
|
|
22
|
-
@abstractmethod
|
|
23
|
-
def get_provider(self) -> Provider:
|
|
24
|
-
"""Return the provider type supported by this handler
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
Provider enum value
|
|
28
|
-
"""
|
|
29
|
-
pass
|
|
30
|
-
|
|
31
21
|
@abstractmethod
|
|
32
22
|
def verify_signature(self, request: WebhookRequest,
|
|
33
23
|
config: ProviderConfig) -> SignatureVerificationResult:
|