dploydb 0.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.
- dploydb/__init__.py +1 -0
- dploydb/__main__.py +6 -0
- dploydb/backup.py +455 -0
- dploydb/candidate.py +868 -0
- dploydb/cli.py +1111 -0
- dploydb/config.py +784 -0
- dploydb/cutover.py +326 -0
- dploydb/deploy.py +1163 -0
- dploydb/deployment_dependencies.py +366 -0
- dploydb/deployment_evidence.py +136 -0
- dploydb/diagnostics.py +1020 -0
- dploydb/errors.py +140 -0
- dploydb/health.py +625 -0
- dploydb/locking.py +609 -0
- dploydb/manual_restore.py +825 -0
- dploydb/migration.py +575 -0
- dploydb/models.py +927 -0
- dploydb/recovery.py +1210 -0
- dploydb/redaction.py +229 -0
- dploydb/releases.py +611 -0
- dploydb/restore.py +461 -0
- dploydb/retention.py +165 -0
- dploydb/runners/__init__.py +33 -0
- dploydb/runners/base.py +389 -0
- dploydb/runners/docker_compose.py +590 -0
- dploydb/runners/docker_compose_production.py +966 -0
- dploydb/sqlite_checks.py +136 -0
- dploydb/state.py +604 -0
- dploydb/storage/__init__.py +13 -0
- dploydb/storage/base.py +52 -0
- dploydb/storage/local.py +301 -0
- dploydb/storage/s3.py +737 -0
- dploydb/subprocesses.py +641 -0
- dploydb/traffic.py +165 -0
- dploydb-0.1.0.dist-info/METADATA +583 -0
- dploydb-0.1.0.dist-info/RECORD +40 -0
- dploydb-0.1.0.dist-info/WHEEL +4 -0
- dploydb-0.1.0.dist-info/entry_points.txt +2 -0
- dploydb-0.1.0.dist-info/licenses/LICENSE +201 -0
- dploydb-0.1.0.dist-info/licenses/NOTICE +4 -0
dploydb/errors.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Stable expected-error taxonomy for DployDB."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import IntEnum, StrEnum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import ClassVar, Final
|
|
8
|
+
|
|
9
|
+
from dploydb.models import FailurePayload
|
|
10
|
+
from dploydb.redaction import SecretRegistry
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExitCode(IntEnum):
|
|
14
|
+
"""Stable DployDB process exit codes."""
|
|
15
|
+
|
|
16
|
+
SUCCESS = 0
|
|
17
|
+
USAGE = 2
|
|
18
|
+
CONFIGURATION = 10
|
|
19
|
+
SAFETY_CHECK = 20
|
|
20
|
+
LOCK_UNAVAILABLE = 30
|
|
21
|
+
EXTERNAL_COMMAND = 40
|
|
22
|
+
OPERATION_FAILED = 50
|
|
23
|
+
RECOVERY_REQUIRED = 60
|
|
24
|
+
INTERNAL_ERROR = 70
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ErrorKind(StrEnum):
|
|
28
|
+
"""Stable machine-readable categories for expected failures."""
|
|
29
|
+
|
|
30
|
+
CONFIGURATION = "configuration_error"
|
|
31
|
+
SAFETY_CHECK = "safety_check_failed"
|
|
32
|
+
LOCK_UNAVAILABLE = "deployment_lock_unavailable"
|
|
33
|
+
EXTERNAL_COMMAND = "external_command_failed"
|
|
34
|
+
OPERATION_FAILED = "operation_failed"
|
|
35
|
+
RECOVERY_REQUIRED = "recovery_required"
|
|
36
|
+
INTERNAL_ERROR = "internal_error"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ERROR_EXIT_CODES: Final[dict[ErrorKind, ExitCode]] = {
|
|
40
|
+
ErrorKind.CONFIGURATION: ExitCode.CONFIGURATION,
|
|
41
|
+
ErrorKind.SAFETY_CHECK: ExitCode.SAFETY_CHECK,
|
|
42
|
+
ErrorKind.LOCK_UNAVAILABLE: ExitCode.LOCK_UNAVAILABLE,
|
|
43
|
+
ErrorKind.EXTERNAL_COMMAND: ExitCode.EXTERNAL_COMMAND,
|
|
44
|
+
ErrorKind.OPERATION_FAILED: ExitCode.OPERATION_FAILED,
|
|
45
|
+
ErrorKind.RECOVERY_REQUIRED: ExitCode.RECOVERY_REQUIRED,
|
|
46
|
+
ErrorKind.INTERNAL_ERROR: ExitCode.INTERNAL_ERROR,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DployDBError(Exception):
|
|
51
|
+
"""Base class for failures that the CLI can report without a traceback."""
|
|
52
|
+
|
|
53
|
+
kind: ClassVar[ErrorKind] = ErrorKind.OPERATION_FAILED
|
|
54
|
+
requires_recovery: ClassVar[bool] = False
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
what_failed: str,
|
|
59
|
+
*,
|
|
60
|
+
production_changed: bool = False,
|
|
61
|
+
previous_application_running: bool | None = None,
|
|
62
|
+
log_path: str | Path | None = None,
|
|
63
|
+
next_safe_action: str,
|
|
64
|
+
) -> None:
|
|
65
|
+
self.payload = FailurePayload(
|
|
66
|
+
error_code=self.kind.value,
|
|
67
|
+
exit_code=int(ERROR_EXIT_CODES[self.kind]),
|
|
68
|
+
what_failed=what_failed,
|
|
69
|
+
production_changed=production_changed,
|
|
70
|
+
previous_application_running=previous_application_running,
|
|
71
|
+
recovery_required=self.requires_recovery,
|
|
72
|
+
log_path=None if log_path is None else str(log_path),
|
|
73
|
+
next_safe_action=next_safe_action,
|
|
74
|
+
)
|
|
75
|
+
super().__init__(what_failed)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def exit_code(self) -> ExitCode:
|
|
79
|
+
"""Return the stable process exit code for this failure."""
|
|
80
|
+
return ERROR_EXIT_CODES[self.kind]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ConfigurationError(DployDBError):
|
|
84
|
+
"""Configuration is absent, invalid, or cannot be resolved safely."""
|
|
85
|
+
|
|
86
|
+
kind = ErrorKind.CONFIGURATION
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SafetyCheckError(DployDBError):
|
|
90
|
+
"""A host or operation safety precondition did not pass."""
|
|
91
|
+
|
|
92
|
+
kind = ErrorKind.SAFETY_CHECK
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class LockUnavailableError(DployDBError):
|
|
96
|
+
"""Another process currently owns the operating-system deployment lock."""
|
|
97
|
+
|
|
98
|
+
kind = ErrorKind.LOCK_UNAVAILABLE
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ExternalCommandError(DployDBError):
|
|
102
|
+
"""A bounded external command failed, timed out, or could not start."""
|
|
103
|
+
|
|
104
|
+
kind = ErrorKind.EXTERNAL_COMMAND
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class OperationFailedError(DployDBError):
|
|
108
|
+
"""An operation failed safely and does not require recovery."""
|
|
109
|
+
|
|
110
|
+
kind = ErrorKind.OPERATION_FAILED
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RecoveryRequiredError(DployDBError):
|
|
114
|
+
"""State is uncertain and a recovery action is required."""
|
|
115
|
+
|
|
116
|
+
kind = ErrorKind.RECOVERY_REQUIRED
|
|
117
|
+
requires_recovery = True
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class StateCorruptionError(RecoveryRequiredError):
|
|
121
|
+
"""Durable state is malformed, contradictory, or incomplete."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class InternalError(DployDBError):
|
|
125
|
+
"""An unexpected failure converted at the outer CLI boundary."""
|
|
126
|
+
|
|
127
|
+
kind = ErrorKind.INTERNAL_ERROR
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def redact_error(error: DployDBError, *, secrets: SecretRegistry) -> DployDBError:
|
|
131
|
+
"""Rebuild an expected error after redacting every user-visible text field."""
|
|
132
|
+
return type(error)(
|
|
133
|
+
secrets.redact_text(error.payload.what_failed),
|
|
134
|
+
production_changed=error.payload.production_changed,
|
|
135
|
+
previous_application_running=error.payload.previous_application_running,
|
|
136
|
+
log_path=(
|
|
137
|
+
None if error.payload.log_path is None else secrets.redact_text(error.payload.log_path)
|
|
138
|
+
),
|
|
139
|
+
next_safe_action=secrets.redact_text(error.payload.next_safe_action),
|
|
140
|
+
)
|