utils_devops 0.1.159__tar.gz → 0.1.160__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.159 → utils_devops-0.1.160}/PKG-INFO +1 -1
  2. {utils_devops-0.1.159 → utils_devops-0.1.160}/pyproject.toml +1 -1
  3. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/docker_ops.py +213 -100
  4. {utils_devops-0.1.159 → utils_devops-0.1.160}/README.md +0 -0
  5. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/__init__.py +0 -0
  6. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/__init__.py +0 -0
  7. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/datetimes.py +0 -0
  8. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/envs.py +0 -0
  9. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/files.py +0 -0
  10. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/logs.py +0 -0
  11. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/script_helpers.py +0 -0
  12. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/strings.py +0 -0
  13. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/core/systems.py +0 -0
  14. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/__init__.py +0 -0
  15. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/aws_ops.py +0 -0
  16. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/git_ops.py +0 -0
  17. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/interaction_ops.py +0 -0
  18. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/metrics_ops.py +0 -0
  19. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/network_ops.py +0 -0
  20. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/nginx_ops.py +0 -0
  21. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/notification_ops.py +0 -0
  22. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/performance_ops.py +0 -0
  23. {utils_devops-0.1.159 → utils_devops-0.1.160}/src/utils_devops/extras/ssh_ops.py +0 -0
  24. {utils_devops-0.1.159 → utils_devops-0.1.160}/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.159
