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.
Files changed (47) hide show
  1. gitwebhooks/__init__.py +18 -0
  2. gitwebhooks/__main__.py +10 -0
  3. gitwebhooks/auth/__init__.py +20 -0
  4. gitwebhooks/auth/custom.py +40 -0
  5. gitwebhooks/auth/factory.py +73 -0
  6. gitwebhooks/auth/gitee.py +86 -0
  7. gitwebhooks/auth/github.py +52 -0
  8. gitwebhooks/auth/gitlab.py +29 -0
  9. gitwebhooks/auth/verifier.py +34 -0
  10. gitwebhooks/cli/__init__.py +107 -0
  11. gitwebhooks/cli/config.py +216 -0
  12. gitwebhooks/cli/init_wizard.py +701 -0
  13. gitwebhooks/cli/prompts.py +275 -0
  14. gitwebhooks/cli/service.py +374 -0
  15. gitwebhooks/config/__init__.py +17 -0
  16. gitwebhooks/config/loader.py +170 -0
  17. gitwebhooks/config/models.py +145 -0
  18. gitwebhooks/config/registry.py +104 -0
  19. gitwebhooks/config/server.py +85 -0
  20. gitwebhooks/handlers/__init__.py +22 -0
  21. gitwebhooks/handlers/base.py +107 -0
  22. gitwebhooks/handlers/custom.py +73 -0
  23. gitwebhooks/handlers/factory.py +83 -0
  24. gitwebhooks/handlers/gitee.py +113 -0
  25. gitwebhooks/handlers/github.py +43 -0
  26. gitwebhooks/handlers/gitlab.py +43 -0
  27. gitwebhooks/handlers/request.py +248 -0
  28. gitwebhooks/logging/__init__.py +10 -0
  29. gitwebhooks/logging/setup.py +57 -0
  30. gitwebhooks/main/__init__.py +117 -0
  31. gitwebhooks/main/__main__.py +12 -0
  32. gitwebhooks/models/__init__.py +14 -0
  33. gitwebhooks/models/provider.py +22 -0
  34. gitwebhooks/models/request.py +61 -0
  35. gitwebhooks/models/result.py +40 -0
  36. gitwebhooks/server.py +128 -0
  37. gitwebhooks/utils/__init__.py +14 -0
  38. gitwebhooks/utils/constants.py +55 -0
  39. gitwebhooks/utils/exceptions.py +34 -0
  40. gitwebhooks/utils/executor.py +34 -0
  41. gitwebhooks/utils/systemd.py +274 -0
  42. gitwebhooks-2.1.0.dist-info/METADATA +494 -0
  43. gitwebhooks-2.1.0.dist-info/RECORD +47 -0
  44. gitwebhooks-2.1.0.dist-info/WHEEL +5 -0
  45. gitwebhooks-2.1.0.dist-info/entry_points.txt +2 -0
  46. gitwebhooks-2.1.0.dist-info/licenses/LICENSE +21 -0
  47. 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,12 @@
1
+ """Main module entry point
2
+
3
+ Allows running the CLI with: python3 -m gitwebhooks.main
4
+ """
5
+
6
+ import sys
7
+
8
+ # Import main from the same directory
9
+ from gitwebhooks.main import main
10
+
11
+ if __name__ == '__main__':
12
+ 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,14 @@
1
+ """Utilities module
2
+
3
+ Provides constants, exceptions, and utility functions.
4
+ """
5
+
6
+ from . import constants
7
+ from . import exceptions
8
+ from . import executor
9
+
10
+ __all__ = [
11
+ 'constants',
12
+ 'exceptions',
13
+ 'executor',
14
+ ]
@@ -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)