dory-sdk 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.
- dory/__init__.py +70 -0
- dory/auto_instrument.py +142 -0
- dory/cli/__init__.py +5 -0
- dory/cli/main.py +290 -0
- dory/cli/templates.py +333 -0
- dory/config/__init__.py +23 -0
- dory/config/defaults.py +50 -0
- dory/config/loader.py +361 -0
- dory/config/presets.py +325 -0
- dory/config/schema.py +152 -0
- dory/core/__init__.py +27 -0
- dory/core/app.py +404 -0
- dory/core/context.py +209 -0
- dory/core/lifecycle.py +214 -0
- dory/core/meta.py +121 -0
- dory/core/modes.py +479 -0
- dory/core/processor.py +654 -0
- dory/core/signals.py +122 -0
- dory/decorators.py +142 -0
- dory/errors/__init__.py +117 -0
- dory/errors/classification.py +362 -0
- dory/errors/codes.py +495 -0
- dory/health/__init__.py +10 -0
- dory/health/probes.py +210 -0
- dory/health/server.py +306 -0
- dory/k8s/__init__.py +11 -0
- dory/k8s/annotation_watcher.py +184 -0
- dory/k8s/client.py +251 -0
- dory/k8s/pod_metadata.py +182 -0
- dory/logging/__init__.py +9 -0
- dory/logging/logger.py +175 -0
- dory/metrics/__init__.py +7 -0
- dory/metrics/collector.py +301 -0
- dory/middleware/__init__.py +36 -0
- dory/middleware/connection_tracker.py +608 -0
- dory/middleware/request_id.py +321 -0
- dory/middleware/request_tracker.py +501 -0
- dory/migration/__init__.py +11 -0
- dory/migration/configmap.py +260 -0
- dory/migration/serialization.py +167 -0
- dory/migration/state_manager.py +301 -0
- dory/monitoring/__init__.py +23 -0
- dory/monitoring/opentelemetry.py +462 -0
- dory/py.typed +2 -0
- dory/recovery/__init__.py +60 -0
- dory/recovery/golden_image.py +480 -0
- dory/recovery/golden_snapshot.py +561 -0
- dory/recovery/golden_validator.py +518 -0
- dory/recovery/partial_recovery.py +479 -0
- dory/recovery/recovery_decision.py +242 -0
- dory/recovery/restart_detector.py +142 -0
- dory/recovery/state_validator.py +187 -0
- dory/resilience/__init__.py +45 -0
- dory/resilience/circuit_breaker.py +454 -0
- dory/resilience/retry.py +389 -0
- dory/sidecar/__init__.py +6 -0
- dory/sidecar/main.py +75 -0
- dory/sidecar/server.py +329 -0
- dory/simple.py +342 -0
- dory/types.py +75 -0
- dory/utils/__init__.py +25 -0
- dory/utils/errors.py +59 -0
- dory/utils/retry.py +115 -0
- dory/utils/timeout.py +80 -0
- dory_sdk-2.1.0.dist-info/METADATA +663 -0
- dory_sdk-2.1.0.dist-info/RECORD +69 -0
- dory_sdk-2.1.0.dist-info/WHEEL +5 -0
- dory_sdk-2.1.0.dist-info/entry_points.txt +3 -0
- dory_sdk-2.1.0.dist-info/top_level.txt +1 -0
dory/__init__.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dory SDK - Python Integration Package for Processor Applications
|
|
3
|
+
|
|
4
|
+
This SDK handles all operational concerns (graceful shutdown, state migration,
|
|
5
|
+
health checks, observability) so developers focus solely on business logic.
|
|
6
|
+
|
|
7
|
+
Quick Start (Class-based API):
|
|
8
|
+
from dory import DoryApp, BaseProcessor, stateful
|
|
9
|
+
|
|
10
|
+
class MyProcessor(BaseProcessor):
|
|
11
|
+
counter = stateful(0) # Auto-saved/restored
|
|
12
|
+
|
|
13
|
+
async def run(self):
|
|
14
|
+
async for _ in self.run_loop(interval=1):
|
|
15
|
+
self.counter += 1
|
|
16
|
+
|
|
17
|
+
if __name__ == '__main__':
|
|
18
|
+
DoryApp().run(MyProcessor)
|
|
19
|
+
|
|
20
|
+
Quick Start (Function-based API):
|
|
21
|
+
from dory.simple import processor, state
|
|
22
|
+
|
|
23
|
+
counter = state(0)
|
|
24
|
+
|
|
25
|
+
@processor
|
|
26
|
+
async def main(ctx):
|
|
27
|
+
async for _ in ctx.run_loop(interval=1):
|
|
28
|
+
counter.value += 1
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__version__ = "1.0.0"
|
|
32
|
+
|
|
33
|
+
# Core API
|
|
34
|
+
from dory.core.processor import BaseProcessor
|
|
35
|
+
from dory.core.context import ExecutionContext
|
|
36
|
+
from dory.core.app import DoryApp
|
|
37
|
+
|
|
38
|
+
# Configuration
|
|
39
|
+
from dory.config.schema import DoryConfig
|
|
40
|
+
|
|
41
|
+
# Decorators for simplified integration
|
|
42
|
+
from dory.decorators import stateful, StatefulVar
|
|
43
|
+
|
|
44
|
+
# Exceptions
|
|
45
|
+
from dory.utils.errors import (
|
|
46
|
+
DoryError,
|
|
47
|
+
DoryStartupError,
|
|
48
|
+
DoryShutdownError,
|
|
49
|
+
DoryStateError,
|
|
50
|
+
DoryConfigError,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Core API
|
|
55
|
+
"BaseProcessor",
|
|
56
|
+
"ExecutionContext",
|
|
57
|
+
"DoryApp",
|
|
58
|
+
"DoryConfig",
|
|
59
|
+
# Decorators
|
|
60
|
+
"stateful",
|
|
61
|
+
"StatefulVar",
|
|
62
|
+
# Exceptions
|
|
63
|
+
"DoryError",
|
|
64
|
+
"DoryStartupError",
|
|
65
|
+
"DoryShutdownError",
|
|
66
|
+
"DoryStateError",
|
|
67
|
+
"DoryConfigError",
|
|
68
|
+
# Version
|
|
69
|
+
"__version__",
|
|
70
|
+
]
|
dory/auto_instrument.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-instrumentation decorator for handlers.
|
|
3
|
+
|
|
4
|
+
Automatically applies:
|
|
5
|
+
- Request ID generation
|
|
6
|
+
- Request tracking
|
|
7
|
+
- OpenTelemetry span creation
|
|
8
|
+
- Error classification
|
|
9
|
+
- Attribute injection
|
|
10
|
+
|
|
11
|
+
No manual decorators needed!
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import functools
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Callable, Any
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def auto_instrument(func: Callable) -> Callable:
|
|
22
|
+
"""
|
|
23
|
+
Auto-instrument async function with all SDK features.
|
|
24
|
+
|
|
25
|
+
Automatically handles:
|
|
26
|
+
- Request ID generation
|
|
27
|
+
- Request tracking with timeout
|
|
28
|
+
- OpenTelemetry span creation
|
|
29
|
+
- Span attributes injection
|
|
30
|
+
- Error classification and logging
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
@auto_instrument
|
|
34
|
+
async def handler(self, request):
|
|
35
|
+
# All instrumentation is automatic!
|
|
36
|
+
return {"status": "ok"}
|
|
37
|
+
|
|
38
|
+
Or with metaclass (no decorator needed):
|
|
39
|
+
class MyProcessor(BaseProcessor):
|
|
40
|
+
# All handle_* methods automatically instrumented!
|
|
41
|
+
async def handle_request(self, request):
|
|
42
|
+
return {"status": "ok"}
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
func: Async function to instrument
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Instrumented async function
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@functools.wraps(func)
|
|
52
|
+
async def wrapper(self, *args, **kwargs):
|
|
53
|
+
# Extract request if present (first arg or keyword arg)
|
|
54
|
+
request = None
|
|
55
|
+
if args:
|
|
56
|
+
request = args[0]
|
|
57
|
+
elif "request" in kwargs:
|
|
58
|
+
request = kwargs["request"]
|
|
59
|
+
|
|
60
|
+
# Get processor components (auto-initialized by BaseProcessor)
|
|
61
|
+
request_id_middleware = getattr(self, "request_id_middleware", None)
|
|
62
|
+
request_tracker = getattr(self, "request_tracker", None)
|
|
63
|
+
otel = getattr(self, "otel", None)
|
|
64
|
+
error_classifier = getattr(self, "error_classifier", None)
|
|
65
|
+
|
|
66
|
+
# 1. Generate request ID
|
|
67
|
+
request_id = None
|
|
68
|
+
if request_id_middleware:
|
|
69
|
+
request_id = request_id_middleware.generate_id()
|
|
70
|
+
# Store in request for retrieval
|
|
71
|
+
if request is not None and hasattr(request, "__setitem__"):
|
|
72
|
+
request["request_id"] = request_id
|
|
73
|
+
elif request is not None and hasattr(request, "__dict__"):
|
|
74
|
+
request.request_id = request_id
|
|
75
|
+
logger.debug(f"Generated request ID: {request_id}")
|
|
76
|
+
|
|
77
|
+
# 2. Track request
|
|
78
|
+
request_tracker_ctx = None
|
|
79
|
+
if request_tracker:
|
|
80
|
+
request_tracker_ctx = request_tracker.track_request(request_id)
|
|
81
|
+
await request_tracker_ctx.__aenter__()
|
|
82
|
+
logger.debug(f"Started tracking request: {request_id}")
|
|
83
|
+
|
|
84
|
+
# 3. Create OpenTelemetry span
|
|
85
|
+
span_ctx = None
|
|
86
|
+
if otel:
|
|
87
|
+
span_name = f"{self.__class__.__name__}.{func.__name__}"
|
|
88
|
+
attributes = {
|
|
89
|
+
"function": func.__name__,
|
|
90
|
+
"class": self.__class__.__name__,
|
|
91
|
+
}
|
|
92
|
+
if request_id:
|
|
93
|
+
attributes["request.id"] = request_id
|
|
94
|
+
if request is not None and hasattr(request, "path"):
|
|
95
|
+
attributes["endpoint"] = str(request.path)
|
|
96
|
+
if request is not None and hasattr(request, "method"):
|
|
97
|
+
attributes["http.method"] = str(request.method)
|
|
98
|
+
|
|
99
|
+
span_ctx = otel.create_span(span_name, attributes=attributes)
|
|
100
|
+
span_ctx.__enter__()
|
|
101
|
+
logger.debug(f"Created span: {span_name}")
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# Execute handler
|
|
105
|
+
result = await func(self, *args, **kwargs)
|
|
106
|
+
|
|
107
|
+
# Mark request as successful
|
|
108
|
+
if request_tracker_ctx:
|
|
109
|
+
await request_tracker_ctx.__aexit__(None, None, None)
|
|
110
|
+
logger.debug(f"Request completed successfully: {request_id}")
|
|
111
|
+
|
|
112
|
+
if span_ctx:
|
|
113
|
+
span_ctx.__exit__(None, None, None)
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
# Classify error
|
|
119
|
+
if error_classifier:
|
|
120
|
+
error_info = error_classifier.classify(e)
|
|
121
|
+
logger.warning(
|
|
122
|
+
f"Handler error: {error_info.error_type.value} - {e}",
|
|
123
|
+
extra={
|
|
124
|
+
"request_id": request_id,
|
|
125
|
+
"error_type": error_info.error_type.value,
|
|
126
|
+
"is_transient": error_info.is_transient,
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
logger.warning(f"Handler error: {e}", extra={"request_id": request_id})
|
|
131
|
+
|
|
132
|
+
# Mark request as failed
|
|
133
|
+
if request_tracker_ctx:
|
|
134
|
+
await request_tracker_ctx.__aexit__(type(e), e, None)
|
|
135
|
+
|
|
136
|
+
# Record exception in span
|
|
137
|
+
if span_ctx:
|
|
138
|
+
span_ctx.__exit__(type(e), e, None)
|
|
139
|
+
|
|
140
|
+
raise
|
|
141
|
+
|
|
142
|
+
return wrapper
|
dory/cli/__init__.py
ADDED
dory/cli/main.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dory SDK CLI - Command line tools for Dory SDK.
|
|
3
|
+
|
|
4
|
+
Provides commands for:
|
|
5
|
+
- Generating Kubernetes manifests (RBAC, Deployment, etc.)
|
|
6
|
+
- Initializing new Dory projects
|
|
7
|
+
- Validating configuration
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
dory init my-app --image my-app:latest
|
|
11
|
+
dory generate k8s --name my-app --image my-app:latest
|
|
12
|
+
dory validate
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from dory.cli.templates import (
|
|
21
|
+
generate_rbac,
|
|
22
|
+
generate_deployment,
|
|
23
|
+
generate_pod,
|
|
24
|
+
generate_all,
|
|
25
|
+
generate_dockerfile,
|
|
26
|
+
generate_processor_template,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def cmd_init(args: argparse.Namespace) -> int:
|
|
31
|
+
"""Initialize a new Dory project."""
|
|
32
|
+
name = args.name
|
|
33
|
+
output_dir = Path(args.output or ".")
|
|
34
|
+
|
|
35
|
+
print(f"Initializing Dory project: {name}")
|
|
36
|
+
|
|
37
|
+
# Create directories
|
|
38
|
+
k8s_dir = output_dir / "k8s"
|
|
39
|
+
k8s_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
# Generate files
|
|
42
|
+
files = {
|
|
43
|
+
"main.py": generate_processor_template(name),
|
|
44
|
+
"Dockerfile": generate_dockerfile(name),
|
|
45
|
+
"k8s/rbac.yaml": generate_rbac(name, args.namespace),
|
|
46
|
+
"k8s/deployment.yaml": generate_deployment(
|
|
47
|
+
name=name,
|
|
48
|
+
image=args.image or f"{name}:latest",
|
|
49
|
+
namespace=args.namespace,
|
|
50
|
+
replicas=args.replicas,
|
|
51
|
+
health_port=args.health_port,
|
|
52
|
+
app_port=args.app_port,
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for filename, content in files.items():
|
|
57
|
+
filepath = output_dir / filename
|
|
58
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
|
|
60
|
+
if filepath.exists() and not args.force:
|
|
61
|
+
print(f" Skipping {filename} (already exists, use --force to overwrite)")
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
filepath.write_text(content)
|
|
65
|
+
print(f" Created {filename}")
|
|
66
|
+
|
|
67
|
+
print()
|
|
68
|
+
print("Next steps:")
|
|
69
|
+
print(f" 1. Edit main.py with your processor logic")
|
|
70
|
+
print(f" 2. Build: docker build -t {name}:latest .")
|
|
71
|
+
print(f" 3. Deploy: kubectl apply -f k8s/")
|
|
72
|
+
print()
|
|
73
|
+
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cmd_generate(args: argparse.Namespace) -> int:
|
|
78
|
+
"""Generate Kubernetes manifests."""
|
|
79
|
+
output_dir = Path(args.output or "k8s")
|
|
80
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
|
|
82
|
+
name = args.name
|
|
83
|
+
image = args.image or f"{name}:latest"
|
|
84
|
+
namespace = args.namespace
|
|
85
|
+
|
|
86
|
+
if args.type == "rbac":
|
|
87
|
+
content = generate_rbac(name, namespace)
|
|
88
|
+
filename = "rbac.yaml"
|
|
89
|
+
elif args.type == "deployment":
|
|
90
|
+
content = generate_deployment(
|
|
91
|
+
name=name,
|
|
92
|
+
image=image,
|
|
93
|
+
namespace=namespace,
|
|
94
|
+
replicas=args.replicas,
|
|
95
|
+
health_port=args.health_port,
|
|
96
|
+
app_port=args.app_port,
|
|
97
|
+
)
|
|
98
|
+
filename = "deployment.yaml"
|
|
99
|
+
elif args.type == "pod":
|
|
100
|
+
content = generate_pod(
|
|
101
|
+
name=name,
|
|
102
|
+
image=image,
|
|
103
|
+
namespace=namespace,
|
|
104
|
+
health_port=args.health_port,
|
|
105
|
+
app_port=args.app_port,
|
|
106
|
+
)
|
|
107
|
+
filename = "pod.yaml"
|
|
108
|
+
elif args.type == "all":
|
|
109
|
+
content = generate_all(
|
|
110
|
+
name=name,
|
|
111
|
+
image=image,
|
|
112
|
+
namespace=namespace,
|
|
113
|
+
replicas=args.replicas,
|
|
114
|
+
health_port=args.health_port,
|
|
115
|
+
app_port=args.app_port,
|
|
116
|
+
)
|
|
117
|
+
filename = "all.yaml"
|
|
118
|
+
else:
|
|
119
|
+
print(f"Unknown type: {args.type}")
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
filepath = output_dir / filename
|
|
123
|
+
|
|
124
|
+
if filepath.exists() and not args.force:
|
|
125
|
+
print(f"File {filepath} already exists. Use --force to overwrite.")
|
|
126
|
+
return 1
|
|
127
|
+
|
|
128
|
+
filepath.write_text(content)
|
|
129
|
+
print(f"Generated: {filepath}")
|
|
130
|
+
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cmd_validate(args: argparse.Namespace) -> int:
|
|
135
|
+
"""Validate Dory configuration."""
|
|
136
|
+
from dory.config.loader import ConfigLoader
|
|
137
|
+
|
|
138
|
+
config_file = args.config
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
loader = ConfigLoader(config_file=config_file)
|
|
142
|
+
config = loader.load()
|
|
143
|
+
print("Configuration is valid!")
|
|
144
|
+
print()
|
|
145
|
+
print("Current settings:")
|
|
146
|
+
for key, value in config.model_dump().items():
|
|
147
|
+
print(f" {key}: {value}")
|
|
148
|
+
return 0
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(f"Configuration error: {e}")
|
|
151
|
+
return 1
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main(argv: list[str] | None = None) -> int:
|
|
155
|
+
"""Main CLI entry point."""
|
|
156
|
+
parser = argparse.ArgumentParser(
|
|
157
|
+
prog="dory",
|
|
158
|
+
description="Dory SDK CLI - Tools for building stateful Kubernetes processors",
|
|
159
|
+
)
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
"--version",
|
|
162
|
+
action="version",
|
|
163
|
+
version="%(prog)s 1.0.0",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
167
|
+
|
|
168
|
+
# init command
|
|
169
|
+
init_parser = subparsers.add_parser(
|
|
170
|
+
"init",
|
|
171
|
+
help="Initialize a new Dory project",
|
|
172
|
+
)
|
|
173
|
+
init_parser.add_argument(
|
|
174
|
+
"name",
|
|
175
|
+
help="Project/app name",
|
|
176
|
+
)
|
|
177
|
+
init_parser.add_argument(
|
|
178
|
+
"-o", "--output",
|
|
179
|
+
help="Output directory (default: current directory)",
|
|
180
|
+
)
|
|
181
|
+
init_parser.add_argument(
|
|
182
|
+
"-i", "--image",
|
|
183
|
+
help="Docker image name (default: <name>:latest)",
|
|
184
|
+
)
|
|
185
|
+
init_parser.add_argument(
|
|
186
|
+
"-n", "--namespace",
|
|
187
|
+
default="default",
|
|
188
|
+
help="Kubernetes namespace (default: default)",
|
|
189
|
+
)
|
|
190
|
+
init_parser.add_argument(
|
|
191
|
+
"--replicas",
|
|
192
|
+
type=int,
|
|
193
|
+
default=1,
|
|
194
|
+
help="Number of replicas (default: 1)",
|
|
195
|
+
)
|
|
196
|
+
init_parser.add_argument(
|
|
197
|
+
"--health-port",
|
|
198
|
+
type=int,
|
|
199
|
+
default=8080,
|
|
200
|
+
help="Health server port (default: 8080)",
|
|
201
|
+
)
|
|
202
|
+
init_parser.add_argument(
|
|
203
|
+
"--app-port",
|
|
204
|
+
type=int,
|
|
205
|
+
default=8081,
|
|
206
|
+
help="Application port (default: 8081)",
|
|
207
|
+
)
|
|
208
|
+
init_parser.add_argument(
|
|
209
|
+
"-f", "--force",
|
|
210
|
+
action="store_true",
|
|
211
|
+
help="Overwrite existing files",
|
|
212
|
+
)
|
|
213
|
+
init_parser.set_defaults(func=cmd_init)
|
|
214
|
+
|
|
215
|
+
# generate command
|
|
216
|
+
gen_parser = subparsers.add_parser(
|
|
217
|
+
"generate",
|
|
218
|
+
help="Generate Kubernetes manifests",
|
|
219
|
+
)
|
|
220
|
+
gen_parser.add_argument(
|
|
221
|
+
"type",
|
|
222
|
+
choices=["rbac", "deployment", "pod", "all"],
|
|
223
|
+
help="Type of manifest to generate",
|
|
224
|
+
)
|
|
225
|
+
gen_parser.add_argument(
|
|
226
|
+
"-n", "--name",
|
|
227
|
+
required=True,
|
|
228
|
+
help="Application name",
|
|
229
|
+
)
|
|
230
|
+
gen_parser.add_argument(
|
|
231
|
+
"-i", "--image",
|
|
232
|
+
help="Docker image (default: <name>:latest)",
|
|
233
|
+
)
|
|
234
|
+
gen_parser.add_argument(
|
|
235
|
+
"--namespace",
|
|
236
|
+
default="default",
|
|
237
|
+
help="Kubernetes namespace (default: default)",
|
|
238
|
+
)
|
|
239
|
+
gen_parser.add_argument(
|
|
240
|
+
"--replicas",
|
|
241
|
+
type=int,
|
|
242
|
+
default=1,
|
|
243
|
+
help="Number of replicas (default: 1)",
|
|
244
|
+
)
|
|
245
|
+
gen_parser.add_argument(
|
|
246
|
+
"--health-port",
|
|
247
|
+
type=int,
|
|
248
|
+
default=8080,
|
|
249
|
+
help="Health server port (default: 8080)",
|
|
250
|
+
)
|
|
251
|
+
gen_parser.add_argument(
|
|
252
|
+
"--app-port",
|
|
253
|
+
type=int,
|
|
254
|
+
default=8081,
|
|
255
|
+
help="Application port (default: 8081)",
|
|
256
|
+
)
|
|
257
|
+
gen_parser.add_argument(
|
|
258
|
+
"-o", "--output",
|
|
259
|
+
default="k8s",
|
|
260
|
+
help="Output directory (default: k8s)",
|
|
261
|
+
)
|
|
262
|
+
gen_parser.add_argument(
|
|
263
|
+
"-f", "--force",
|
|
264
|
+
action="store_true",
|
|
265
|
+
help="Overwrite existing files",
|
|
266
|
+
)
|
|
267
|
+
gen_parser.set_defaults(func=cmd_generate)
|
|
268
|
+
|
|
269
|
+
# validate command
|
|
270
|
+
validate_parser = subparsers.add_parser(
|
|
271
|
+
"validate",
|
|
272
|
+
help="Validate Dory configuration",
|
|
273
|
+
)
|
|
274
|
+
validate_parser.add_argument(
|
|
275
|
+
"-c", "--config",
|
|
276
|
+
help="Path to config file (default: dory.yaml)",
|
|
277
|
+
)
|
|
278
|
+
validate_parser.set_defaults(func=cmd_validate)
|
|
279
|
+
|
|
280
|
+
args = parser.parse_args(argv)
|
|
281
|
+
|
|
282
|
+
if not args.command:
|
|
283
|
+
parser.print_help()
|
|
284
|
+
return 0
|
|
285
|
+
|
|
286
|
+
return args.func(args)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
if __name__ == "__main__":
|
|
290
|
+
sys.exit(main())
|