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