gitwebhooks 2.4.0__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__.py +31 -7
- gitwebhooks/cli/help.py +27 -96
- gitwebhooks/cli/init_wizard.py +13 -7
- 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/__init__.py +30 -17
- 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.0.dist-info → gitwebhooks-2.4.2.dist-info}/METADATA +37 -27
- gitwebhooks-2.4.2.dist-info/RECORD +51 -0
- {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/WHEEL +1 -1
- gitwebhooks-2.4.0.dist-info/RECORD +0 -51
- {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/entry_points.txt +0 -0
- {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/licenses/LICENSE +0 -0
- {gitwebhooks-2.4.0.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__.py
CHANGED
|
@@ -7,6 +7,22 @@ from gitwebhooks.cli.config import cmd_init, cmd_view
|
|
|
7
7
|
from gitwebhooks.cli.help import cmd_help
|
|
8
8
|
from gitwebhooks.cli.serve import cmd_serve
|
|
9
9
|
|
|
10
|
+
# Top-level subparsers keyed by name, so the `help` subcommand can render
|
|
11
|
+
# exactly the same text argparse prints for `<subcommand> --help`.
|
|
12
|
+
_SUBPARSERS = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_subparser(topic):
|
|
16
|
+
"""Get a registered top-level subparser by name.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
topic: Subcommand name ('serve' or 'config')
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The ArgumentParser for that subcommand, or None if unknown
|
|
23
|
+
"""
|
|
24
|
+
return _SUBPARSERS.get(topic)
|
|
25
|
+
|
|
10
26
|
|
|
11
27
|
def register_subparsers(subparsers):
|
|
12
28
|
"""Register all CLI subparsers
|
|
@@ -38,9 +54,9 @@ def register_serve_subparser(subparsers):
|
|
|
38
54
|
"""
|
|
39
55
|
serve_parser = subparsers.add_parser(
|
|
40
56
|
'serve',
|
|
41
|
-
add_help=False,
|
|
42
57
|
help='Start webhook server or manage systemd service'
|
|
43
58
|
)
|
|
59
|
+
_SUBPARSERS['serve'] = serve_parser
|
|
44
60
|
serve_parser.add_argument(
|
|
45
61
|
'-c', '--config',
|
|
46
62
|
dest='config',
|
|
@@ -49,7 +65,10 @@ def register_serve_subparser(subparsers):
|
|
|
49
65
|
serve_subparsers = serve_parser.add_subparsers(dest='serve_action')
|
|
50
66
|
|
|
51
67
|
# install action
|
|
52
|
-
install_parser = serve_subparsers.add_parser(
|
|
68
|
+
install_parser = serve_subparsers.add_parser(
|
|
69
|
+
'install',
|
|
70
|
+
help='Install systemd service'
|
|
71
|
+
)
|
|
53
72
|
install_parser.add_argument(
|
|
54
73
|
'--force',
|
|
55
74
|
action='store_true',
|
|
@@ -74,7 +93,10 @@ def register_serve_subparser(subparsers):
|
|
|
74
93
|
install_parser.set_defaults(func=cmd_serve)
|
|
75
94
|
|
|
76
95
|
# uninstall action
|
|
77
|
-
uninstall_parser = serve_subparsers.add_parser(
|
|
96
|
+
uninstall_parser = serve_subparsers.add_parser(
|
|
97
|
+
'uninstall',
|
|
98
|
+
help='Uninstall systemd service'
|
|
99
|
+
)
|
|
78
100
|
uninstall_parser.add_argument(
|
|
79
101
|
'--purge',
|
|
80
102
|
action='store_true',
|
|
@@ -97,7 +119,7 @@ def register_help_subparser(subparsers):
|
|
|
97
119
|
"""
|
|
98
120
|
help_parser = subparsers.add_parser(
|
|
99
121
|
'help',
|
|
100
|
-
|
|
122
|
+
help='Show help information'
|
|
101
123
|
)
|
|
102
124
|
help_parser.add_argument(
|
|
103
125
|
'topic',
|
|
@@ -118,15 +140,14 @@ def register_config_subparser(subparsers):
|
|
|
118
140
|
"""
|
|
119
141
|
config_parser = subparsers.add_parser(
|
|
120
142
|
'config',
|
|
121
|
-
add_help=False,
|
|
122
143
|
help='Manage gitwebhooks configuration'
|
|
123
144
|
)
|
|
145
|
+
_SUBPARSERS['config'] = config_parser
|
|
124
146
|
config_subparsers = config_parser.add_subparsers(dest='config_action')
|
|
125
147
|
|
|
126
148
|
# init action
|
|
127
149
|
init_parser = config_subparsers.add_parser(
|
|
128
150
|
'init',
|
|
129
|
-
add_help=False,
|
|
130
151
|
help='Initialize configuration file using interactive wizard'
|
|
131
152
|
)
|
|
132
153
|
init_parser.add_argument(
|
|
@@ -138,7 +159,10 @@ def register_config_subparser(subparsers):
|
|
|
138
159
|
init_parser.set_defaults(func=cmd_init)
|
|
139
160
|
|
|
140
161
|
# view action
|
|
141
|
-
view_parser = config_subparsers.add_parser(
|
|
162
|
+
view_parser = config_subparsers.add_parser(
|
|
163
|
+
'view',
|
|
164
|
+
help='View configuration file content'
|
|
165
|
+
)
|
|
142
166
|
view_parser.add_argument(
|
|
143
167
|
'-c',
|
|
144
168
|
'--config',
|
gitwebhooks/cli/help.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"""Help command module
|
|
2
2
|
|
|
3
3
|
Provides help information display functionality for gitwebhooks-cli.
|
|
4
|
+
|
|
5
|
+
All help output is generated by argparse from the parser definitions, so
|
|
6
|
+
``<command> -h``/``--help`` and ``gitwebhooks-cli help <command>`` always
|
|
7
|
+
show exactly the same text.
|
|
4
8
|
"""
|
|
5
9
|
|
|
6
10
|
import argparse
|
|
@@ -17,7 +21,7 @@ def cmd_help(args: argparse.Namespace) -> int:
|
|
|
17
21
|
- topic: Optional subcommand name to show help for
|
|
18
22
|
|
|
19
23
|
Returns:
|
|
20
|
-
Exit code (0 = success)
|
|
24
|
+
Exit code (0 = success, 1 = unknown topic)
|
|
21
25
|
|
|
22
26
|
Examples:
|
|
23
27
|
>>> args = argparse.Namespace(topic=None)
|
|
@@ -29,16 +33,23 @@ def cmd_help(args: argparse.Namespace) -> int:
|
|
|
29
33
|
"""
|
|
30
34
|
# Import here to avoid circular dependency
|
|
31
35
|
from gitwebhooks.main import get_parser
|
|
36
|
+
from gitwebhooks.cli import get_subparser
|
|
32
37
|
|
|
33
38
|
parser = get_parser()
|
|
39
|
+
topic = getattr(args, 'topic', None)
|
|
34
40
|
|
|
35
|
-
if
|
|
36
|
-
# Show subcommand help
|
|
37
|
-
show_subcommand_help(parser, args.topic)
|
|
38
|
-
else:
|
|
39
|
-
# Show main help
|
|
41
|
+
if not topic:
|
|
40
42
|
show_main_help(parser)
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
if get_subparser(topic) is None:
|
|
46
|
+
print(f"Error: Unknown command '{topic}'", file=sys.stderr)
|
|
47
|
+
print("Available commands: serve, config", file=sys.stderr)
|
|
48
|
+
print()
|
|
49
|
+
print("Use 'gitwebhooks-cli help' to see main help", file=sys.stderr)
|
|
50
|
+
return 1
|
|
41
51
|
|
|
52
|
+
show_subcommand_help(parser, topic)
|
|
42
53
|
return 0
|
|
43
54
|
|
|
44
55
|
|
|
@@ -51,105 +62,25 @@ def show_main_help(parser: argparse.ArgumentParser) -> None:
|
|
|
51
62
|
Returns:
|
|
52
63
|
None
|
|
53
64
|
"""
|
|
54
|
-
|
|
55
|
-
help_text = """Git Webhooks Server - Automated deployment webhook handler
|
|
56
|
-
|
|
57
|
-
Usage:
|
|
58
|
-
gitwebhooks-cli [-v] [-c CONFIG] <command> [<args>]
|
|
59
|
-
|
|
60
|
-
Commands:
|
|
61
|
-
serve Start webhook server or manage systemd service
|
|
62
|
-
config Manage configuration file
|
|
63
|
-
help Show help information
|
|
64
|
-
|
|
65
|
-
Options:
|
|
66
|
-
-v, --version Show version information
|
|
67
|
-
-c, --config Path to configuration file (default: auto-discover)
|
|
68
|
-
|
|
69
|
-
Examples:
|
|
70
|
-
gitwebhooks-cli serve # Start server
|
|
71
|
-
gitwebhooks-cli serve install # Install systemd service
|
|
72
|
-
gitwebhooks-cli config init # Initialize configuration
|
|
73
|
-
gitwebhooks-cli help serve # Show serve command help
|
|
74
|
-
|
|
75
|
-
For more information on a specific command:
|
|
76
|
-
gitwebhooks-cli help <command>
|
|
77
|
-
"""
|
|
78
|
-
print(help_text)
|
|
65
|
+
print(parser.format_help(), end='')
|
|
79
66
|
|
|
80
67
|
|
|
81
68
|
def show_subcommand_help(parser: argparse.ArgumentParser, topic: str) -> None:
|
|
82
69
|
"""Display help for a specific subcommand.
|
|
83
70
|
|
|
84
71
|
Args:
|
|
85
|
-
parser: The main argument parser
|
|
72
|
+
parser: The main argument parser (unused, kept for call compatibility)
|
|
86
73
|
topic: Subcommand name
|
|
87
74
|
|
|
88
75
|
Returns:
|
|
89
76
|
None
|
|
90
77
|
"""
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
Usage:
|
|
97
|
-
gitwebhooks-cli serve [-c CONFIG]
|
|
98
|
-
gitwebhooks-cli serve install [--force] [--dry-run] [--verbose]
|
|
99
|
-
gitwebhooks-cli serve uninstall [--purge]
|
|
100
|
-
|
|
101
|
-
Subcommands:
|
|
102
|
-
(none) Start the webhook server
|
|
103
|
-
install Install systemd service
|
|
104
|
-
uninstall Uninstall systemd service
|
|
105
|
-
|
|
106
|
-
Options:
|
|
107
|
-
-c, --config Path to configuration file
|
|
108
|
-
|
|
109
|
-
Install options:
|
|
110
|
-
--force Force overwrite existing service
|
|
111
|
-
--dry-run Preview mode, don't make changes
|
|
112
|
-
--verbose Increase verbosity
|
|
113
|
-
|
|
114
|
-
Uninstall options:
|
|
115
|
-
--purge Also remove configuration file
|
|
116
|
-
|
|
117
|
-
Examples:
|
|
118
|
-
gitwebhooks-cli serve # Start server
|
|
119
|
-
gitwebhooks-cli serve -c /etc/webhooks.ini # Start with custom config
|
|
120
|
-
gitwebhooks-cli serve install # Install service
|
|
121
|
-
gitwebhooks-cli serve install --force # Force overwrite
|
|
122
|
-
gitwebhooks-cli serve uninstall # Uninstall service
|
|
123
|
-
""",
|
|
124
|
-
'config': """Command: config
|
|
125
|
-
Manage configuration file
|
|
126
|
-
|
|
127
|
-
Usage:
|
|
128
|
-
gitwebhooks-cli config init [level]
|
|
129
|
-
gitwebhooks-cli config view [-c CONFIG]
|
|
130
|
-
|
|
131
|
-
Subcommands:
|
|
132
|
-
init Initialize configuration file
|
|
133
|
-
view View configuration file
|
|
134
|
-
|
|
135
|
-
Init arguments:
|
|
136
|
-
level Configuration level (system, local, user)
|
|
137
|
-
|
|
138
|
-
View options:
|
|
139
|
-
-c, --config Path to configuration file
|
|
140
|
-
|
|
141
|
-
Examples:
|
|
142
|
-
gitwebhooks-cli config init # Interactive init
|
|
143
|
-
gitwebhooks-cli config init user # User-level config
|
|
144
|
-
gitwebhooks-cli config init system # System-level config (requires sudo)
|
|
145
|
-
gitwebhooks-cli config view # View current config
|
|
146
|
-
""",
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if topic in help_texts:
|
|
150
|
-
print(help_texts[topic])
|
|
151
|
-
else:
|
|
78
|
+
# Import here to avoid circular dependency
|
|
79
|
+
from gitwebhooks.cli import get_subparser
|
|
80
|
+
|
|
81
|
+
subparser = get_subparser(topic)
|
|
82
|
+
if subparser is None:
|
|
152
83
|
print(f"Error: Unknown command '{topic}'", file=sys.stderr)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
print(subparser.format_help(), end='')
|
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
|
|
|
@@ -760,7 +766,7 @@ class Wizard:
|
|
|
760
766
|
print("后续步骤:")
|
|
761
767
|
print(f" 1. 检查配置文件内容: gitwebhooks-cli config view {path}")
|
|
762
768
|
print(f" 2. 启动服务器: gitwebhooks-cli -c {path}")
|
|
763
|
-
print(" 3. 或安装为服务: sudo gitwebhooks-cli
|
|
769
|
+
print(" 3. 或安装为服务: sudo gitwebhooks-cli serve install")
|
|
764
770
|
|
|
765
771
|
def _cleanup(self) -> None:
|
|
766
772
|
"""
|
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 = {
|