3
+ Version: 0.1.160
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.159" # Bumped for new string features + diffing
3
+ version = "0.1.160" # 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"
@@ -2229,6 +2229,7 @@ def playwright_test_compose(
2229
2229
  application compose and a separate Playwright compose. This function reuses the
2230
2230
  existing `test_compose` function to perform validation, startup and health checks for
2231
2231
  the application compose.
2232
+
2232
2233
  Behavior summary:
2233
2234
  1. Validate inputs and (optionally) update version as in test_compose.
2234
2235
  2. Set STATIC_CAPTCHA=true in the system process environment (without modifying the file).
@@ -2238,17 +2239,34 @@ def playwright_test_compose(
2238
2239
  5. Follow logs (simulating -f) for the Playwright service(s) while polling the compose state
2239
2240
  to detect completion, and determine pass/fail from exit status and log patterns.
2240
2241
  6. Return a detailed result dict similar in structure to test_compose.
2242
+
2241
2243
  Notes:
2242
2244
  - Reuses `test_compose` for application startup and health checks.
2243
2245
  - Sets STATIC_CAPTCHA in the process environment only (os.environ).
2244
2246
  - Does not automatically bring services down; that is left to the caller.
2245
2247
  - Success is determined by all containers exiting with code 0 and/or presence of success patterns in logs.
2248
+
2246
2249
  Returns:
2247
2250
  Dict with keys: test_id, success, steps, app_result, logs_captured, duration, error
2248
2251
  """
2252
+
2253
+ # Minimal sticker set - only what's necessary
2254
+ STICKERS = {
2255
+ 'start': '🎬',
2256
+ 'success': '✅',
2257
+ 'error': '❌',
2258
+ 'info': 'ℹ️',
2259
+ 'waiting': '⏳',
2260
+ 'complete': '🏁',
2261
+ 'logs': '📝',
2262
+ 'analysis': '📊',
2263
+ 'cleanup': '🧹',
2264
+ }
2265
+
2249
2266
  logger = logger or DEFAULT_LOGGER
2250
2267
  test_id = str(uuid.uuid4())[:8]
2251
2268
  start_time = dt_ops.current_datetime()
2269
+
2252
2270
  result = {
2253
2271
  "test_id": test_id,
2254
2272
  "success": False,
@@ -2258,42 +2276,50 @@ def playwright_test_compose(
2258
2276
  "duration": 0.0,
2259
2277
  "error": None,
2260
2278
  }
2261
- #project_name_playwright = f"playwright-{test_id[:8]}"
2279
+
2262
2280
  project_name_playwright = project_name
2281
+
2263
2282
  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")
2283
+ # Start header - clean and professional
2284
+ logger.info(f"{STICKERS['start']} Playwright test starting (ID: {test_id})")
2285
+ logger.info(f"{STICKERS['info']} App: {compose_file}")
2286
+ logger.info(f"{STICKERS['info']} Playwright: {playwright_compose_file}")
2287
+ if dry_run:
2288
+ logger.info(f"{STICKERS['info']} Dry run mode")
2289
+ logger.info("")
2290
+
2291
+ # STEP 0: Validation
2292
+ logger.info(f"{STICKERS['info']} STEP 0: Validating files")
2268
2293
  if not files.file_exists(compose_file):
2269
2294
  raise DockerOpsError(f"Application compose file not found: {compose_file}")
2270
2295
  if not files.file_exists(playwright_compose_file):
2271
2296
  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")
2297
+ logger.info(f"{STICKERS['success']} Validation complete")
2298
+ logger.info("")
2299
+
2300
+ # STEP 1: Version update
2301
+ if update_version:
2302
+ logger.info(f"{STICKERS['info']} STEP 1: Updating version")
2303
+ if not dry_run:
2304
+ new_version = _update_env_version(env_file=env_file, source=version_source, logger=logger)
2305
+ if new_version:
2306
+ logger.info(f"{STICKERS['success']} Version updated to {new_version}")
2307
+ envs.set_system_env(key='STATIC_CAPTCHA', value='true')
2308
+ else:
2309
+ logger.info(f"{STICKERS['info']} Version update skipped (dry run)")
2310
+ logger.info("")
2311
+
2312
+ # STEP 2: Environment
2286
2313
  if not dry_run:
2287
2314
  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)")
2315
+
2316
+ # STEP 3: Application stack
2317
+ logger.info(f"{STICKERS['info']} STEP 3: Starting application stack")
2292
2318
  if not dry_run:
2293
2319
  app_result = test_compose(
2294
2320
  compose_file=compose_file,
2295
2321
  env_file=env_file,
2296
- update_version=False, # already handled
2322
+ update_version=False,
2297
2323
  services=services,
2298
2324
  health_timeout=health_timeout,
2299
2325
  health_interval=health_interval,
@@ -2302,128 +2328,215 @@ def playwright_test_compose(
2302
2328
  pull_missing=pull_missing,
2303
2329
  no_build=no_build,
2304
2330
  no_pull=no_pull,
2305
- keep_compose_up=True, # DO NOT tear down
2331
+ keep_compose_up=True,
2306
2332
  project_name=project_name,
2307
2333
  dry_run=dry_run,
2308
2334
  logger=logger,
2309
2335
  )
2336
+
2310
2337
  result['app_result'] = app_result
2311
2338
  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")
2339
+ raise DockerOpsError("Application stack failed")
2340
+
2341
+ logger.info(f"{STICKERS['success']} Application stack ready")
2342
+ logger.info("")
2314
2343
  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")
2344
+ logger.info(f"{STICKERS['info']} Application start skipped (dry run)")
2345
+ logger.info("")
2346
+
2347
+ # STEP 4: Playwright stack
2348
+ logger.info(f"{STICKERS['info']} STEP 4: Starting Playwright runner")
2318
2349
  if not dry_run:
2350
+ logger.info(f"{STICKERS['info']} Project: {project_name_playwright}")
2319
2351
  up_success = compose_up(
2320
2352
  playwright_compose_file,
2321
2353
  services=playwright_services,
2322
2354
  project_name=project_name_playwright,
2323
2355
  )
2356
+
2324
2357
  if not up_success:
2325
2358
  raise DockerOpsError("Playwright compose_up failed")
2326
- logger.info(" Playwright stack started - now following logs until completion")
2359
+
2360
+ logger.info(f"{STICKERS['success']} Playwright started")
2361
+ logger.info(f"{STICKERS['waiting']} Following logs until completion...")
2362
+ logger.info("")
2327
2363
  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)
2364
+ logger.info(f"{STICKERS['info']} Playwright start skipped (dry run)")
2365
+ logger.info("")
2366
+
2367
+ # STEP 5: Log capture
2368
+ pw_services = []
2369
+ logs_acc = {}
2370
+
2330
2371
  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}
2372
+ logger.info(f"{STICKERS['logs']} STEP 5: Capturing logs")
2373
+
2374
+ # Determine services to monitor
2375
+ compose_config = read_compose_file(playwright_compose_file, env_file)
2376
+ all_services = list(compose_config.get('services', {}).keys())
2377
+ pw_services = playwright_services or all_services
2378
+
2379
+ logger.info(f"{STICKERS['info']} Services: {', '.join(pw_services)}")
2380
+ logger.info(f"{STICKERS['info']} Tail: {log_tail} lines, Timeout: {health_timeout}s")
2381
+ logger.info("")
2382
+
2383
+ logs_acc = {service: [] for service in pw_services}
2334
2384
  deadline = time.time() + health_timeout
2335
2385
  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:
2386
+
2387
+ # Wait for containers to initialize
2388
+ time.sleep(3)
2389
+
2390
+ while not finished:
2391
+ current_time = time.time()
2392
+ if current_time > deadline:
2393
+ raise DockerOpsError(f"Playwright runner timed out after {health_timeout}s")
2394
+
2395
+ for service in pw_services:
2341
2396
  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)]
2397
+ # Get fresh logs for this service
2398
+ log_lines = compose_logs(
2399
+ playwright_compose_file,
2400
+ services=[service],
2401
+ tail=log_tail,
2402
+ project_name=project_name_playwright,
2403
+ follow=True,
2404
+
2405
+ )
2406
+
2407
+ for log_line in log_lines:
2408
+ if hasattr(log_line, 'message'):
2409
+ log_message = log_line.message
2410
+ # Add only new log lines
2411
+ if log_message not in logs_acc[service]:
2412
+ logs_acc[service].append(log_message)
2413
+ # Print with clean formatting: only service name at the beginning
2414
+ logger.info(f"{STICKERS['info']} {service}")
2415
+ logger.info(log_message)
2343
2416
  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
2417
+ error_msg = f"Error fetching logs: {str(e)}"
2418
+ logger.error(f"{STICKERS['error']} {service} - {error_msg}")
2419
+
2420
+ # Check if all services have finished
2351
2421
  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:
2422
+ running_count = 0
2423
+
2424
+ for container in ps:
2425
+ service_name = container.service or ''
2426
+ if service_name in pw_services:
2427
+ status = (container.status or '').lower()
2428
+ if 'running' in status or 'up' in status:
2429
+ running_count += 1
2430
+
2431
+ if running_count == 0:
2357
2432
  finished = True
2358
- if time.time() > deadline:
2359
- timed_out = True
2360
- if not finished and not timed_out:
2433
+ logger.info(f"{STICKERS['complete']} All services completed")
2434
+ logger.info("")
2435
+ else:
2361
2436
  time.sleep(health_interval)
2437
+
2362
2438
  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")
2439
+
2440
+ # STEP 6: Analysis
2441
+ logger.info(f"{STICKERS['analysis']} STEP 6: Analyzing results")
2442
+
2374
2443
  if not dry_run:
2375
2444
  # Get exit codes
2376
2445
  ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2377
2446
  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
2447
+
2448
+ for container in ps:
2449
+ service_name = container.service or ''
2450
+ if service_name in pw_services:
2451
+ exit_code = getattr(container, 'exit_code', None) or 1
2452
+ exit_codes[service_name] = exit_code
2453
+
2454
+ # Check overall success
2382
2455
  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):
2456
+
2457
+ if all_success:
2388
2458
  result['success'] = True
2389
- logger.info(f"Playwright run successful ({test_id})")
2459
+ logger.info(f"{STICKERS['success']} " + "="*50)
2460
+ logger.info(f"{STICKERS['success']} TESTS PASSED")
2461
+ logger.info(f"{STICKERS['success']} Test ID: {test_id}")
2462
+ logger.info(f"{STICKERS['success']} " + "="*50)
2390
2463
  else:
2391
2464
  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
- }
2465
+ result['error'] = 'Playwright tests failed'
2466
+ logger.error(f"{STICKERS['error']} " + "="*50)
2467
+ logger.error(f"{STICKERS['error']} TESTS FAILED")
2468
+ logger.error(f"{STICKERS['error']} Exit codes: {exit_codes}")
2469
+ logger.error(f"{STICKERS['error']} " + "="*50)
2399
2470
  else:
2400
- result['success'] = True # Assume success in dry run
2471
+ result['success'] = True
2472
+ logger.info(f"{STICKERS['info']} Analysis skipped (dry run)")
2473
+
2474
+ # Final timing
2401
2475
  result['duration'] = (dt_ops.current_datetime() - start_time).total_seconds()
2402
- if not keep_compose_up :
2476
+ logger.info(f"{STICKERS['info']} Duration: {result['duration']:.2f}s")
2477
+ logger.info("")
2478
+
2479
+ # Cleanup
2480
+ if not keep_compose_up and not dry_run:
2481
+ logger.info(f"{STICKERS['cleanup']} Cleaning up")
2482
+ logger.info(f"{STICKERS['info']} Stopping Playwright stack...")
2403
2483
  compose_down(
2404
2484
  compose_file=playwright_compose_file,
2405
2485
  project_name=project_name_playwright
2406
2486
  )
2407
- logger.info("Playwright compose down")
2487
+ logger.info(f"{STICKERS['success']} Playwright stopped")
2488
+
2489
+ logger.info(f"{STICKERS['info']} Stopping Application stack...")
2408
2490
  compose_down(
2409
2491
  compose_file=compose_file,
2492
+ project_name=project_name
2410
2493
  )
2411
- logger.info("all servise compose down")
2494
+ logger.info(f"{STICKERS['success']} Application stopped")
2495
+ logger.info(f"{STICKERS['success']} Cleanup complete")
2496
+ logger.info("")
2497
+
2498
+ # Final summary
2499
+ if result['success']:
2500
+ logger.info(f"{STICKERS['complete']} " + "="*70)
2501
+ logger.info(f"{STICKERS['complete']} TEST COMPLETE: SUCCESS")
2502
+ logger.info(f"{STICKERS['complete']} Duration: {result['duration']:.2f}s")
2503
+ logger.info(f"{STICKERS['complete']} Test ID: {test_id}")
2504
+ logger.info(f"{STICKERS['complete']} " + "="*70)
2505
+ else:
2506
+ logger.error(f"{STICKERS['error']} " + "="*70)
2507
+ logger.error(f"{STICKERS['error']} TEST COMPLETE: FAILED")
2508
+ logger.error(f"{STICKERS['error']} Duration: {result['duration']:.2f}s")
2509
+ logger.error(f"{STICKERS['error']} Test ID: {test_id}")
2510
+ logger.error(f"{STICKERS['error']} " + "="*70)
2511
+
2412
2512
  return result
2513
+
2413
2514
  except Exception as e:
2414
2515
  result['duration'] = (dt_ops.current_datetime() - start_time).total_seconds()
2415
2516
  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")
2517
+
2518
+ logger.error(f"{STICKERS['error']} Test failed: {e}")
2519
+ logger.error(f"{STICKERS['info']} Duration: {result['duration']:.2f}s")
2520
+
2521
+ # Emergency cleanup
2522
+ if not keep_compose_up and not dry_run:
2523
+ logger.info(f"{STICKERS['cleanup']} Emergency cleanup")
2524
+ try:
2525
+ compose_down(
2526
+ compose_file=playwright_compose_file,
2527
+ project_name=project_name_playwright
2528
+ )
2529
+ except:
2530
+ pass
2531
+
2532
+ try:
2533
+ compose_down(
2534
+ compose_file=compose_file,
2535
+ project_name=project_name
2536
+ )
2537
+ except:
2538
+ pass
2539
+
2427
2540
  return result
2428
2541
 
2429
2542
 
File without changes