utils_devops 0.1.157__tar.gz → 0.1.159__tar.gz
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.
- {utils_devops-0.1.157 → utils_devops-0.1.159}/PKG-INFO +1 -1
- {utils_devops-0.1.157 → utils_devops-0.1.159}/pyproject.toml +1 -1
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/docker_ops.py +229 -3
- {utils_devops-0.1.157 → utils_devops-0.1.159}/README.md +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/__init__.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/__init__.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/datetimes.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/envs.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/files.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/logs.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/script_helpers.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/strings.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/systems.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/__init__.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/aws_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/git_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/interaction_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/metrics_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/network_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/nginx_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/notification_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/performance_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/ssh_ops.py +0 -0
- {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/vault_ops.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: utils_devops
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.159
|
|
4
4
|
Summary: Lightweight DevOps utilities for automation scripts: config editing (YAML/JSON/INI/.env), templating, diffing, and CLI tools
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: devops,automation,nginx,cli,jinja2,yaml,config,diff,templating,logging,docker,compose,file-ops
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "utils_devops"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.159" # Bumped for new string features + diffing
|
|
4
4
|
description = "Lightweight DevOps utilities for automation scripts: config editing (YAML/JSON/INI/.env), templating, diffing, and CLI tools"
|
|
5
5
|
authors = ["Hamed Sheikhan <sh.sheikhan.m@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -8,7 +8,6 @@ import json
|
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
from logging import Logger
|
|
11
|
-
import re
|
|
12
11
|
import sys
|
|
13
12
|
import tarfile
|
|
14
13
|
import tempfile
|
|
@@ -618,7 +617,7 @@ def compose_restart(compose_file: str, remove_volumes: bool = False,
|
|
|
618
617
|
|
|
619
618
|
def compose_ps(compose_file: str, project_name: Optional[str] = None, env_file: Optional[str] = None) -> List[ContainerInfo]:
|
|
620
619
|
"""List compose services status using detected compose command"""
|
|
621
|
-
result = run_compose_command(compose_file, ["ps", "--format", "json"], project_name , env_file)
|
|
620
|
+
result = run_compose_command(compose_file, ["ps" ,"-a", "--format", "json"], project_name , env_file)
|
|
622
621
|
|
|
623
622
|
containers = []
|
|
624
623
|
try:
|
|
@@ -909,7 +908,7 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
|
|
|
909
908
|
"""
|
|
910
909
|
try:
|
|
911
910
|
# Run docker compose ps with JSON format
|
|
912
|
-
result = run_compose_command(compose_file, ["ps", "--format", "json"])
|
|
911
|
+
result = run_compose_command(compose_file, ["ps","-a", "--format", "json"])
|
|
913
912
|
if result.rc != 0:
|
|
914
913
|
return {"healthy": False, "error": f"docker compose ps failed: {result.stderr}"}
|
|
915
914
|
|
|
@@ -2200,6 +2199,233 @@ def push_compose(
|
|
|
2200
2199
|
logger.error(f"💥 Push {push_id} failed: {e}")
|
|
2201
2200
|
return result
|
|
2202
2201
|
|
|
2202
|
+
def playwright_test_compose(
|
|
2203
|
+
compose_file: str,
|
|
2204
|
+
playwright_compose_file: str,
|
|
2205
|
+
# Environment configuration
|
|
2206
|
+
env_file: str = '.env',
|
|
2207
|
+
update_version: bool = False,
|
|
2208
|
+
version_source: str = 'git',
|
|
2209
|
+
# Services selection
|
|
2210
|
+
services: Optional[List[str]] = None,
|
|
2211
|
+
playwright_services: Optional[List[str]] = None,
|
|
2212
|
+
# Health / timing
|
|
2213
|
+
health_timeout: int = 500,
|
|
2214
|
+
health_interval: int = 10,
|
|
2215
|
+
# Logging
|
|
2216
|
+
capture_logs: bool = True,
|
|
2217
|
+
log_tail: int = 200,
|
|
2218
|
+
# Compose startup options (delegated to test_compose)
|
|
2219
|
+
pull_missing: bool = True,
|
|
2220
|
+
no_build: bool = True,
|
|
2221
|
+
no_pull: bool = True,
|
|
2222
|
+
keep_compose_up: bool = False,
|
|
2223
|
+
dry_run: bool = False,
|
|
2224
|
+
project_name: Optional[str] = None,
|
|
2225
|
+
logger: Optional[logger] = None,
|
|
2226
|
+
) -> Dict[str, Any]:
|
|
2227
|
+
"""
|
|
2228
|
+
Specialized workflow to run a Playwright-driven integration test that depends on an
|
|
2229
|
+
application compose and a separate Playwright compose. This function reuses the
|
|
2230
|
+
existing `test_compose` function to perform validation, startup and health checks for
|
|
2231
|
+
the application compose.
|
|
2232
|
+
Behavior summary:
|
|
2233
|
+
1. Validate inputs and (optionally) update version as in test_compose.
|
|
2234
|
+
2. Set STATIC_CAPTCHA=true in the system process environment (without modifying the file).
|
|
2235
|
+
3. Call test_compose(...) against `compose_file` with keep_compose_up=True
|
|
2236
|
+
so the primary application stack is up and healthy.
|
|
2237
|
+
4. Run compose_up on the Playwright compose file to start the test runner.
|
|
2238
|
+
5. Follow logs (simulating -f) for the Playwright service(s) while polling the compose state
|
|
2239
|
+
to detect completion, and determine pass/fail from exit status and log patterns.
|
|
2240
|
+
6. Return a detailed result dict similar in structure to test_compose.
|
|
2241
|
+
Notes:
|
|
2242
|
+
- Reuses `test_compose` for application startup and health checks.
|
|
2243
|
+
- Sets STATIC_CAPTCHA in the process environment only (os.environ).
|
|
2244
|
+
- Does not automatically bring services down; that is left to the caller.
|
|
2245
|
+
- Success is determined by all containers exiting with code 0 and/or presence of success patterns in logs.
|
|
2246
|
+
Returns:
|
|
2247
|
+
Dict with keys: test_id, success, steps, app_result, logs_captured, duration, error
|
|
2248
|
+
"""
|
|
2249
|
+
logger = logger or DEFAULT_LOGGER
|
|
2250
|
+
test_id = str(uuid.uuid4())[:8]
|
|
2251
|
+
start_time = dt_ops.current_datetime()
|
|
2252
|
+
result = {
|
|
2253
|
+
"test_id": test_id,
|
|
2254
|
+
"success": False,
|
|
2255
|
+
"steps": {},
|
|
2256
|
+
"app_result": None,
|
|
2257
|
+
"logs_captured": {},
|
|
2258
|
+
"duration": 0.0,
|
|
2259
|
+
"error": None,
|
|
2260
|
+
}
|
|
2261
|
+
#project_name_playwright = f"playwright-{test_id[:8]}"
|
|
2262
|
+
project_name_playwright = project_name
|
|
2263
|
+
try:
|
|
2264
|
+
logger.info(f"▶️ Playwright test workflow starting ({test_id})")
|
|
2265
|
+
logger.info(f" Compose files: app={compose_file} playwright={playwright_compose_file}")
|
|
2266
|
+
# Step 0: Basic validation
|
|
2267
|
+
logger.info("Step 0: validating compose and env files")
|
|
2268
|
+
if not files.file_exists(compose_file):
|
|
2269
|
+
raise DockerOpsError(f"Application compose file not found: {compose_file}")
|
|
2270
|
+
if not files.file_exists(playwright_compose_file):
|
|
2271
|
+
raise DockerOpsError(f"Playwright compose file not found: {playwright_compose_file}")
|
|
2272
|
+
# Step 1: Optional version update
|
|
2273
|
+
if update_version and not dry_run:
|
|
2274
|
+
logger.info("Step 1: updating version in env (requested)")
|
|
2275
|
+
new_version = _update_env_version(env_file=env_file, source=version_source, logger=logger)
|
|
2276
|
+
if new_version:
|
|
2277
|
+
logger.info(f" Version updated to {new_version}")
|
|
2278
|
+
# make sure runtime env sees new values
|
|
2279
|
+
envs.import_env_to_system(get_env_compose(env_file))
|
|
2280
|
+
elif update_version and dry_run:
|
|
2281
|
+
logger.info("Step 1: version update skipped (dry run)")
|
|
2282
|
+
else:
|
|
2283
|
+
logger.info("Step 1: version update not requested")
|
|
2284
|
+
# Step 2: Set STATIC_CAPTCHA=true in system process env
|
|
2285
|
+
logger.info("Step 2: setting STATIC_CAPTCHA=true in process environment")
|
|
2286
|
+
if not dry_run:
|
|
2287
|
+
envs.set_system_env(key='STATIC_CAPTCHA', value='true')
|
|
2288
|
+
else:
|
|
2289
|
+
logger.info("Step 2: env set skipped (dry run)")
|
|
2290
|
+
# Step 3: Start application compose using test_compose but keep services up
|
|
2291
|
+
logger.info("Step 3: bringing up application stack (health checks via test_compose)")
|
|
2292
|
+
if not dry_run:
|
|
2293
|
+
app_result = test_compose(
|
|
2294
|
+
compose_file=compose_file,
|
|
2295
|
+
env_file=env_file,
|
|
2296
|
+
update_version=False, # already handled
|
|
2297
|
+
services=services,
|
|
2298
|
+
health_timeout=health_timeout,
|
|
2299
|
+
health_interval=health_interval,
|
|
2300
|
+
capture_logs=capture_logs,
|
|
2301
|
+
log_tail=log_tail,
|
|
2302
|
+
pull_missing=pull_missing,
|
|
2303
|
+
no_build=no_build,
|
|
2304
|
+
no_pull=no_pull,
|
|
2305
|
+
keep_compose_up=True, # DO NOT tear down
|
|
2306
|
+
project_name=project_name,
|
|
2307
|
+
dry_run=dry_run,
|
|
2308
|
+
logger=logger,
|
|
2309
|
+
)
|
|
2310
|
+
result['app_result'] = app_result
|
|
2311
|
+
if not app_result.get('success'):
|
|
2312
|
+
raise DockerOpsError("Application compose failed to start or pass health checks")
|
|
2313
|
+
logger.info(" Application stack is up and healthy")
|
|
2314
|
+
else:
|
|
2315
|
+
logger.info("Step 3: application start skipped (dry run)")
|
|
2316
|
+
# Step 4: Start Playwright compose using compose_up with unique project name
|
|
2317
|
+
logger.info("Step 4: bringing up Playwright runner stack with compose_up")
|
|
2318
|
+
if not dry_run:
|
|
2319
|
+
up_success = compose_up(
|
|
2320
|
+
playwright_compose_file,
|
|
2321
|
+
services=playwright_services,
|
|
2322
|
+
project_name=project_name_playwright,
|
|
2323
|
+
)
|
|
2324
|
+
if not up_success:
|
|
2325
|
+
raise DockerOpsError("Playwright compose_up failed")
|
|
2326
|
+
logger.info(" Playwright stack started - now following logs until completion")
|
|
2327
|
+
else:
|
|
2328
|
+
logger.info("Step 4: playwright start skipped (dry run)")
|
|
2329
|
+
# Step 5: Follow logs and wait for completion (simulate logs -f with polling)
|
|
2330
|
+
if not dry_run and capture_logs:
|
|
2331
|
+
logger.info("Step 5: following Playwright logs and waiting for completion")
|
|
2332
|
+
pw_services = playwright_services or list(read_compose_file(playwright_compose_file, env_file).get('services', {}).keys())
|
|
2333
|
+
logs_acc = {s: [] for s in pw_services}
|
|
2334
|
+
deadline = time.time() + health_timeout
|
|
2335
|
+
finished = False
|
|
2336
|
+
timed_out = False
|
|
2337
|
+
time.sleep(5) # Give time for container to start producing logs
|
|
2338
|
+
while not finished and not timed_out:
|
|
2339
|
+
# Fetch current log snapshot
|
|
2340
|
+
for svc in pw_services:
|
|
2341
|
+
try:
|
|
2342
|
+
lines = [l.message for l in compose_logs(playwright_compose_file, services=[svc], tail=log_tail, project_name=project_name_playwright,follow=True)]
|
|
2343
|
+
except Exception as e:
|
|
2344
|
+
lines = [f"Error fetching logs: {str(e)}"]
|
|
2345
|
+
# Append new unique lines
|
|
2346
|
+
for ln in lines:
|
|
2347
|
+
if ln not in logs_acc[svc]:
|
|
2348
|
+
logs_acc[svc].append(ln)
|
|
2349
|
+
logger.info(f"[{svc}] {ln}") # Simulate real-time logging
|
|
2350
|
+
# Check if all services have exited
|
|
2351
|
+
ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
|
|
2352
|
+
running = any(
|
|
2353
|
+
'running' in (c.status or '').lower() or 'up' in (c.status or '').lower()
|
|
2354
|
+
for c in ps if (c.service or '') in pw_services
|
|
2355
|
+
)
|
|
2356
|
+
if not running:
|
|
2357
|
+
finished = True
|
|
2358
|
+
if time.time() > deadline:
|
|
2359
|
+
timed_out = True
|
|
2360
|
+
if not finished and not timed_out:
|
|
2361
|
+
time.sleep(health_interval)
|
|
2362
|
+
result['logs_captured'] = logs_acc
|
|
2363
|
+
result['steps']['playwright_logs'] = {
|
|
2364
|
+
'finished': finished,
|
|
2365
|
+
'timed_out': timed_out,
|
|
2366
|
+
}
|
|
2367
|
+
if timed_out:
|
|
2368
|
+
raise DockerOpsError("Playwright runner timed out")
|
|
2369
|
+
else:
|
|
2370
|
+
if dry_run:
|
|
2371
|
+
logger.info("Step 5: log following skipped (dry run)")
|
|
2372
|
+
# Step 6: Analyze outcome
|
|
2373
|
+
logger.info("Step 6: analyzing Playwright runner outcome")
|
|
2374
|
+
if not dry_run:
|
|
2375
|
+
# Get exit codes
|
|
2376
|
+
ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
|
|
2377
|
+
exit_codes = {}
|
|
2378
|
+
for c in ps:
|
|
2379
|
+
if (c.service or '') in pw_services:
|
|
2380
|
+
exit_code = getattr(c, 'exit_code', None) or 1 # Assume non-zero if missing
|
|
2381
|
+
exit_codes[c.service] = exit_code
|
|
2382
|
+
all_success = all(code == 0 for code in exit_codes.values())
|
|
2383
|
+
# Log analysis
|
|
2384
|
+
aggregated_logs = '\n'.join(' '.join(v) for v in logs_acc.values())
|
|
2385
|
+
failed = any(re.search(pat, aggregated_logs, re.I) for pat in ['FAILED', 'FAIL', 'ERROR', 'AssertionError', 'Test Failed'])
|
|
2386
|
+
passed = any(re.search(pat, aggregated_logs, re.I) for pat in ['All tests passed', 'Tests passed', 'PASS'])
|
|
2387
|
+
if all_success and (passed or not failed):
|
|
2388
|
+
result['success'] = True
|
|
2389
|
+
logger.info(f"Playwright run successful ({test_id})")
|
|
2390
|
+
else:
|
|
2391
|
+
result['success'] = False
|
|
2392
|
+
result['error'] = 'Playwright tests failed based on exit codes or logs'
|
|
2393
|
+
logger.error(f"Playwright run failed ({test_id})")
|
|
2394
|
+
result['steps']['analysis'] = {
|
|
2395
|
+
'exit_codes': exit_codes,
|
|
2396
|
+
'passed_patterns': passed,
|
|
2397
|
+
'failed_patterns': failed,
|
|
2398
|
+
}
|
|
2399
|
+
else:
|
|
2400
|
+
result['success'] = True # Assume success in dry run
|
|
2401
|
+
result['duration'] = (dt_ops.current_datetime() - start_time).total_seconds()
|
|
2402
|
+
if not keep_compose_up :
|
|
2403
|
+
compose_down(
|
|
2404
|
+
compose_file=playwright_compose_file,
|
|
2405
|
+
project_name=project_name_playwright
|
|
2406
|
+
)
|
|
2407
|
+
logger.info("Playwright compose down")
|
|
2408
|
+
compose_down(
|
|
2409
|
+
compose_file=compose_file,
|
|
2410
|
+
)
|
|
2411
|
+
logger.info("all servise compose down")
|
|
2412
|
+
return result
|
|
2413
|
+
except Exception as e:
|
|
2414
|
+
result['duration'] = (dt_ops.current_datetime() - start_time).total_seconds()
|
|
2415
|
+
result['error'] = str(e)
|
|
2416
|
+
logger.error(f"Playwright test workflow failed ({test_id}): {e}")
|
|
2417
|
+
if not keep_compose_up :
|
|
2418
|
+
compose_down(
|
|
2419
|
+
compose_file=playwright_compose_file,
|
|
2420
|
+
project_name=project_name_playwright
|
|
2421
|
+
)
|
|
2422
|
+
logger.info("Playwright compose down")
|
|
2423
|
+
compose_down(
|
|
2424
|
+
compose_file=compose_file,
|
|
2425
|
+
)
|
|
2426
|
+
logger.info("all servise compose down")
|
|
2427
|
+
return result
|
|
2428
|
+
|
|
2203
2429
|
|
|
2204
2430
|
def test_compose(
|
|
2205
2431
|
compose_file: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|