gitwebhooks 2.1.0__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 +18 -0
- gitwebhooks/__main__.py +10 -0
- gitwebhooks/auth/__init__.py +20 -0
- gitwebhooks/auth/custom.py +40 -0
- gitwebhooks/auth/factory.py +73 -0
- gitwebhooks/auth/gitee.py +86 -0
- gitwebhooks/auth/github.py +52 -0
- gitwebhooks/auth/gitlab.py +29 -0
- gitwebhooks/auth/verifier.py +34 -0
- gitwebhooks/cli/__init__.py +107 -0
- gitwebhooks/cli/config.py +216 -0
- gitwebhooks/cli/init_wizard.py +701 -0
- gitwebhooks/cli/prompts.py +275 -0
- gitwebhooks/cli/service.py +374 -0
- gitwebhooks/config/__init__.py +17 -0
- gitwebhooks/config/loader.py +170 -0
- gitwebhooks/config/models.py +145 -0
- gitwebhooks/config/registry.py +104 -0
- gitwebhooks/config/server.py +85 -0
- gitwebhooks/handlers/__init__.py +22 -0
- gitwebhooks/handlers/base.py +107 -0
- gitwebhooks/handlers/custom.py +73 -0
- gitwebhooks/handlers/factory.py +83 -0
- gitwebhooks/handlers/gitee.py +113 -0
- gitwebhooks/handlers/github.py +43 -0
- gitwebhooks/handlers/gitlab.py +43 -0
- gitwebhooks/handlers/request.py +248 -0
- gitwebhooks/logging/__init__.py +10 -0
- gitwebhooks/logging/setup.py +57 -0
- gitwebhooks/main/__init__.py +117 -0
- gitwebhooks/main/__main__.py +12 -0
- gitwebhooks/models/__init__.py +14 -0
- gitwebhooks/models/provider.py +22 -0
- gitwebhooks/models/request.py +61 -0
- gitwebhooks/models/result.py +40 -0
- gitwebhooks/server.py +128 -0
- gitwebhooks/utils/__init__.py +14 -0
- gitwebhooks/utils/constants.py +55 -0
- gitwebhooks/utils/exceptions.py +34 -0
- gitwebhooks/utils/executor.py +34 -0
- gitwebhooks/utils/systemd.py +274 -0
- gitwebhooks-2.1.0.dist-info/METADATA +494 -0
- gitwebhooks-2.1.0.dist-info/RECORD +47 -0
- gitwebhooks-2.1.0.dist-info/WHEEL +5 -0
- gitwebhooks-2.1.0.dist-info/entry_points.txt +2 -0
- gitwebhooks-2.1.0.dist-info/licenses/LICENSE +21 -0
- gitwebhooks-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""CLI main function
|
|
2
|
+
|
|
3
|
+
Command-line interface module providing compatibility with the original git-webhooks-server.py.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
from gitwebhooks.server import WebhookServer
|
|
12
|
+
from gitwebhooks.utils.exceptions import ConfigurationError
|
|
13
|
+
|
|
14
|
+
# Try to import subcommands, may not be available in older versions
|
|
15
|
+
try:
|
|
16
|
+
from gitwebhooks.cli import register_subparsers
|
|
17
|
+
HAS_SUBCOMMANDS = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
HAS_SUBCOMMANDS = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(argv: List[str] = None) -> int:
|
|
23
|
+
"""Main entry point
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
argv: Command-line argument list (defaults to sys.argv[1:])
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Exit code (0 = success, 1 = error)
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
SystemExit: Exit on error
|
|
33
|
+
|
|
34
|
+
Command Line Arguments:
|
|
35
|
+
-h, --help Show help message and exit
|
|
36
|
+
-c, --config Specify configuration file path
|
|
37
|
+
service Service management subcommands
|
|
38
|
+
config Configuration management subcommands
|
|
39
|
+
|
|
40
|
+
Default Config Path:
|
|
41
|
+
~/.gitwebhook.ini
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
python3 -m gitwebhooks.main -c /path/to/config.ini
|
|
45
|
+
gitwebhooks-cli --config /etc/webhooks.ini
|
|
46
|
+
gitwebhooks-cli service install
|
|
47
|
+
gitwebhooks-cli config init
|
|
48
|
+
"""
|
|
49
|
+
if argv is None:
|
|
50
|
+
argv = sys.argv[1:]
|
|
51
|
+
|
|
52
|
+
# Default configuration file path (changed to user home directory)
|
|
53
|
+
default_config = '~/.gitwebhook.ini'
|
|
54
|
+
|
|
55
|
+
# Create main argument parser
|
|
56
|
+
parser = argparse.ArgumentParser(
|
|
57
|
+
prog='gitwebhooks-cli',
|
|
58
|
+
description='Git Webhooks Server - Automated deployment webhook handler',
|
|
59
|
+
add_help=True
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
'-c', '--config',
|
|
63
|
+
default=default_config,
|
|
64
|
+
help='Path to INI configuration file (default: ~/.gitwebhook.ini)'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Add subcommands if available
|
|
68
|
+
if HAS_SUBCOMMANDS:
|
|
69
|
+
subparsers = parser.add_subparsers(dest='command', help='Subcommands')
|
|
70
|
+
register_subparsers(subparsers)
|
|
71
|
+
|
|
72
|
+
# Parse arguments
|
|
73
|
+
args = parser.parse_args(argv)
|
|
74
|
+
|
|
75
|
+
# If no subcommand, run server (backward compatible behavior)
|
|
76
|
+
if not hasattr(args, 'func'):
|
|
77
|
+
return run_server(args.config)
|
|
78
|
+
|
|
79
|
+
# Execute subcommand
|
|
80
|
+
return args.func(args)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_server(config_file: str) -> int:
|
|
84
|
+
"""Run the webhook server
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
config_file: Path to configuration file
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Exit code (0 = success, 1 = error)
|
|
91
|
+
"""
|
|
92
|
+
# Expand user path
|
|
93
|
+
config_path = Path(config_file).expanduser()
|
|
94
|
+
|
|
95
|
+
# Check configuration file exists
|
|
96
|
+
if not config_path.exists():
|
|
97
|
+
print(f'Error: Configuration file not found: {config_path}', file=sys.stderr)
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
# Create and run server
|
|
101
|
+
try:
|
|
102
|
+
server = WebhookServer(config_path=str(config_path))
|
|
103
|
+
server.run()
|
|
104
|
+
return 0
|
|
105
|
+
except ConfigurationError as e:
|
|
106
|
+
print(f'Configuration error: {e}', file=sys.stderr)
|
|
107
|
+
return 1
|
|
108
|
+
except KeyboardInterrupt:
|
|
109
|
+
print('\nServer stopped by user')
|
|
110
|
+
return 0
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f'Error: {e}', file=sys.stderr)
|
|
113
|
+
return 1
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == '__main__':
|
|
117
|
+
sys.exit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Data models module
|
|
2
|
+
|
|
3
|
+
Defines core data types and enumerations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .provider import Provider
|
|
7
|
+
from .request import WebhookRequest
|
|
8
|
+
from .result import SignatureVerificationResult
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'Provider',
|
|
12
|
+
'WebhookRequest',
|
|
13
|
+
'SignatureVerificationResult',
|
|
14
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Git platform provider enumeration
|
|
2
|
+
|
|
3
|
+
Defines supported Git hosting platform types.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import Enum, unique
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@unique
|
|
10
|
+
class Provider(Enum):
|
|
11
|
+
"""Git platform provider type
|
|
12
|
+
|
|
13
|
+
Supported Git hosting platforms:
|
|
14
|
+
- GITHUB: GitHub platform
|
|
15
|
+
- GITEE: Gitee platform (China)
|
|
16
|
+
- GITLAB: GitLab platform
|
|
17
|
+
- CUSTOM: Custom webhook platform
|
|
18
|
+
"""
|
|
19
|
+
GITHUB = 'github'
|
|
20
|
+
GITEE = 'gitee'
|
|
21
|
+
GITLAB = 'gitlab'
|
|
22
|
+
CUSTOM = 'custom'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Webhook request data wrapper
|
|
2
|
+
|
|
3
|
+
Represents a parsed HTTP webhook request.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
from gitwebhooks.models.provider import Provider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class WebhookRequest:
|
|
14
|
+
"""Webhook request data wrapper
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
provider: Git platform provider
|
|
18
|
+
event: Event type (e.g., 'push', 'merge_request')
|
|
19
|
+
payload: Raw request body bytes
|
|
20
|
+
headers: HTTP request headers
|
|
21
|
+
post_data: Parsed JSON/form data
|
|
22
|
+
content_type: Content-Type header value
|
|
23
|
+
content_length: Content-Length header value
|
|
24
|
+
"""
|
|
25
|
+
provider: Provider
|
|
26
|
+
event: Optional[str]
|
|
27
|
+
payload: bytes
|
|
28
|
+
headers: Dict[str, str]
|
|
29
|
+
post_data: Optional[Dict[str, Any]]
|
|
30
|
+
content_type: str
|
|
31
|
+
content_length: int
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def repo_identifier(self) -> Optional[str]:
|
|
35
|
+
"""Extract repository identifier from post_data
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Repository identifier (e.g., 'owner/repo'), None if not found
|
|
39
|
+
|
|
40
|
+
Extraction rules:
|
|
41
|
+
- GitHub: repository.full_name
|
|
42
|
+
- Gitee: repository.full_name
|
|
43
|
+
- GitLab: project.path_with_namespace
|
|
44
|
+
- Custom: Uses ProviderConfig.identifier_path
|
|
45
|
+
"""
|
|
46
|
+
if self.post_data is None:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
extractors = {
|
|
50
|
+
Provider.GITHUB: lambda d: d.get('repository', {}).get('full_name'),
|
|
51
|
+
Provider.GITEE: lambda d: d.get('repository', {}).get('full_name'),
|
|
52
|
+
Provider.GITLAB: lambda d: d.get('project', {}).get('path_with_namespace'),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
extractor = extractors.get(self.provider)
|
|
56
|
+
if extractor:
|
|
57
|
+
result = extractor(self.post_data)
|
|
58
|
+
return result if isinstance(result, str) else None
|
|
59
|
+
|
|
60
|
+
# Custom provider uses identifier_path (handled in handler)
|
|
61
|
+
return None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Signature verification result dataclass
|
|
2
|
+
|
|
3
|
+
Represents the result status of signature verification.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class SignatureVerificationResult:
|
|
12
|
+
"""Signature verification result
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
is_valid: Whether verification passed
|
|
16
|
+
error_message: Error description when verification fails
|
|
17
|
+
"""
|
|
18
|
+
is_valid: bool
|
|
19
|
+
error_message: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def success(cls) -> 'SignatureVerificationResult':
|
|
23
|
+
"""Create a successful verification result
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Result object with is_valid=True
|
|
27
|
+
"""
|
|
28
|
+
return cls(is_valid=True)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def failure(cls, message: str) -> 'SignatureVerificationResult':
|
|
32
|
+
"""Create a failed verification result
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
message: Error description
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Result object with is_valid=False and error message
|
|
39
|
+
"""
|
|
40
|
+
return cls(is_valid=False, error_message=message)
|
gitwebhooks/server.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""HTTP server main logic
|
|
2
|
+
|
|
3
|
+
Creates and runs the HTTP server, handles webhook requests.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import ssl
|
|
8
|
+
import sys
|
|
9
|
+
from http.server import HTTPServer
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from gitwebhooks.config.loader import ConfigLoader
|
|
13
|
+
from gitwebhooks.config.registry import ConfigurationRegistry
|
|
14
|
+
from gitwebhooks.config.server import ServerConfig
|
|
15
|
+
from gitwebhooks.handlers.request import WebhookRequestHandler
|
|
16
|
+
from gitwebhooks.logging.setup import setup_logging
|
|
17
|
+
from gitwebhooks.utils.exceptions import ConfigurationError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WebhookServer:
|
|
21
|
+
"""Git Webhooks server main class
|
|
22
|
+
|
|
23
|
+
Responsible for creating and running the HTTP server, handling webhook requests.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config_path: str,
|
|
27
|
+
registry: Optional[ConfigurationRegistry] = None):
|
|
28
|
+
"""Initialize Webhook server
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config_path: INI configuration file path
|
|
32
|
+
registry: Configuration registry (optional, for dependency injection)
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ConfigurationError: Invalid or missing configuration
|
|
36
|
+
"""
|
|
37
|
+
self.config_path = config_path
|
|
38
|
+
self.loader = ConfigLoader(config_path)
|
|
39
|
+
self.registry = registry or ConfigurationRegistry(self.loader)
|
|
40
|
+
self.server_config = self.registry.server_config
|
|
41
|
+
|
|
42
|
+
# Configure logging
|
|
43
|
+
self._setup_logging()
|
|
44
|
+
|
|
45
|
+
def _setup_logging(self) -> None:
|
|
46
|
+
"""Configure logging system
|
|
47
|
+
|
|
48
|
+
Sets up logging based on server configuration:
|
|
49
|
+
- If log_file is empty, output to stdout only
|
|
50
|
+
- If log_file is non-empty, output to both file and stdout
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ConfigurationError: Log file cannot be created
|
|
54
|
+
"""
|
|
55
|
+
setup_logging(self.server_config.log_file)
|
|
56
|
+
|
|
57
|
+
def create_http_server(self) -> HTTPServer:
|
|
58
|
+
"""Create HTTP server instance
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Configured HTTPServer instance
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ConfigurationError: Invalid server configuration
|
|
65
|
+
OSError: Failed to bind address
|
|
66
|
+
"""
|
|
67
|
+
# Configure handler class (inject dependencies)
|
|
68
|
+
WebhookRequestHandler.configure(
|
|
69
|
+
self.registry.provider_configs,
|
|
70
|
+
self.registry.repository_configs
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Create server
|
|
74
|
+
server = HTTPServer(
|
|
75
|
+
(self.server_config.address, self.server_config.port),
|
|
76
|
+
WebhookRequestHandler
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Configure SSL (if enabled)
|
|
80
|
+
if self.server_config.ssl_enabled:
|
|
81
|
+
server.socket = self._wrap_socket_ssl(server.socket)
|
|
82
|
+
|
|
83
|
+
return server
|
|
84
|
+
|
|
85
|
+
def _wrap_socket_ssl(self, socket):
|
|
86
|
+
"""Wrap socket with SSL
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
socket: Raw socket
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
SSL-wrapped socket
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ConfigurationError: Invalid SSL configuration
|
|
96
|
+
ssl.SSLError: SSL initialization failed
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
100
|
+
context.load_cert_chain(
|
|
101
|
+
certfile=self.server_config.ssl_cert_file,
|
|
102
|
+
keyfile=self.server_config.ssl_key_file
|
|
103
|
+
)
|
|
104
|
+
return context.wrap_socket(socket, server_side=True)
|
|
105
|
+
except (ssl.SSLError, OSError) as e:
|
|
106
|
+
raise ConfigurationError(f'SSL configuration error: {e}')
|
|
107
|
+
|
|
108
|
+
def run(self) -> None:
|
|
109
|
+
"""Start server main loop
|
|
110
|
+
|
|
111
|
+
This method blocks until the server is stopped.
|
|
112
|
+
"""
|
|
113
|
+
server = self.create_http_server()
|
|
114
|
+
|
|
115
|
+
ssl_message = ' with SSL' if self.server_config.ssl_enabled else ''
|
|
116
|
+
logging.info('Serving on %s port %d%s...',
|
|
117
|
+
self.server_config.address,
|
|
118
|
+
self.server_config.port,
|
|
119
|
+
ssl_message)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
server.serve_forever()
|
|
123
|
+
except KeyboardInterrupt:
|
|
124
|
+
logging.info('Server stopped by user')
|
|
125
|
+
server.shutdown()
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logging.error('Server error: %s', e)
|
|
128
|
+
raise
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""HTTP and response constants definition
|
|
2
|
+
|
|
3
|
+
Defines all constants used in the project.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# HTTP Status Codes
|
|
7
|
+
HTTP_OK = 200
|
|
8
|
+
HTTP_BAD_REQUEST = 400
|
|
9
|
+
HTTP_UNAUTHORIZED = 401
|
|
10
|
+
HTTP_FORBIDDEN = 403
|
|
11
|
+
HTTP_NOT_FOUND = 404
|
|
12
|
+
HTTP_NOT_ACCEPTABLE = 406
|
|
13
|
+
HTTP_PRECONDITION_FAILED = 412
|
|
14
|
+
HTTP_INTERNAL_SERVER_ERROR = 500
|
|
15
|
+
|
|
16
|
+
# HTTP Content Types
|
|
17
|
+
CONTENT_TYPE_JSON = 'application/json'
|
|
18
|
+
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
|
19
|
+
|
|
20
|
+
# HTTP Headers
|
|
21
|
+
HEADER_CONTENT_TYPE = 'Content-Type'
|
|
22
|
+
HEADER_CONTENT_LENGTH = 'Content-Length'
|
|
23
|
+
HEADER_GITHUB_EVENT = 'X-GitHub-Event'
|
|
24
|
+
HEADER_GITHUB_SIGNATURE = 'X-Hub-Signature'
|
|
25
|
+
HEADER_GITEE_EVENT = 'X-Gitee-Event'
|
|
26
|
+
HEADER_GITEE_TOKEN = 'X-Gitee-Token'
|
|
27
|
+
HEADER_GITEE_TIMESTAMP = 'X-Gitee-Timestamp'
|
|
28
|
+
HEADER_GITLAB_EVENT = 'X-Gitlab-Event'
|
|
29
|
+
HEADER_GITLAB_TOKEN = 'X-Gitlab-Token'
|
|
30
|
+
|
|
31
|
+
# Response Messages
|
|
32
|
+
MESSAGE_OK = b'OK'
|
|
33
|
+
MESSAGE_FORBIDDEN = 'Forbidden'
|
|
34
|
+
MESSAGE_BAD_REQUEST = 'Bad Request'
|
|
35
|
+
MESSAGE_UNAUTHORIZED = 'Unauthorized'
|
|
36
|
+
MESSAGE_NOT_FOUND = 'Not Found'
|
|
37
|
+
MESSAGE_NOT_ACCEPTABLE = 'Not Acceptable'
|
|
38
|
+
MESSAGE_PRECONDITION_FAILED = 'Precondition Failed'
|
|
39
|
+
MESSAGE_INTERNAL_SERVER_ERROR = 'Internal Server Error'
|
|
40
|
+
|
|
41
|
+
# Reserved configuration sections
|
|
42
|
+
RESERVED_SECTIONS = {'server', 'ssl', 'github', 'gitee', 'gitlab', 'custom'}
|
|
43
|
+
|
|
44
|
+
# Configuration file paths (in priority order)
|
|
45
|
+
CONFIG_PATH_USER = "~/.gitwebhooks.ini"
|
|
46
|
+
CONFIG_PATH_LOCAL = "/usr/local/etc/gitwebhooks.ini"
|
|
47
|
+
CONFIG_PATH_SYSTEM = "/etc/gitwebhooks.ini"
|
|
48
|
+
CONFIG_SEARCH_PATHS = [CONFIG_PATH_USER, CONFIG_PATH_LOCAL, CONFIG_PATH_SYSTEM]
|
|
49
|
+
|
|
50
|
+
# Sensitive field keywords
|
|
51
|
+
SENSITIVE_KEYWORDS = {"secret", "password", "token", "key", "passphrase"}
|
|
52
|
+
|
|
53
|
+
# ANSI color codes for sensitive field highlighting
|
|
54
|
+
COLOR_SENSITIVE = "\033[33m" # Yellow
|
|
55
|
+
COLOR_RESET = "\033[0m"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Custom exception classes
|
|
2
|
+
|
|
3
|
+
Defines all exception types used in the project.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class WebhookError(Exception):
|
|
8
|
+
"""Webhook processing error base class"""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SignatureValidationError(WebhookError):
|
|
13
|
+
"""Signature verification failed"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UnsupportedEventError(WebhookError):
|
|
18
|
+
"""Unsupported event type"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnsupportedProviderError(WebhookError):
|
|
23
|
+
"""Unrecognized platform provider"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConfigurationError(WebhookError):
|
|
28
|
+
"""Configuration error or missing"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RequestParseError(WebhookError):
|
|
33
|
+
"""Request parsing failed"""
|
|
34
|
+
pass
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Command executor
|
|
2
|
+
|
|
3
|
+
Asynchronously executes deployment commands.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def execute_deployment(repo_name: str, cwd: str, cmd: str) -> None:
|
|
11
|
+
"""Execute deployment command
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
repo_name: Repository name (for logging)
|
|
15
|
+
cwd: Working directory
|
|
16
|
+
cmd: Command to execute
|
|
17
|
+
|
|
18
|
+
Note:
|
|
19
|
+
Command executes asynchronously without blocking the server.
|
|
20
|
+
stdin is set to DEVNULL to prevent file descriptor leaks.
|
|
21
|
+
"""
|
|
22
|
+
logging.info('[%s] Executing: %s', repo_name, cmd)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
subprocess.Popen(
|
|
26
|
+
cmd,
|
|
27
|
+
cwd=cwd,
|
|
28
|
+
shell=True,
|
|
29
|
+
stdin=subprocess.DEVNULL, # Prevent inheriting stdin, avoid fd leaks
|
|
30
|
+
stdout=subprocess.PIPE,
|
|
31
|
+
stderr=subprocess.PIPE
|
|
32
|
+
)
|
|
33
|
+
except (OSError, subprocess.SubprocessError) as e:
|
|
34
|
+
logging.warning('[%s] Execution failed: %s', repo_name, e)
|