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.
Files changed (24) hide show
  1. {utils_devops-0.1.157 → utils_devops-0.1.159}/PKG-INFO +1 -1
  2. {utils_devops-0.1.157 → utils_devops-0.1.159}/pyproject.toml +1 -1
  3. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/docker_ops.py +229 -3
  4. {utils_devops-0.1.157 → utils_devops-0.1.159}/README.md +0 -0
  5. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/__init__.py +0 -0
  6. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/__init__.py +0 -0
  7. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/datetimes.py +0 -0
  8. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/envs.py +0 -0
  9. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/files.py +0 -0
  10. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/logs.py +0 -0
  11. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/script_helpers.py +0 -0
  12. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/strings.py +0 -0
  13. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/core/systems.py +0 -0
  14. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/__init__.py +0 -0
  15. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/aws_ops.py +0 -0
  16. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/git_ops.py +0 -0
  17. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/interaction_ops.py +0 -0
  18. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/metrics_ops.py +0 -0
  19. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/network_ops.py +0 -0
  20. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/nginx_ops.py +0 -0
  21. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/notification_ops.py +0 -0
  22. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/performance_ops.py +0 -0
  23. {utils_devops-0.1.157 → utils_devops-0.1.159}/src/utils_devops/extras/ssh_ops.py +0 -0
  24. {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.157
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.157" # Bumped for new string features + diffing
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