utils_devops 0.1.129__py3-none-any.whl → 0.1.130__py3-none-any.whl
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/core/systems.py +241 -95
- utils_devops/extras/docker_ops.py +1 -2
- {utils_devops-0.1.129.dist-info → utils_devops-0.1.130.dist-info}/METADATA +1 -1
- {utils_devops-0.1.129.dist-info → utils_devops-0.1.130.dist-info}/RECORD +6 -6
- {utils_devops-0.1.129.dist-info → utils_devops-0.1.130.dist-info}/WHEEL +0 -0
- {utils_devops-0.1.129.dist-info → utils_devops-0.1.130.dist-info}/entry_points.txt +0 -0
utils_devops/core/systems.py
CHANGED
|
@@ -11,6 +11,7 @@ import shutil
|
|
|
11
11
|
import time
|
|
12
12
|
import socket
|
|
13
13
|
import getpass
|
|
14
|
+
import threading
|
|
14
15
|
import ctypes # For Windows admin check
|
|
15
16
|
from typing import Optional, List, Dict, Union, Any, Callable, Tuple
|
|
16
17
|
from rich.console import Console
|
|
@@ -293,157 +294,301 @@ def run(
|
|
|
293
294
|
elevated: bool = False,
|
|
294
295
|
capture: bool = True,
|
|
295
296
|
logger: Optional[logger] = None,
|
|
296
|
-
stream: bool = False
|
|
297
|
+
stream: bool = False
|
|
297
298
|
) -> subprocess.CompletedProcess:
|
|
298
299
|
"""
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
Robust run() with cross-platform threaded streaming + smart carriage-return handling.
|
|
301
|
+
|
|
302
|
+
- stream=True : streams output in real-time using threads, understands '\r' updates.
|
|
303
|
+
- capture controls whether stdout/stderr are returned in CompletedProcess (when stream=True,
|
|
304
|
+
capture=True will also collect into strings).
|
|
305
|
+
- If streaming fails for any reason we fallback to communicate() to collect remaining output.
|
|
306
|
+
- Works on Linux and Windows.
|
|
306
307
|
"""
|
|
307
|
-
logger = logger or DEFAULT_LOGGER
|
|
308
|
+
logger = logger or DEFAULT_LOGGER
|
|
309
|
+
|
|
308
310
|
if shell is None:
|
|
309
311
|
shell = isinstance(cmd, str)
|
|
310
|
-
|
|
312
|
+
|
|
313
|
+
# Normalize command forms
|
|
311
314
|
if isinstance(cmd, (list, tuple)):
|
|
312
315
|
cmd_str = subprocess.list2cmdline(cmd)
|
|
313
316
|
cmd_list: Union[List[str], str] = list(cmd)
|
|
314
317
|
else:
|
|
315
318
|
cmd_str = str(cmd)
|
|
316
319
|
cmd_list = cmd_str if shell else [cmd_str]
|
|
320
|
+
|
|
317
321
|
if dry_run:
|
|
318
322
|
logger.info(f"[DRY-RUN] {cmd_str}")
|
|
319
323
|
return subprocess.CompletedProcess(cmd_list if not shell else cmd_str, 0, stdout="", stderr="")
|
|
324
|
+
|
|
320
325
|
stdin_input: Optional[str] = None
|
|
321
326
|
use_list: Union[List[str], str] = cmd_list
|
|
327
|
+
|
|
328
|
+
# Determine platform
|
|
329
|
+
try:
|
|
330
|
+
is_win = (is_windows())
|
|
331
|
+
except NameError:
|
|
332
|
+
is_win = (os.name == "nt") or sys.platform.startswith("win")
|
|
333
|
+
|
|
334
|
+
# Handle elevated
|
|
322
335
|
if elevated:
|
|
323
|
-
if
|
|
324
|
-
#
|
|
336
|
+
if is_win:
|
|
337
|
+
# Windows: use Start-Process via powershell RunAs
|
|
325
338
|
ps = f"Start-Process -Verb RunAs -FilePath powershell -ArgumentList '-NoProfile','-Command','{cmd_str}' -Wait -PassThru"
|
|
326
339
|
use_list = ["powershell", "-NoProfile", "-Command", ps]
|
|
327
340
|
shell = False
|
|
328
341
|
else:
|
|
329
|
-
# Unix:
|
|
330
|
-
|
|
331
|
-
|
|
342
|
+
# Unix: attempt to get cached sudo password via user-provided helper
|
|
343
|
+
try:
|
|
344
|
+
pw = _get_sudo_password()
|
|
345
|
+
except NameError:
|
|
346
|
+
raise RuntimeError("elevated=True requested but _get_sudo_password() is not implemented in the environment.")
|
|
332
347
|
if isinstance(cmd, (list, tuple)):
|
|
333
348
|
base_list = list(cmd)
|
|
334
349
|
else:
|
|
335
|
-
# if original was a string and shell=True, pass the string to sudo as a single shell invocation
|
|
336
350
|
base_list = [cmd_str] if shell else [cmd_str]
|
|
337
351
|
use_list = ["sudo", "-S"] + base_list
|
|
338
352
|
stdin_input = (pw + "\n") if pw is not None else None
|
|
339
|
-
shell = False
|
|
340
|
-
|
|
353
|
+
shell = False
|
|
354
|
+
|
|
355
|
+
# Log final command (avoid logging sensitive data)
|
|
341
356
|
try:
|
|
342
357
|
if isinstance(use_list, list):
|
|
343
358
|
logger.debug(f"Executing (list): {' '.join(use_list)}")
|
|
344
359
|
else:
|
|
345
360
|
logger.debug(f"Executing (shell): {use_list}")
|
|
346
|
-
|
|
361
|
+
|
|
347
362
|
proc = subprocess.Popen(
|
|
348
363
|
use_list if not shell else (cmd_str),
|
|
349
364
|
cwd=str(cwd) if cwd else None,
|
|
350
365
|
env=env,
|
|
351
|
-
stdout=subprocess.PIPE if capture else None,
|
|
352
|
-
stderr=subprocess.PIPE if capture else None,
|
|
366
|
+
stdout=subprocess.PIPE if (capture or stream) else None,
|
|
367
|
+
stderr=subprocess.PIPE if (capture or stream) else None,
|
|
353
368
|
stdin=subprocess.PIPE if stdin_input is not None else None,
|
|
354
369
|
text=True,
|
|
355
370
|
shell=shell,
|
|
356
|
-
bufsize=1,
|
|
371
|
+
bufsize=1,
|
|
357
372
|
universal_newlines=True,
|
|
358
373
|
)
|
|
359
|
-
|
|
374
|
+
|
|
375
|
+
# Send sudo password if needed
|
|
360
376
|
if stdin_input is not None and proc.stdin:
|
|
361
|
-
# write password and flush; do not keep password in logs
|
|
362
377
|
try:
|
|
363
378
|
proc.stdin.write(stdin_input)
|
|
364
379
|
proc.stdin.flush()
|
|
365
380
|
proc.stdin.close()
|
|
366
381
|
except Exception:
|
|
367
|
-
# If writing fails, ensure we close and continue to wait for process
|
|
368
382
|
try:
|
|
369
383
|
proc.stdin.close()
|
|
370
384
|
except Exception:
|
|
371
385
|
pass
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
386
|
+
|
|
387
|
+
# Output collectors
|
|
388
|
+
stdout_lines: List[str] = []
|
|
389
|
+
stderr_lines: List[str] = []
|
|
390
|
+
|
|
391
|
+
# --- Smart threaded streaming implementation ---
|
|
392
|
+
def _smart_reader(pipe, log_func, collector: Optional[List[str]], stop_event: threading.Event):
|
|
393
|
+
"""
|
|
394
|
+
Read from pipe in chunks, handle '\r' (line replace) and '\n' (new line).
|
|
395
|
+
Appends to collector (if provided) and logs via log_func.
|
|
396
|
+
"""
|
|
397
|
+
try:
|
|
398
|
+
buffer = ""
|
|
399
|
+
last_rendered = None # used to avoid repeated identical logs for \r updates
|
|
400
|
+
|
|
401
|
+
# We'll read in reasonably-sized chunks. read() will block, but on separate thread that's fine.
|
|
402
|
+
while not stop_event.is_set():
|
|
403
|
+
chunk = pipe.read(1024)
|
|
404
|
+
if not chunk:
|
|
405
|
+
# EOF reached
|
|
406
|
+
break
|
|
407
|
+
buffer += chunk
|
|
408
|
+
|
|
409
|
+
# Process as long as there's control chars
|
|
410
|
+
while True:
|
|
411
|
+
# find next control char indices
|
|
412
|
+
idx_n = buffer.find("\n")
|
|
413
|
+
idx_r = buffer.find("\r")
|
|
414
|
+
|
|
415
|
+
if idx_n == -1 and idx_r == -1:
|
|
416
|
+
break
|
|
417
|
+
|
|
418
|
+
# Which control comes first?
|
|
419
|
+
if idx_r != -1 and (idx_n == -1 or idx_r < idx_n):
|
|
420
|
+
# Carriage return: replace current line
|
|
421
|
+
line = buffer[:idx_r]
|
|
422
|
+
buffer = buffer[idx_r + 1:]
|
|
423
|
+
# Only log if changed (avoids spamming identical updates)
|
|
424
|
+
if line != last_rendered:
|
|
425
|
+
# strip trailing CR/LF but preserve internal whitespace
|
|
426
|
+
to_log = line.rstrip("\r\n")
|
|
427
|
+
try:
|
|
428
|
+
log_func(to_log)
|
|
429
|
+
except Exception:
|
|
430
|
+
# logging should not raise to user
|
|
431
|
+
pass
|
|
432
|
+
if collector is not None:
|
|
433
|
+
collector.append(line + ("\n" if collector is not None else ""))
|
|
434
|
+
last_rendered = line
|
|
435
|
+
else:
|
|
436
|
+
# Newline: finalize this line
|
|
437
|
+
line = buffer[:idx_n]
|
|
438
|
+
buffer = buffer[idx_n + 1:]
|
|
439
|
+
to_log = line.rstrip("\r\n")
|
|
440
|
+
try:
|
|
441
|
+
log_func(to_log)
|
|
442
|
+
except Exception:
|
|
443
|
+
pass
|
|
444
|
+
if collector is not None:
|
|
445
|
+
collector.append(line + "\n")
|
|
446
|
+
last_rendered = None
|
|
447
|
+
|
|
448
|
+
# Flush whatever remains in buffer
|
|
449
|
+
if buffer:
|
|
450
|
+
buf_strip = buffer.rstrip("\r\n")
|
|
451
|
+
if buf_strip:
|
|
452
|
+
try:
|
|
453
|
+
log_func(buf_strip)
|
|
454
|
+
except Exception:
|
|
455
|
+
pass
|
|
456
|
+
if collector is not None:
|
|
457
|
+
collector.append(buffer)
|
|
458
|
+
# close pipe
|
|
459
|
+
try:
|
|
460
|
+
pipe.close()
|
|
461
|
+
except Exception:
|
|
462
|
+
pass
|
|
463
|
+
except Exception as exc:
|
|
464
|
+
# Ensure we don't crash the thread; bubble up via logging
|
|
465
|
+
logger.exception(f"stream reader error: {exc}")
|
|
466
|
+
try:
|
|
467
|
+
pipe.close()
|
|
468
|
+
except Exception:
|
|
469
|
+
pass
|
|
470
|
+
# re-raise to let outer context know (we'll catch in caller via threads status)
|
|
471
|
+
raise
|
|
472
|
+
|
|
473
|
+
rc = None
|
|
474
|
+
if stream and (proc.stdout is not None or proc.stderr is not None):
|
|
378
475
|
logger.info("Streaming command output...")
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
while True:
|
|
386
|
-
# Check if process has terminated
|
|
387
|
-
if proc.poll() is not None:
|
|
388
|
-
break
|
|
389
|
-
|
|
390
|
-
# Use select to wait for data
|
|
391
|
-
read_fds = []
|
|
476
|
+
stop_event = threading.Event()
|
|
477
|
+
threads: List[threading.Thread] = []
|
|
478
|
+
stream_exception = None
|
|
479
|
+
|
|
480
|
+
# Start threads
|
|
481
|
+
try:
|
|
392
482
|
if proc.stdout:
|
|
393
|
-
|
|
483
|
+
t_out = threading.Thread(
|
|
484
|
+
target=_smart_reader,
|
|
485
|
+
args=(proc.stdout, logger.info, stdout_lines if capture else None, stop_event),
|
|
486
|
+
daemon=True
|
|
487
|
+
)
|
|
488
|
+
t_out.start()
|
|
489
|
+
threads.append(t_out)
|
|
490
|
+
|
|
394
491
|
if proc.stderr:
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
492
|
+
t_err = threading.Thread(
|
|
493
|
+
target=_smart_reader,
|
|
494
|
+
args=(proc.stderr, logger.warning, stderr_lines if capture else None, stop_event),
|
|
495
|
+
daemon=True
|
|
496
|
+
)
|
|
497
|
+
t_err.start()
|
|
498
|
+
threads.append(t_err)
|
|
499
|
+
|
|
500
|
+
# Wait for process while allowing KeyboardInterrupt
|
|
501
|
+
try:
|
|
502
|
+
rc = proc.wait()
|
|
503
|
+
except KeyboardInterrupt:
|
|
504
|
+
logger.debug("KeyboardInterrupt caught: terminating child process")
|
|
505
|
+
stop_event.set()
|
|
506
|
+
try:
|
|
507
|
+
proc.terminate()
|
|
508
|
+
except Exception:
|
|
509
|
+
pass
|
|
510
|
+
# wait a bit then force kill
|
|
511
|
+
try:
|
|
512
|
+
proc.wait(timeout=2)
|
|
513
|
+
except Exception:
|
|
514
|
+
try:
|
|
515
|
+
proc.kill()
|
|
516
|
+
except Exception:
|
|
517
|
+
pass
|
|
518
|
+
rc = proc.returncode if proc.returncode is not None else -1
|
|
519
|
+
|
|
520
|
+
except Exception as exc:
|
|
521
|
+
# If any exception occurs while starting/monitoring threads -> fallback
|
|
522
|
+
stream_exception = exc
|
|
523
|
+
logger.exception(f"Streaming failed, falling back to communicate(): {exc}")
|
|
524
|
+
finally:
|
|
525
|
+
# Signal threads to stop and join
|
|
526
|
+
stop_event.set()
|
|
527
|
+
for t in threads:
|
|
528
|
+
t.join(timeout=1)
|
|
529
|
+
|
|
530
|
+
# If streaming had exception, fallback to communicate to collect remaining output safely.
|
|
531
|
+
if stream_exception is not None:
|
|
532
|
+
try:
|
|
533
|
+
# communicate will return remaining output that reader threads didn't capture
|
|
534
|
+
comm_out, comm_err = proc.communicate(timeout=5)
|
|
535
|
+
except Exception:
|
|
536
|
+
try:
|
|
537
|
+
# best-effort: kill and read whatever
|
|
538
|
+
proc.kill()
|
|
539
|
+
except Exception:
|
|
540
|
+
pass
|
|
541
|
+
try:
|
|
542
|
+
comm_out, comm_err = proc.communicate(timeout=5)
|
|
543
|
+
except Exception:
|
|
544
|
+
comm_out, comm_err = ("", "")
|
|
545
|
+
# append remaining to collectors if capture True
|
|
546
|
+
if capture:
|
|
547
|
+
if comm_out:
|
|
548
|
+
stdout_lines.append(comm_out)
|
|
549
|
+
if comm_err:
|
|
550
|
+
stderr_lines.append(comm_err)
|
|
551
|
+
# set rc if not set
|
|
552
|
+
if rc is None:
|
|
553
|
+
rc = proc.returncode if proc.returncode is not None else 0
|
|
554
|
+
|
|
555
|
+
# Compose final stdout/stderr
|
|
556
|
+
stdout = "".join(stdout_lines) if capture else ""
|
|
557
|
+
stderr = "".join(stderr_lines) if capture else ""
|
|
558
|
+
if rc is None:
|
|
559
|
+
rc = proc.returncode if proc.returncode is not None else 0
|
|
560
|
+
|
|
428
561
|
else:
|
|
429
|
-
#
|
|
430
|
-
|
|
562
|
+
# original behavior: blocking wait & capture (or not)
|
|
563
|
+
try:
|
|
564
|
+
stdout, stderr = proc.communicate()
|
|
565
|
+
except Exception as exc:
|
|
566
|
+
# if communicate fails, try to kill and fallback
|
|
567
|
+
logger.exception(f"proc.communicate() failed: {exc}")
|
|
568
|
+
try:
|
|
569
|
+
proc.kill()
|
|
570
|
+
except Exception:
|
|
571
|
+
pass
|
|
572
|
+
try:
|
|
573
|
+
stdout, stderr = proc.communicate(timeout=5)
|
|
574
|
+
except Exception:
|
|
575
|
+
stdout, stderr = ("", "")
|
|
431
576
|
rc = proc.returncode
|
|
432
|
-
|
|
577
|
+
|
|
578
|
+
# Build CompletedProcess result
|
|
433
579
|
result = subprocess.CompletedProcess(
|
|
434
580
|
use_list if not shell else cmd_str,
|
|
435
581
|
rc,
|
|
436
582
|
stdout=stdout or "",
|
|
437
583
|
stderr=stderr or "",
|
|
438
584
|
)
|
|
439
|
-
|
|
585
|
+
|
|
586
|
+
# Post-run handling & sudo auth checks (Unix)
|
|
440
587
|
if rc == 0:
|
|
441
588
|
logger.info(f"Command succeeded (rc={rc})")
|
|
442
589
|
else:
|
|
443
|
-
|
|
444
|
-
if elevated and not is_windows():
|
|
590
|
+
if elevated and not is_win:
|
|
445
591
|
lowerr = (stderr or "").lower()
|
|
446
|
-
# Common sudo auth failure tokens
|
|
447
592
|
auth_tokens = [
|
|
448
593
|
"incorrect password",
|
|
449
594
|
"authentication failure",
|
|
@@ -454,25 +599,26 @@ def run(
|
|
|
454
599
|
"pam_authenticate",
|
|
455
600
|
]
|
|
456
601
|
if any(tok in lowerr for tok in auth_tokens):
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
602
|
+
try:
|
|
603
|
+
clear_sudo_password()
|
|
604
|
+
except NameError:
|
|
605
|
+
logger.error("sudo authentication failed and clear_sudo_password() not implemented.")
|
|
606
|
+
else:
|
|
607
|
+
logger.error("sudo authentication failed. Cached sudo password cleared.")
|
|
461
608
|
raise subprocess.CalledProcessError(rc, use_list if not shell else cmd_str, output=stdout, stderr=stderr)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
609
|
+
logger.error(f"Command failed (rc={rc}) – { (stderr or '').strip() }")
|
|
610
|
+
|
|
465
611
|
if rc != 0 and not no_die:
|
|
466
612
|
raise subprocess.CalledProcessError(rc, use_list if not shell else cmd_str, output=stdout, stderr=stderr)
|
|
467
|
-
|
|
613
|
+
|
|
468
614
|
return result
|
|
469
|
-
|
|
615
|
+
|
|
470
616
|
except subprocess.CalledProcessError:
|
|
471
|
-
# Re-raise CalledProcessError so callers can handle; do not clear cached password here
|
|
472
617
|
raise
|
|
473
618
|
except Exception as e:
|
|
474
619
|
logger.exception(f"Unexpected error running command: {e}")
|
|
475
620
|
raise
|
|
621
|
+
|
|
476
622
|
# -------------------------------------------------
|
|
477
623
|
# Exec helper – shows command + output + logs
|
|
478
624
|
# -------------------------------------------------
|
|
@@ -430,8 +430,7 @@ def read_compose_file(compose_file: str, env_file: Optional[str] = None) -> Dict
|
|
|
430
430
|
envs.import_env_to_system(get_env_compose(env_file))
|
|
431
431
|
|
|
432
432
|
# Read compose file content
|
|
433
|
-
|
|
434
|
-
content = strings.parse_yaml(compose_file)
|
|
433
|
+
content = files.read_file(compose_file)
|
|
435
434
|
|
|
436
435
|
# Expand environment variables in the content
|
|
437
436
|
data = _expand_env_vars_in_compose(content)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: utils_devops
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.130
|
|
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
|
|
@@ -6,10 +6,10 @@ utils_devops/core/files.py,sha256=WwLkEy83PTyIQT2yhYJhJA6xkrMi2mfKoDT45BswlvQ,50
|
|
|
6
6
|
utils_devops/core/logs.py,sha256=xHVSPLE1a7kivrBqQWQdOYjYfm69e2C9yQ7vBu3U68k,30829
|
|
7
7
|
utils_devops/core/script_helpers.py,sha256=NcOh1f1X_TYm5uuP5YFCYpNF5tr2NVE8DehLj9DMo3c,31480
|
|
8
8
|
utils_devops/core/strings.py,sha256=8s0GSjcyTKwLjJjsJ_XfOJxPtyb549icDlU9SUxSvHI,42481
|
|
9
|
-
utils_devops/core/systems.py,sha256=
|
|
9
|
+
utils_devops/core/systems.py,sha256=wNbEFUAvbMPdqWN-iXvTzvj5iE9xaWfjZYYvD0EZAH0,47577
|
|
10
10
|
utils_devops/extras/__init__.py,sha256=ZXHeVLHO3_qiW9AY-UQ_YA9cQzmkLGv54a2UbyvtlM0,3571
|
|
11
11
|
utils_devops/extras/aws_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
utils_devops/extras/docker_ops.py,sha256=
|
|
12
|
+
utils_devops/extras/docker_ops.py,sha256=7941chrSL1OnbvQCOM9Uz7TRtalKDPs4yNFkPre3YUA,175029
|
|
13
13
|
utils_devops/extras/git_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
utils_devops/extras/interaction_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
utils_devops/extras/metrics_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -19,7 +19,7 @@ utils_devops/extras/notification_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
|
19
19
|
utils_devops/extras/performance_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
utils_devops/extras/ssh_ops.py,sha256=tBhydL7z-XzJUy8Cv4QWn_tHFToyRK0Enx3EllnZS4A,68976
|
|
21
21
|
utils_devops/extras/vault_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
utils_devops-0.1.
|
|
23
|
-
utils_devops-0.1.
|
|
24
|
-
utils_devops-0.1.
|
|
25
|
-
utils_devops-0.1.
|
|
22
|
+
utils_devops-0.1.130.dist-info/METADATA,sha256=Qz7EmI-m2ZtIJiLLzRj98dtCYvpbJGfk9EMGQu5hfRQ,1903
|
|
23
|
+
utils_devops-0.1.130.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
24
|
+
utils_devops-0.1.130.dist-info/entry_points.txt,sha256=ei3B6ZL5yu6dOq-U1r8wsBdkXeg63RAyV7m8_ADaE6k,53
|
|
25
|
+
utils_devops-0.1.130.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|