@tdk-landscape/tdk-cli-core 1.3.15 → 1.3.22
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.
- package/Tiltfile +144 -0
- package/discovery/AGENTS.md +362 -0
- package/discovery/TILT_DISCOVERY.star +424 -0
- package/discovery/Tiltfile +58 -0
- package/discovery/config.star +258 -0
- package/discovery/constants.star +127 -0
- package/discovery/deterministic.star +211 -0
- package/discovery/discovery_daemon.star +191 -0
- package/discovery/discovery_orchestrator.star +460 -0
- package/discovery/filtering.star +5 -0
- package/discovery/identity-management-backend/service.yaml +26 -0
- package/discovery/identity-management-frontend/service.yaml +29 -0
- package/discovery/identity-sdk/service.yaml +9 -0
- package/discovery/incremental_discovery.star +213 -0
- package/discovery/indexing.star +7 -0
- package/discovery/json_manifest_scanner.star +51 -0
- package/discovery/libraries.star +46 -0
- package/discovery/loading.star +23 -0
- package/discovery/manifest/constants.star +104 -0
- package/discovery/manifest/loading.star +386 -0
- package/discovery/manifest/normalize.star +281 -0
- package/discovery/manifest/validation.star +108 -0
- package/discovery/normalization.star +11 -0
- package/discovery/registry.star +544 -0
- package/discovery/resource_snapshot.py +382 -0
- package/discovery/resource_snapshot.star +143 -0
- package/discovery/schemas.star +1 -0
- package/discovery/services/platform/cdc/debezium/nats-connector/service.yaml +9 -0
- package/discovery/services/platform/cdc/nats-http-bridge/service.yaml +9 -0
- package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +26 -0
- package/discovery/services/platform/synthetic-monitor/service.yaml +9 -0
- package/discovery/test_resource_snapshot.py +251 -0
- package/discovery/validation.star +78 -0
- package/discovery/versioning.star +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +11 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +1 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +36 -41
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/resource.d.ts.map +1 -1
- package/dist/commands/resource.js +51 -55
- package/dist/commands/resource.js.map +1 -1
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +22 -24
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +1 -2
- package/dist/commands/ui.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +1 -2
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/upgrade.d.ts +8 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +59 -18
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/generator/extension-fetch.d.ts +19 -0
- package/dist/generator/extension-fetch.d.ts.map +1 -1
- package/dist/generator/extension-fetch.js +71 -5
- package/dist/generator/extension-fetch.js.map +1 -1
- package/dist/generator/template-engine.d.ts +1 -0
- package/dist/generator/template-engine.d.ts.map +1 -1
- package/dist/generator/template-engine.js +50 -6
- package/dist/generator/template-engine.js.map +1 -1
- package/dist/utils/command-helpers.d.ts.map +1 -1
- package/dist/utils/command-helpers.js +7 -9
- package/dist/utils/command-helpers.js.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +1 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +6 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/project-features.d.ts.map +1 -1
- package/dist/utils/project-features.js +1 -1
- package/dist/utils/project-features.js.map +1 -1
- package/dist/utils/resource-features.d.ts.map +1 -1
- package/dist/utils/resource-features.js +2 -2
- package/dist/utils/resource-features.js.map +1 -1
- package/dist/utils/tar.js +4 -4
- package/dist/utils/tar.js.map +1 -1
- package/dist/utils/validation.d.ts +7 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +9 -0
- package/dist/utils/validation.js.map +1 -1
- package/engine/AGENTS.md +705 -0
- package/engine/README.md +29 -0
- package/engine/REFACTORING_MANIFEST_SYSTEM.md +974 -0
- package/engine/Tiltfile +80 -0
- package/engine/assets/docker/golden-layers.Dockerfile +221 -0
- package/engine/assets/scripts/cleanup-completed-migrator-images.sh +37 -0
- package/engine/assets/scripts/cleanup-tilt-images.sh +120 -0
- package/engine/assets/scripts/mac-m1-resource-monitor.sh +121 -0
- package/engine/config.star +3 -0
- package/engine/docs/AUTODISCOVERY_TEST.md +182 -0
- package/engine/docs/C4-ARCHITECTURE-operations.md +396 -0
- package/engine/docs/C4-ARCHITECTURE-overview.md +284 -0
- package/engine/docs/C4-ARCHITECTURE.md +17 -0
- package/engine/docs/GOLDEN_IMAGE_INTEGRATION.md +349 -0
- package/engine/docs/MANIFEST_DRIVEN_ARCHITECTURE.md +357 -0
- package/engine/docs/README.md +284 -0
- package/engine/entrypoint.star +97 -0
- package/engine/lifecycle/orchestrator.star +260 -0
- package/engine/paths.star +717 -0
- package/engine/schemas/manifest-schema.json +301 -0
- package/engine/schemas/service-schema.json +301 -0
- package/engine/scripts/README.md +89 -0
- package/engine/scripts/build-and-publish-lib.sh +205 -0
- package/engine/scripts/check-lib-publish.sh +85 -0
- package/engine/scripts/debug-container.sh +258 -0
- package/engine/scripts/generate-manifest.sh +197 -0
- package/engine/spec.master +702 -0
- package/engine/spec.master.example +341 -0
- package/engine/topologies/README.md +34 -0
- package/engine/topologies/platform/docker/AGENTS.md +59 -0
- package/engine/topologies/platform/docker/auth/auth.star +167 -0
- package/engine/topologies/platform/docker/build/golden_image.star +37 -0
- package/engine/topologies/platform/docker/build/golden_image_build.star +82 -0
- package/engine/topologies/platform/docker/build/golden_image_constants.star +93 -0
- package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +291 -0
- package/engine/topologies/platform/docker/compose/compose.star +636 -0
- package/engine/topologies/platform/docker/compose/compose_helpers.star +52 -0
- package/engine/topologies/platform/docker/compose/traefik_standalone.star +53 -0
- package/engine/topologies/platform/docker/config/bunignore.star +142 -0
- package/engine/topologies/platform/docker/config/dockerignore.star +150 -0
- package/engine/topologies/platform/docker/constants.star +170 -0
- package/engine/topologies/platform/docker/dockerfile/dockerfile.star +54 -0
- package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +73 -0
- package/engine/topologies/platform/docker/index.star +27 -0
- package/engine/topologies/platform/docker/layers/bun_helpers.star +23 -0
- package/engine/topologies/platform/docker/layers/infisical/index.star +40 -0
- package/engine/topologies/platform/docker/layers/infisical/infisical_docker.star +262 -0
- package/engine/topologies/platform/docker/layers/l1_base_layers.star +87 -0
- package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +125 -0
- package/engine/topologies/platform/docker/layers/l3_builder_layers.star +209 -0
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +214 -0
- package/engine/topologies/platform/docker/layers/prisma/constants.star +135 -0
- package/engine/topologies/platform/docker/layers/prisma/db_readiness.star +96 -0
- package/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +109 -0
- package/engine/topologies/platform/docker/layers/prisma/migrate_script.star +98 -0
- package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +126 -0
- package/engine/topologies/platform/docker/layers/prisma/prisma_runtime.star +161 -0
- package/engine/topologies/platform/docker/layers/prisma/secret_resolution.star +119 -0
- package/engine/topologies/platform/docker/networking/api_path_constants.star +230 -0
- package/engine/topologies/platform/docker/networking/sablier_container_cycle.star +27 -0
- package/engine/topologies/platform/docker/networking/traefik.star +232 -0
- package/engine/topologies/platform/docker/networking/traefik_constants.star +146 -0
- package/engine/topologies/platform/docker/networking/traefik_helpers.star +160 -0
- package/engine/topologies/platform/docker/secrets/infisical.star +135 -0
- package/engine/topologies/platform/net/proxy.star +4 -0
- package/engine/topologies/platform/registries/local.star +4 -0
- package/engine/topologies/platform/security/secrets.star +239 -0
- package/engine/topologies/platform/state/relational.star +4 -0
- package/engine/topologies/tilt/common/AGENTS.md +59 -0
- package/engine/topologies/tilt/common/utils.star +364 -0
- package/engine/topologies/tilt/common/utils_debug.star +69 -0
- package/engine/topologies/tilt/common/utils_deterministic.star +168 -0
- package/engine/topologies/tilt/common/utils_docker_networks.star +46 -0
- package/engine/topologies/tilt/common/utils_env.star +43 -0
- package/engine/topologies/tilt/common/utils_output.star +97 -0
- package/engine/topologies/tilt/config/AGENTS.md +68 -0
- package/engine/topologies/tilt/config/defaults.star +3 -0
- package/engine/topologies/tilt/config/feature_flags.star +10 -0
- package/engine/topologies/tilt/config/global.star +55 -0
- package/engine/topologies/tilt/config/profiles.star +382 -0
- package/engine/topologies/tilt/generators/agents_md.star +62 -0
- package/engine/topologies/tilt/generators/api_gateway.star +145 -0
- package/engine/topologies/tilt/generators/index.star +124 -0
- package/engine/topologies/tilt/generators/infisical/README.md +424 -0
- package/engine/topologies/tilt/generators/infisical/examples.star +312 -0
- package/engine/topologies/tilt/generators/infisical/index.star +57 -0
- package/engine/topologies/tilt/generators/infisical/machine_identity.star +420 -0
- package/engine/topologies/tilt/generators/infisical/organization.star +534 -0
- package/engine/topologies/tilt/generators/infisical/path_manager.star +476 -0
- package/engine/topologies/tilt/generators/infisical/secrets_generator.star +420 -0
- package/engine/topologies/tilt/generators/nginx_conf.star +143 -0
- package/engine/topologies/tilt/generators/npmrc.star +111 -0
- package/engine/topologies/tilt/generators/tsconfig.star +23 -0
- package/engine/topologies/tilt/generators/typescript/backend_library_tsconfig.star +71 -0
- package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +124 -0
- package/engine/topologies/tilt/generators/typescript/common.star +7 -0
- package/engine/topologies/tilt/generators/typescript/frontend_library_tsconfig.star +76 -0
- package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +110 -0
- package/engine/topologies/tilt/generators/typescript/include.star +23 -0
- package/engine/topologies/tilt/generators/typescript/playwright_config.star +40 -0
- package/engine/topologies/tilt/generators/typescript/prisma_config.star +49 -0
- package/engine/topologies/tilt/generators/validators.star +853 -0
- package/engine/topologies/tilt/generators/vite/backend.star +78 -0
- package/engine/topologies/tilt/generators/vite/frontend.star +98 -0
- package/engine/topologies/tilt/generators/vite/generators.star +43 -0
- package/engine/topologies/tilt/generators/vite/helpers.star +244 -0
- package/engine/topologies/tilt/generators/vite/library.star +65 -0
- package/engine/topologies/tilt/generators/vite/sdk.star +73 -0
- package/engine/topologies/tilt/generators/vite/templates.star +737 -0
- package/engine/topologies/tilt/generators/vite_config.star +22 -0
- package/engine/topologies/tilt/manifest/AGENTS.md +61 -0
- package/engine/topologies/tilt/manifest/README.md +693 -0
- package/engine/topologies/tilt/manifest/__init__.star +218 -0
- package/engine/topologies/tilt/manifest/constants.star +252 -0
- package/engine/topologies/tilt/manifest/errors.star +266 -0
- package/engine/topologies/tilt/manifest/integration.star +339 -0
- package/engine/topologies/tilt/manifest/json_to_yaml.py +54 -0
- package/engine/topologies/tilt/manifest/loader.star +214 -0
- package/engine/topologies/tilt/manifest/mapper.star +227 -0
- package/engine/topologies/tilt/manifest/parser.star +400 -0
- package/engine/topologies/tilt/manifest/schema.star +462 -0
- package/engine/topologies/tilt/manifest/tests/test_integration.star +276 -0
- package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +397 -0
- package/engine/topologies/tilt/manifest/tests/test_manifest.star +316 -0
- package/engine/topologies/tilt/manifest/tests/test_validator.star +470 -0
- package/engine/topologies/tilt/manifest/validator.star +717 -0
- package/engine/topologies/tilt/manifest/yaml_to_json.py +18 -0
- package/engine/topologies/tilt/resources/AGENTS.md +71 -0
- package/engine/topologies/tilt/resources/conditions.star +3 -0
- package/engine/topologies/tilt/resources/databases.star +244 -0
- package/engine/topologies/tilt/resources/declaration.star +3 -0
- package/engine/topologies/tilt/resources/deps.star +3 -0
- package/engine/topologies/tilt/resources/infra-loader.star +437 -0
- package/engine/topologies/tilt/resources/libs.star +30 -0
- package/engine/topologies/tilt/resources/libs_discovery.star +102 -0
- package/engine/topologies/tilt/resources/libs_paths.star +79 -0
- package/engine/topologies/tilt/resources/libs_resource_definition.star +151 -0
- package/engine/topologies/tilt/resources/libs_setup.star +164 -0
- package/engine/topologies/tilt/resources/libs_tsconfig.star +37 -0
- package/engine/topologies/tilt/resources/orchestrator/apply.star +77 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +431 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_manifest_orchestration.star +81 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +148 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_resource_validation.star +36 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star +44 -0
- package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +164 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/AGENTS.md +75 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/index.star +21 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +134 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/env.star +258 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +169 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +524 -0
- package/engine/topologies/tilt/resources/orchestrator/helpers.star +33 -0
- package/engine/topologies/tilt/resources/orchestrator.star +54 -0
- package/engine/topologies/tilt/resources/ordering.star +3 -0
- package/engine/topologies/tilt/resources/triggers.star +3 -0
- package/engine/topologies/tilt/resources/watchers.star +299 -0
- package/engine/where-things-live.star +717 -0
- package/ext/Tiltfile +66 -0
- package/ext/ide-components/README.md +219 -0
- package/ext/ide-components/code_executor/server.py +268 -0
- package/ext/ide-components/code_viewer/server.py +206 -0
- package/ext/ide-components/config_inspector/server.py +1050 -0
- package/ext/ide-components/file_browser/server.py +285 -0
- package/ext/ide-components/shared/config_service.py +469 -0
- package/ext/ide-components/shared/file_utils.py +298 -0
- package/ext/ide-components/shared/http_utils.py +232 -0
- package/ext/ide-components/shared/static/styles.css +1334 -0
- package/ext/ide-components/shared/syntax_highlighter.py +140 -0
- package/ext/ide-components/shared/template_engine.py +123 -0
- package/ext/ide-components/shared/templates/base.html +121 -0
- package/ext/ide-components/shared/test_config_service.py +326 -0
- package/ext/ide-components/shared/tilt_integration.py +158 -0
- package/ext/ui-enhancements/cron-status-server.sh +49 -0
- package/ext/ui-enhancements/cron-status-tracker.sh +146 -0
- package/ext/ui-enhancements/extension.yaml +36 -0
- package/ext/ui-enhancements/tiltfile.py +99 -0
- package/ext/ui-enhancements/ui/cron-jobs-tab.css +396 -0
- package/ext/ui-enhancements/ui/help-panel.css +279 -0
- package/ext/ui-enhancements/ui/icons.css +125 -0
- package/ext/ui-enhancements/ui/tooltips.css +114 -0
- package/package.json +13 -9
- package/specs/specs/TILT_SERVICE_DEFAULTS.star +205 -0
- package/specs/specs/TILT_TECH_STACK.star +159 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Automated tests for ConfigService
|
|
4
|
+
Tests path validation, file operations, backups, and security features.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Add parent to path
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
15
|
+
from config_service import ConfigService, SaveResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestConfigService:
|
|
19
|
+
"""Test suite for ConfigService"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.passed = 0
|
|
23
|
+
self.failed = 0
|
|
24
|
+
self.temp_dir = None
|
|
25
|
+
|
|
26
|
+
def setup(self):
|
|
27
|
+
"""Create temporary test directory"""
|
|
28
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
29
|
+
# Create structure mimicking project
|
|
30
|
+
(Path(self.temp_dir) / '.tilt-engine' / 'topologies').mkdir(parents=True)
|
|
31
|
+
(Path(self.temp_dir) / '.tilt-engine' / 'topologies' / 'test.star').write_text('# test')
|
|
32
|
+
|
|
33
|
+
# Create root master configs
|
|
34
|
+
(Path(self.temp_dir) / 'TILT_RESOURCE_DEFAULTS.star').write_text('# defaults')
|
|
35
|
+
(Path(self.temp_dir) / 'spec.master').write_text('# spec')
|
|
36
|
+
|
|
37
|
+
# Override PROJECT_ROOT for testing by patching the module directly
|
|
38
|
+
import config_service
|
|
39
|
+
self.original_root = config_service.PROJECT_ROOT
|
|
40
|
+
config_service.PROJECT_ROOT = Path(self.temp_dir)
|
|
41
|
+
# Also patch in file_utils
|
|
42
|
+
import file_utils
|
|
43
|
+
self.original_file_utils_root = file_utils.PROJECT_ROOT
|
|
44
|
+
file_utils.PROJECT_ROOT = Path(self.temp_dir)
|
|
45
|
+
|
|
46
|
+
def teardown(self):
|
|
47
|
+
"""Cleanup temporary directory"""
|
|
48
|
+
# Restore original
|
|
49
|
+
import config_service
|
|
50
|
+
config_service.PROJECT_ROOT = self.original_root
|
|
51
|
+
import file_utils
|
|
52
|
+
file_utils.PROJECT_ROOT = self.original_file_utils_root
|
|
53
|
+
|
|
54
|
+
if self.temp_dir and os.path.exists(self.temp_dir):
|
|
55
|
+
shutil.rmtree(self.temp_dir)
|
|
56
|
+
|
|
57
|
+
def assert_true(self, condition, msg):
|
|
58
|
+
"""Assert condition is true"""
|
|
59
|
+
if condition:
|
|
60
|
+
self.passed += 1
|
|
61
|
+
print(f" ✓ {msg}")
|
|
62
|
+
else:
|
|
63
|
+
self.failed += 1
|
|
64
|
+
print(f" ✗ {msg}")
|
|
65
|
+
|
|
66
|
+
def assert_false(self, condition, msg):
|
|
67
|
+
"""Assert condition is false"""
|
|
68
|
+
self.assert_true(not condition, msg)
|
|
69
|
+
|
|
70
|
+
def assert_equals(self, a, b, msg):
|
|
71
|
+
"""Assert two values are equal"""
|
|
72
|
+
if a == b:
|
|
73
|
+
self.passed += 1
|
|
74
|
+
print(f" ✓ {msg}")
|
|
75
|
+
else:
|
|
76
|
+
self.failed += 1
|
|
77
|
+
print(f" ✗ {msg}: expected {b}, got {a}")
|
|
78
|
+
|
|
79
|
+
def run_all_tests(self):
|
|
80
|
+
"""Run all test methods"""
|
|
81
|
+
print("\n" + "="*60)
|
|
82
|
+
print("ConfigService Test Suite")
|
|
83
|
+
print("="*60)
|
|
84
|
+
|
|
85
|
+
self.setup()
|
|
86
|
+
try:
|
|
87
|
+
self.test_path_validation()
|
|
88
|
+
self.test_file_type_validation()
|
|
89
|
+
self.test_hash_computation()
|
|
90
|
+
self.test_backup_rotation()
|
|
91
|
+
self.test_conflict_detection()
|
|
92
|
+
self.test_atomic_write()
|
|
93
|
+
self.test_csrf_validation()
|
|
94
|
+
self.test_content_length_validation()
|
|
95
|
+
finally:
|
|
96
|
+
self.teardown()
|
|
97
|
+
|
|
98
|
+
print("\n" + "="*60)
|
|
99
|
+
print(f"Results: {self.passed} passed, {self.failed} failed")
|
|
100
|
+
print("="*60)
|
|
101
|
+
return self.failed == 0
|
|
102
|
+
|
|
103
|
+
def test_path_validation(self):
|
|
104
|
+
"""Test 7.4, 7.10, 7.11: Path validation and traversal prevention"""
|
|
105
|
+
print("\n📁 Testing Path Validation (Tasks 7.4, 7.10, 7.11)")
|
|
106
|
+
|
|
107
|
+
# Valid paths - topologies
|
|
108
|
+
valid_paths = [
|
|
109
|
+
'.tilt-engine/topologies/test.star',
|
|
110
|
+
'.tilt-engine/topologies/tilt/discovery/registry.star',
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
for path in valid_paths:
|
|
114
|
+
result = ConfigService.validate_write_path(path)
|
|
115
|
+
self.assert_true(result is not None, f"Valid path accepted: {path}")
|
|
116
|
+
|
|
117
|
+
# Valid paths - project root master configs (Task 7.11)
|
|
118
|
+
root_configs = [
|
|
119
|
+
'TILT_RESOURCE_DEFAULTS.star',
|
|
120
|
+
'spec.master',
|
|
121
|
+
'.tilt-engine/spec.master',
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for path in root_configs:
|
|
125
|
+
result = ConfigService.validate_write_path(path)
|
|
126
|
+
self.assert_true(result is not None, f"Root config accepted: {path}")
|
|
127
|
+
|
|
128
|
+
# Invalid paths - traversal attempts
|
|
129
|
+
invalid_paths = [
|
|
130
|
+
'../outside.star',
|
|
131
|
+
'.tilt-engine/../../etc/passwd',
|
|
132
|
+
'%2e%2e%2fsecret.star',
|
|
133
|
+
'/etc/passwd',
|
|
134
|
+
'random/file.star',
|
|
135
|
+
'README.md',
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
for path in invalid_paths:
|
|
139
|
+
result = ConfigService.validate_write_path(path)
|
|
140
|
+
self.assert_true(result is None, f"Invalid path rejected: {path}")
|
|
141
|
+
|
|
142
|
+
def test_file_type_validation(self):
|
|
143
|
+
"""Test 7.12: File extension validation"""
|
|
144
|
+
print("\n📄 Testing File Type Validation (Task 7.12)")
|
|
145
|
+
|
|
146
|
+
# Valid file types
|
|
147
|
+
valid_files = [
|
|
148
|
+
'test.star',
|
|
149
|
+
'spec.master',
|
|
150
|
+
'.tilt-engine/spec.master',
|
|
151
|
+
'TILT_RESOURCE_DEFAULTS.star',
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
for path in valid_files:
|
|
155
|
+
is_valid, _ = ConfigService.validate_file_type(path)
|
|
156
|
+
self.assert_true(is_valid, f"Valid file type: {path}")
|
|
157
|
+
|
|
158
|
+
# Invalid file types
|
|
159
|
+
invalid_files = [
|
|
160
|
+
'README.md',
|
|
161
|
+
'file.txt',
|
|
162
|
+
'script.py',
|
|
163
|
+
'config.json',
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
for path in invalid_files:
|
|
167
|
+
is_valid, _ = ConfigService.validate_file_type(path)
|
|
168
|
+
self.assert_false(is_valid, f"Invalid file type rejected: {path}")
|
|
169
|
+
|
|
170
|
+
def test_hash_computation(self):
|
|
171
|
+
"""Test 7.9: Hash computation for conflict detection"""
|
|
172
|
+
print("\n🔐 Testing Hash Computation (Task 7.9)")
|
|
173
|
+
|
|
174
|
+
# Same content produces same hash
|
|
175
|
+
content1 = "Hello, World!"
|
|
176
|
+
hash1 = ConfigService.compute_content_hash(content1)
|
|
177
|
+
hash2 = ConfigService.compute_content_hash(content1)
|
|
178
|
+
self.assert_equals(hash1, hash2, "Same content produces same hash")
|
|
179
|
+
|
|
180
|
+
# Different content produces different hash
|
|
181
|
+
content2 = "Hello, World"
|
|
182
|
+
hash3 = ConfigService.compute_content_hash(content2)
|
|
183
|
+
self.assert_true(hash1 != hash3, "Different content produces different hash")
|
|
184
|
+
|
|
185
|
+
# Hash is SHA-256 (64 hex characters)
|
|
186
|
+
self.assert_equals(len(hash1), 64, "Hash is 64 hex chars (SHA-256)")
|
|
187
|
+
|
|
188
|
+
def test_backup_rotation(self):
|
|
189
|
+
"""Test 7.8: Backup creation and rotation"""
|
|
190
|
+
print("\n💾 Testing Backup Rotation (Task 7.8)")
|
|
191
|
+
|
|
192
|
+
import time
|
|
193
|
+
|
|
194
|
+
# Create test file
|
|
195
|
+
test_file = Path(self.temp_dir) / '.tilt-engine' / 'topologies' / 'backup_test.star'
|
|
196
|
+
test_file.write_text('version 1')
|
|
197
|
+
|
|
198
|
+
# Create multiple backups with small delays to ensure different timestamps
|
|
199
|
+
for i in range(5):
|
|
200
|
+
time.sleep(0.01) # Small delay to ensure different timestamps
|
|
201
|
+
test_file.write_text(f'version {i+2}')
|
|
202
|
+
backup_path, msg = ConfigService.create_backup_with_rotation(test_file, max_backups=3)
|
|
203
|
+
self.assert_true(backup_path is not None, f"Backup {i+1} created: {msg}")
|
|
204
|
+
|
|
205
|
+
# Check only 3 backups exist
|
|
206
|
+
backup_dir = test_file.parent / '.backups'
|
|
207
|
+
if backup_dir.exists():
|
|
208
|
+
backups = list(backup_dir.glob('backup_test.star.*.backup'))
|
|
209
|
+
self.assert_true(len(backups) <= 3, f"Rotation keeps max 3 backups (found {len(backups)})")
|
|
210
|
+
else:
|
|
211
|
+
self.assert_true(False, "Backup directory should exist")
|
|
212
|
+
|
|
213
|
+
def test_conflict_detection(self):
|
|
214
|
+
"""Test 7.9: File content hash conflict detection"""
|
|
215
|
+
print("\n⚠️ Testing Conflict Detection (Task 7.9)")
|
|
216
|
+
|
|
217
|
+
# Create test file
|
|
218
|
+
test_file = Path(self.temp_dir) / '.tilt-engine' / 'topologies' / 'conflict_test.star'
|
|
219
|
+
original_content = "Original content"
|
|
220
|
+
test_file.write_text(original_content)
|
|
221
|
+
|
|
222
|
+
# Get original hash
|
|
223
|
+
original_hash = ConfigService.compute_content_hash(original_content)
|
|
224
|
+
|
|
225
|
+
# No conflict when file unchanged
|
|
226
|
+
has_conflict, msg = ConfigService.check_conflict(test_file, original_hash)
|
|
227
|
+
self.assert_false(has_conflict, "No conflict when file unchanged")
|
|
228
|
+
|
|
229
|
+
# Modify file
|
|
230
|
+
test_file.write_text("Modified content")
|
|
231
|
+
|
|
232
|
+
# Conflict detected after modification
|
|
233
|
+
has_conflict, msg = ConfigService.check_conflict(test_file, original_hash)
|
|
234
|
+
self.assert_true(has_conflict, "Conflict detected after file modification")
|
|
235
|
+
|
|
236
|
+
def test_atomic_write(self):
|
|
237
|
+
"""Test 7.7, 7.20: Atomic write and failure recovery"""
|
|
238
|
+
print("\n📝 Testing Atomic Write (Tasks 7.7, 7.20)")
|
|
239
|
+
|
|
240
|
+
# Use relative path that will pass validation
|
|
241
|
+
rel_path = '.tilt-engine/topologies/atomic_test.star'
|
|
242
|
+
abs_path = Path(self.temp_dir) / rel_path
|
|
243
|
+
|
|
244
|
+
# Verify path validation works
|
|
245
|
+
validated = ConfigService.validate_write_path(rel_path)
|
|
246
|
+
self.assert_true(validated is not None, "Path validation for atomic write")
|
|
247
|
+
|
|
248
|
+
# Test successful write (using relative path)
|
|
249
|
+
result = ConfigService.atomic_write(validated, "Test content", create_backup=False)
|
|
250
|
+
self.assert_true(result.success, f"Atomic write succeeds: {result.message}")
|
|
251
|
+
self.assert_true(abs_path.exists(), "File created")
|
|
252
|
+
self.assert_equals(abs_path.read_text(), "Test content", "Content written correctly")
|
|
253
|
+
|
|
254
|
+
# Test write with backup
|
|
255
|
+
result = ConfigService.atomic_write(validated, "Updated content", create_backup=True)
|
|
256
|
+
self.assert_true(result.success, f"Atomic write with backup succeeds: {result.message}")
|
|
257
|
+
self.assert_true(result.backup_path is not None, "Backup created")
|
|
258
|
+
|
|
259
|
+
# Test conflict detection during write
|
|
260
|
+
original_hash = ConfigService.compute_content_hash("Wrong hash")
|
|
261
|
+
result = ConfigService.atomic_write(
|
|
262
|
+
validated,
|
|
263
|
+
"New content",
|
|
264
|
+
original_hash=original_hash,
|
|
265
|
+
create_backup=False
|
|
266
|
+
)
|
|
267
|
+
self.assert_false(result.success, "Write fails with hash conflict")
|
|
268
|
+
|
|
269
|
+
def test_csrf_validation(self):
|
|
270
|
+
"""Test 7.13: CSRF protection"""
|
|
271
|
+
print("\n🛡️ Testing CSRF Protection (Task 7.13)")
|
|
272
|
+
|
|
273
|
+
# Valid token with localhost origin
|
|
274
|
+
is_valid, _ = ConfigService.validate_csrf_token(
|
|
275
|
+
"valid_token_12345",
|
|
276
|
+
referer="http://localhost:9767/",
|
|
277
|
+
origin="http://localhost:9767"
|
|
278
|
+
)
|
|
279
|
+
self.assert_true(is_valid, "Valid CSRF token with localhost origin")
|
|
280
|
+
|
|
281
|
+
# Invalid token (too short)
|
|
282
|
+
is_valid, _ = ConfigService.validate_csrf_token(
|
|
283
|
+
"short",
|
|
284
|
+
referer="http://localhost:9767/",
|
|
285
|
+
origin="http://localhost:9767"
|
|
286
|
+
)
|
|
287
|
+
self.assert_false(is_valid, "Short token rejected")
|
|
288
|
+
|
|
289
|
+
# Valid token but wrong origin
|
|
290
|
+
is_valid, _ = ConfigService.validate_csrf_token(
|
|
291
|
+
"valid_token_12345",
|
|
292
|
+
referer="https://evil.com/",
|
|
293
|
+
origin="https://evil.com"
|
|
294
|
+
)
|
|
295
|
+
self.assert_false(is_valid, "Wrong origin rejected")
|
|
296
|
+
|
|
297
|
+
def test_content_length_validation(self):
|
|
298
|
+
"""Test content length limits"""
|
|
299
|
+
print("\n📏 Testing Content Length Validation")
|
|
300
|
+
|
|
301
|
+
# Valid content length
|
|
302
|
+
is_valid, _ = ConfigService.validate_request_content_length("1024")
|
|
303
|
+
self.assert_true(is_valid, "Valid content length accepted")
|
|
304
|
+
|
|
305
|
+
# Too large
|
|
306
|
+
is_valid, _ = ConfigService.validate_request_content_length(str(2 * 1024 * 1024))
|
|
307
|
+
self.assert_false(is_valid, "Oversized content rejected")
|
|
308
|
+
|
|
309
|
+
# Missing
|
|
310
|
+
is_valid, _ = ConfigService.validate_request_content_length(None)
|
|
311
|
+
self.assert_false(is_valid, "Missing content length rejected")
|
|
312
|
+
|
|
313
|
+
# Invalid format
|
|
314
|
+
is_valid, _ = ConfigService.validate_request_content_length("invalid")
|
|
315
|
+
self.assert_false(is_valid, "Invalid content length rejected")
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def run_tests():
|
|
319
|
+
"""Run all tests and return exit code"""
|
|
320
|
+
tester = TestConfigService()
|
|
321
|
+
success = tester.run_all_tests()
|
|
322
|
+
return 0 if success else 1
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == '__main__':
|
|
326
|
+
sys.exit(run_tests())
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tilt integration utilities for IDE components
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from file_utils import PROJECT_ROOT
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TiltIntegration:
|
|
13
|
+
"""Utilities for calling Tilt commands from Python"""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def trigger_resource(resource_name: str) -> dict:
|
|
17
|
+
"""
|
|
18
|
+
Trigger a Tilt resource (e.g., config-gen for a service)
|
|
19
|
+
|
|
20
|
+
Returns: {'success': bool, 'output': str, 'error': str}
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
result = subprocess.run(
|
|
24
|
+
['tilt', 'trigger', resource_name],
|
|
25
|
+
cwd=PROJECT_ROOT,
|
|
26
|
+
capture_output=True,
|
|
27
|
+
text=True,
|
|
28
|
+
timeout=30
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
'success': result.returncode == 0,
|
|
33
|
+
'output': result.stdout,
|
|
34
|
+
'error': result.stderr if result.returncode != 0 else None
|
|
35
|
+
}
|
|
36
|
+
except subprocess.TimeoutExpired:
|
|
37
|
+
return {'success': False, 'output': '', 'error': 'Command timed out'}
|
|
38
|
+
except FileNotFoundError:
|
|
39
|
+
return {'success': False, 'output': '', 'error': 'tilt command not found. Is Tilt installed?'}
|
|
40
|
+
except Exception as e:
|
|
41
|
+
return {'success': False, 'output': '', 'error': str(e)}
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def get_resources() -> list:
|
|
45
|
+
"""
|
|
46
|
+
Get list of all Tilt resources
|
|
47
|
+
|
|
48
|
+
Returns: List of resource names
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
['tilt', 'get', 'uiresources', '-o', 'json'],
|
|
53
|
+
cwd=PROJECT_ROOT,
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
timeout=10
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if result.returncode != 0:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
data = json.loads(result.stdout)
|
|
63
|
+
resources = []
|
|
64
|
+
|
|
65
|
+
for item in data.get('items', []):
|
|
66
|
+
name = item.get('metadata', {}).get('name', '')
|
|
67
|
+
if name:
|
|
68
|
+
resources.append(name)
|
|
69
|
+
|
|
70
|
+
return resources
|
|
71
|
+
except Exception:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def get_resource_status(resource_name: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
Get status of a specific Tilt resource
|
|
78
|
+
|
|
79
|
+
Returns: {'exists': bool, 'status': str, 'error': str}
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
result = subprocess.run(
|
|
83
|
+
['tilt', 'get', 'uiresources', resource_name, '-o', 'json'],
|
|
84
|
+
cwd=PROJECT_ROOT,
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
timeout=10
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if result.returncode != 0:
|
|
91
|
+
return {'exists': False, 'status': 'unknown', 'error': result.stderr}
|
|
92
|
+
|
|
93
|
+
data = json.loads(result.stdout)
|
|
94
|
+
status = data.get('status', {}).get('runtimeStatus', 'unknown')
|
|
95
|
+
|
|
96
|
+
return {'exists': True, 'status': status, 'error': None}
|
|
97
|
+
except Exception as e:
|
|
98
|
+
return {'exists': False, 'status': 'unknown', 'error': str(e)}
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def regenerate_resource_configs(resource_name: str) -> dict:
|
|
102
|
+
"""
|
|
103
|
+
Trigger config regeneration for a service
|
|
104
|
+
|
|
105
|
+
Tries common config-gen resource naming patterns:
|
|
106
|
+
- <service>-config-gen
|
|
107
|
+
- <service>-yaml
|
|
108
|
+
|
|
109
|
+
Returns: {'success': bool, 'message': str}
|
|
110
|
+
"""
|
|
111
|
+
# Try different resource name patterns
|
|
112
|
+
patterns = [
|
|
113
|
+
f'{resource_name}-config-gen',
|
|
114
|
+
f'{resource_name}-yaml',
|
|
115
|
+
f'{resource_name}-config',
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
for pattern in patterns:
|
|
119
|
+
result = TiltIntegration.trigger_resource(pattern)
|
|
120
|
+
if result['success']:
|
|
121
|
+
return {
|
|
122
|
+
'success': True,
|
|
123
|
+
'message': f'Regenerated configs for {resource_name} via {pattern}'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
'success': False,
|
|
128
|
+
'message': f'Could not find config-gen resource for {resource_name}. Tried: {", ".join(patterns)}'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def is_tilt_running() -> bool:
|
|
133
|
+
"""Check if Tilt is currently running"""
|
|
134
|
+
try:
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
['tilt', 'status'],
|
|
137
|
+
cwd=PROJECT_ROOT,
|
|
138
|
+
capture_output=True,
|
|
139
|
+
text=True,
|
|
140
|
+
timeout=5
|
|
141
|
+
)
|
|
142
|
+
return result.returncode == 0
|
|
143
|
+
except Exception:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == '__main__':
|
|
148
|
+
# Test
|
|
149
|
+
print(f"Project root: {PROJECT_ROOT}")
|
|
150
|
+
print(f"Tilt running: {TiltIntegration.is_tilt_running()}")
|
|
151
|
+
|
|
152
|
+
resources = TiltIntegration.get_resources()
|
|
153
|
+
print(f"Found {len(resources)} resources")
|
|
154
|
+
|
|
155
|
+
# Show first 5
|
|
156
|
+
for name in resources[:5]:
|
|
157
|
+
status = TiltIntegration.get_resource_status(name)
|
|
158
|
+
print(f" - {name}: {status['status']}")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# Cron Jobs Status HTTP Server
|
|
4
|
+
# Serves cron job status as JSON for the UI extension
|
|
5
|
+
# ============================================================================
|
|
6
|
+
|
|
7
|
+
STATUS_FILE="/tmp/cron-jobs-status.json"
|
|
8
|
+
PORT=10352
|
|
9
|
+
|
|
10
|
+
echo "🌐 Cron Jobs Status Server starting on port ${PORT}..."
|
|
11
|
+
|
|
12
|
+
# Simple HTTP server using Python
|
|
13
|
+
python3 << 'PYEOF'
|
|
14
|
+
import http.server
|
|
15
|
+
import socketserver
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
STATUS_FILE = "/tmp/cron-jobs-status.json"
|
|
20
|
+
PORT = 10352
|
|
21
|
+
|
|
22
|
+
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
23
|
+
def do_GET(self):
|
|
24
|
+
if self.path == '/api/cron-jobs-status':
|
|
25
|
+
self.send_response(200)
|
|
26
|
+
self.send_header('Content-Type', 'application/json')
|
|
27
|
+
self.send_header('Access-Control-Allow-Origin', '*')
|
|
28
|
+
self.end_headers()
|
|
29
|
+
|
|
30
|
+
# Read status file or return default
|
|
31
|
+
if os.path.exists(STATUS_FILE):
|
|
32
|
+
with open(STATUS_FILE, 'r') as f:
|
|
33
|
+
data = f.read()
|
|
34
|
+
self.wfile.write(data.encode())
|
|
35
|
+
else:
|
|
36
|
+
default = {"jobs": [], "updatedAt": None}
|
|
37
|
+
self.wfile.write(json.dumps(default).encode())
|
|
38
|
+
else:
|
|
39
|
+
self.send_response(404)
|
|
40
|
+
self.end_headers()
|
|
41
|
+
|
|
42
|
+
def log_message(self, format, *args):
|
|
43
|
+
# Suppress log messages
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
|
47
|
+
print(f"Cron Jobs Status Server running on port {PORT}")
|
|
48
|
+
httpd.serve_forever()
|
|
49
|
+
PYEOF
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# Cron Jobs Status Tracker
|
|
4
|
+
# Tracks execution status of all cron jobs
|
|
5
|
+
# ============================================================================
|
|
6
|
+
|
|
7
|
+
STATUS_FILE="/tmp/cron-jobs-status.json"
|
|
8
|
+
CONTROL_DIR="/tmp/cron-jobs-control"
|
|
9
|
+
LOG_DIR="/tmp/cron-jobs-logs"
|
|
10
|
+
|
|
11
|
+
# Ensure directories exist
|
|
12
|
+
mkdir -p "$CONTROL_DIR" "$LOG_DIR"
|
|
13
|
+
|
|
14
|
+
# Initialize status file
|
|
15
|
+
init_status() {
|
|
16
|
+
cat > "$STATUS_FILE" << 'EOF'
|
|
17
|
+
{
|
|
18
|
+
"jobs": [
|
|
19
|
+
{
|
|
20
|
+
"name": "cron-lint",
|
|
21
|
+
"status": "idle",
|
|
22
|
+
"lastRun": null,
|
|
23
|
+
"nextRun": null,
|
|
24
|
+
"history": []
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "cron-format",
|
|
28
|
+
"status": "idle",
|
|
29
|
+
"lastRun": null,
|
|
30
|
+
"nextRun": null,
|
|
31
|
+
"history": []
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "auto-prune-dangling",
|
|
35
|
+
"status": "running",
|
|
36
|
+
"lastRun": null,
|
|
37
|
+
"nextRun": null,
|
|
38
|
+
"history": []
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "auto-prune-migrator-images",
|
|
42
|
+
"status": "running",
|
|
43
|
+
"lastRun": null,
|
|
44
|
+
"nextRun": null,
|
|
45
|
+
"history": []
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"updatedAt": null
|
|
49
|
+
}
|
|
50
|
+
EOF
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Update job status
|
|
54
|
+
update_job() {
|
|
55
|
+
local name=$1
|
|
56
|
+
local status=$2
|
|
57
|
+
local duration=$3
|
|
58
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
59
|
+
|
|
60
|
+
# Read current status
|
|
61
|
+
local current=$(cat "$STATUS_FILE" 2>/dev/null || echo '{"jobs":[]}')
|
|
62
|
+
|
|
63
|
+
# Calculate next run (60 seconds from now for cron jobs)
|
|
64
|
+
local nextRun=$(date -u -v+60S +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d '+60 seconds' +"%Y-%m-%dT%H:%M:%SZ")
|
|
65
|
+
|
|
66
|
+
# Update using Python for reliable JSON manipulation
|
|
67
|
+
python3 << PYEOF
|
|
68
|
+
import json
|
|
69
|
+
import sys
|
|
70
|
+
from datetime import datetime, timedelta
|
|
71
|
+
|
|
72
|
+
data = json.loads('''$current''')
|
|
73
|
+
job_name = '''$name'''
|
|
74
|
+
status = '''$status'''
|
|
75
|
+
timestamp = '''$timestamp'''
|
|
76
|
+
duration = '''$duration'''
|
|
77
|
+
next_run = '''$nextRun'''
|
|
78
|
+
|
|
79
|
+
# Find and update job
|
|
80
|
+
for job in data.get('jobs', []):
|
|
81
|
+
if job['name'] == job_name:
|
|
82
|
+
job['status'] = status
|
|
83
|
+
job['lastRun'] = timestamp
|
|
84
|
+
job['nextRun'] = next_run
|
|
85
|
+
|
|
86
|
+
# Add to history
|
|
87
|
+
if 'history' not in job:
|
|
88
|
+
job['history'] = []
|
|
89
|
+
|
|
90
|
+
job['history'].insert(0, {
|
|
91
|
+
'time': timestamp,
|
|
92
|
+
'status': status,
|
|
93
|
+
'duration': duration if duration else 'N/A'
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
# Keep only last 10 entries
|
|
97
|
+
job['history'] = job['history'][:10]
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
data['updatedAt'] = timestamp
|
|
101
|
+
print(json.dumps(data, indent=2))
|
|
102
|
+
PYEOF
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Check if job should pause
|
|
106
|
+
check_pause() {
|
|
107
|
+
local name=$1
|
|
108
|
+
if [[ -f "$CONTROL_DIR/$name.pause" ]]; then
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Check if manual trigger requested
|
|
115
|
+
check_trigger() {
|
|
116
|
+
local name=$1
|
|
117
|
+
if [[ -f "$CONTROL_DIR/$name.trigger" ]]; then
|
|
118
|
+
rm "$CONTROL_DIR/$name.trigger"
|
|
119
|
+
return 0
|
|
120
|
+
fi
|
|
121
|
+
return 1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Main execution
|
|
125
|
+
command=$1
|
|
126
|
+
shift
|
|
127
|
+
|
|
128
|
+
case $command in
|
|
129
|
+
init)
|
|
130
|
+
init_status
|
|
131
|
+
echo "Status file initialized at $STATUS_FILE"
|
|
132
|
+
;;
|
|
133
|
+
update)
|
|
134
|
+
update_job "$1" "$2" "$3" > "${STATUS_FILE}.tmp" && mv "${STATUS_FILE}.tmp" "$STATUS_FILE"
|
|
135
|
+
;;
|
|
136
|
+
pause-check)
|
|
137
|
+
check_pause "$1"
|
|
138
|
+
;;
|
|
139
|
+
trigger-check)
|
|
140
|
+
check_trigger "$1"
|
|
141
|
+
;;
|
|
142
|
+
*)
|
|
143
|
+
echo "Usage: $0 {init|update <name> <status> [duration]|pause-check <name>|trigger-check <name>}"
|
|
144
|
+
exit 1
|
|
145
|
+
;;
|
|
146
|
+
esac
|