dfsync 0.4.9__tar.gz → 0.4.10__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.
- {dfsync-0.4.9 → dfsync-0.4.10}/PKG-INFO +3 -2
- dfsync-0.4.10/dfsync/lib.py +101 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/monitor.py +28 -12
- {dfsync-0.4.9 → dfsync-0.4.10}/pyproject.toml +1 -1
- dfsync-0.4.9/dfsync/lib.py +0 -49
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/__init__.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/backends/__init__.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/backends/kube.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/backends/kube_exec.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/backends/rsync.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/char_ui.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/chcli.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/check_dns.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/cli.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/config.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/diffui.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/distribution.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/exp.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/filters.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/kube_credentials.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/test_dfsync.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/test_distribution.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/test_kube_credentials.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/test_lib.py +0 -0
- {dfsync-0.4.9 → dfsync-0.4.10}/dfsync/transactions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: dfsync
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.10
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Mihai Balint
|
|
6
6
|
Author-email: balint.mihai@gmail.com
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
15
|
Requires-Dist: black (==24.3.0)
|
|
15
16
|
Requires-Dist: click (>=8,<9)
|
|
16
17
|
Requires-Dist: click-default-group (>=1.2.2,<2.0.0)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ThreadedOperationsManager:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.threads = []
|
|
11
|
+
|
|
12
|
+
def stop(self, *args, **kwargs):
|
|
13
|
+
for thread in self.threads:
|
|
14
|
+
thread.stop(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
def register_threaded_operation(self, op):
|
|
17
|
+
self.threads.append(op)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
thread_manager = ThreadedOperationsManager()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ControlledThreadedOperation:
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self._running = False
|
|
26
|
+
self._thread = threading.Thread(target=self.run)
|
|
27
|
+
thread_manager.register_threaded_operation(self)
|
|
28
|
+
|
|
29
|
+
def start(self):
|
|
30
|
+
self._running = True
|
|
31
|
+
self._thread.start()
|
|
32
|
+
|
|
33
|
+
def stop(self, *args, **kwargs):
|
|
34
|
+
self._running = False
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_running(self):
|
|
38
|
+
return self._running
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_completed(self):
|
|
42
|
+
return self._running is False
|
|
43
|
+
|
|
44
|
+
def run(self):
|
|
45
|
+
while self._running:
|
|
46
|
+
try:
|
|
47
|
+
self._run_once()
|
|
48
|
+
except Exception:
|
|
49
|
+
time.sleep(0.001)
|
|
50
|
+
|
|
51
|
+
def _run_once(self):
|
|
52
|
+
time.sleep(0.001)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RsyncFlavour(Enum):
|
|
56
|
+
SAMBA_RSYNC = "SAMBA_RSYNC"
|
|
57
|
+
OPEN_RSYNC = "OPENRSYNC"
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def executable(self):
|
|
61
|
+
if platform.system() == "Darwin": # Check if running on macOS
|
|
62
|
+
if self == RsyncFlavour.SAMBA_RSYNC:
|
|
63
|
+
return "/opt/homebrew/bin/rsync"
|
|
64
|
+
elif self == RsyncFlavour.OPEN_RSYNC:
|
|
65
|
+
return "/usr/bin/rsync"
|
|
66
|
+
|
|
67
|
+
# Default fallback for other platforms or if paths don't exist
|
|
68
|
+
return "rsync"
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def exists_installed(self):
|
|
72
|
+
try:
|
|
73
|
+
# Check if the executable exists and is executable
|
|
74
|
+
result = subprocess.run(
|
|
75
|
+
["which", self.executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
|
|
76
|
+
)
|
|
77
|
+
if result.returncode != 0:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
# Additionally verify it can be executed
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
[self.executable, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
|
|
83
|
+
)
|
|
84
|
+
return result.returncode == 0
|
|
85
|
+
except Exception:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def check_rsync() -> RsyncFlavour:
|
|
90
|
+
try:
|
|
91
|
+
result = subprocess.run(["rsync", "--version"], capture_output=True, text=True)
|
|
92
|
+
version_text = result.stdout
|
|
93
|
+
|
|
94
|
+
if "rsync.samba.org/" in version_text:
|
|
95
|
+
return RsyncFlavour.SAMBA_RSYNC
|
|
96
|
+
else:
|
|
97
|
+
# Default to OpenRsync if we can't positively identify Samba rsync
|
|
98
|
+
return RsyncFlavour.OPEN_RSYNC
|
|
99
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
100
|
+
# If rsync isn't available or fails, default to Samba rsync
|
|
101
|
+
return RsyncFlavour.SAMBA_RSYNC
|
|
@@ -13,6 +13,7 @@ from watchdog.observers import Observer
|
|
|
13
13
|
from watchdog.events import FileSystemEventHandler
|
|
14
14
|
|
|
15
15
|
import dfsync.filters as filters
|
|
16
|
+
import dfsync.lib as lib
|
|
16
17
|
from dfsync.backends import rsync_backend, kube_backend
|
|
17
18
|
from dfsync.distribution import (
|
|
18
19
|
get_installed_version,
|
|
@@ -25,7 +26,6 @@ from dfsync.distribution import (
|
|
|
25
26
|
from dfsync.config import read_config
|
|
26
27
|
from dfsync.char_ui import KeyController
|
|
27
28
|
from dfsync.kube_credentials import contextualize_kube_credentials, update_local_kube_config, normalized_k8s_url
|
|
28
|
-
from dfsync.lib import ControlledThreadedOperation, thread_manager
|
|
29
29
|
|
|
30
30
|
logging.basicConfig(level=logging.WARN)
|
|
31
31
|
|
|
@@ -44,7 +44,11 @@ class RelatedLocationsError(ValueError):
|
|
|
44
44
|
pass
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class
|
|
47
|
+
class UnsupportedRsyncError(ValueError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FileChangedEventHandler(lib.ControlledThreadedOperation, FileSystemEventHandler):
|
|
48
52
|
def __init__(
|
|
49
53
|
self,
|
|
50
54
|
backend: str = "log",
|
|
@@ -258,7 +262,7 @@ def main():
|
|
|
258
262
|
pass
|
|
259
263
|
|
|
260
264
|
|
|
261
|
-
@main.command()
|
|
265
|
+
@main.command(add_help_option=False)
|
|
262
266
|
def version():
|
|
263
267
|
"""
|
|
264
268
|
Print the currently installed version
|
|
@@ -279,17 +283,16 @@ def _version():
|
|
|
279
283
|
click.echo(f"Latest version is {latest_version}, please update using `dfsync self-update`")
|
|
280
284
|
|
|
281
285
|
|
|
282
|
-
@main.command(name="help",
|
|
283
|
-
|
|
284
|
-
def help(help):
|
|
286
|
+
@main.command(name="help", add_help_option=False)
|
|
287
|
+
def help():
|
|
285
288
|
"""
|
|
286
289
|
Print the usage/help for dfsync
|
|
287
290
|
"""
|
|
288
|
-
with click.Context(sync, info_name="dfsync") as ctx:
|
|
291
|
+
with click.Context(sync, info_name="dfsync", max_content_width=999) as ctx:
|
|
289
292
|
click.echo(sync.get_help(ctx))
|
|
290
293
|
|
|
291
294
|
|
|
292
|
-
@main.command()
|
|
295
|
+
@main.command(add_help_option=False)
|
|
293
296
|
def self_update():
|
|
294
297
|
"""
|
|
295
298
|
Update dfsync to the latest version published on pypi
|
|
@@ -395,6 +398,11 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
|
|
|
395
398
|
elif len(missing) > 0:
|
|
396
399
|
click.echo(f"Using source file/dirs {', '.join(paths)}")
|
|
397
400
|
|
|
401
|
+
installed_rsync = lib.check_rsync()
|
|
402
|
+
click.echo(f"Installed: {installed_rsync.executable}")
|
|
403
|
+
if installed_rsync != lib.RsyncFlavour.SAMBA_RSYNC:
|
|
404
|
+
raise UnsupportedRsyncError()
|
|
405
|
+
|
|
398
406
|
backend_options = dict(
|
|
399
407
|
destination_dir=destination_dir,
|
|
400
408
|
supervisor=supervisor,
|
|
@@ -423,6 +431,14 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
|
|
|
423
431
|
click.echo(str(e))
|
|
424
432
|
return -1
|
|
425
433
|
|
|
434
|
+
except UnsupportedRsyncError:
|
|
435
|
+
click.echo(
|
|
436
|
+
"\nStarting from Mac OSX Sequoia 15.4, Apple are shipping openrsync instead of the GPL-licensed rsync from Samba"
|
|
437
|
+
"\nThe Apple version of rsync has less features and is currently not supported by dfsync"
|
|
438
|
+
"\nPlease use homebrew to install the GPL-licensed rsync from Samba with this command `brew install rsync`."
|
|
439
|
+
)
|
|
440
|
+
return -1
|
|
441
|
+
|
|
426
442
|
except ValueError as e:
|
|
427
443
|
click.echo(
|
|
428
444
|
"Trying {} from source: {} to destination: '{}'\n".format(
|
|
@@ -455,7 +471,7 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
|
|
|
455
471
|
controller.on_key(
|
|
456
472
|
"x",
|
|
457
473
|
description="to exit",
|
|
458
|
-
action=partial(thread_manager.stop, "Exiting."),
|
|
474
|
+
action=partial(lib.thread_manager.stop, "Exiting."),
|
|
459
475
|
)
|
|
460
476
|
|
|
461
477
|
try:
|
|
@@ -481,16 +497,16 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
|
|
|
481
497
|
controller.raise_exceptions()
|
|
482
498
|
|
|
483
499
|
except KeyboardInterrupt:
|
|
484
|
-
thread_manager.stop()
|
|
500
|
+
lib.thread_manager.stop()
|
|
485
501
|
click.echo("Received [Ctrl-C], exiting.")
|
|
486
502
|
|
|
487
503
|
except:
|
|
488
|
-
thread_manager.stop()
|
|
504
|
+
lib.thread_manager.stop()
|
|
489
505
|
raise
|
|
490
506
|
|
|
491
507
|
finally:
|
|
492
508
|
observer.stop()
|
|
493
|
-
thread_manager.stop()
|
|
509
|
+
lib.thread_manager.stop()
|
|
494
510
|
backend_engine.on_monitor_exit(**backend_options)
|
|
495
511
|
if observer.ident is not None:
|
|
496
512
|
# only join the observer if it was previously started
|
dfsync-0.4.9/dfsync/lib.py
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import threading
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ThreadedOperationsManager:
|
|
6
|
-
def __init__(self):
|
|
7
|
-
self.threads = []
|
|
8
|
-
|
|
9
|
-
def stop(self, *args, **kwargs):
|
|
10
|
-
for thread in self.threads:
|
|
11
|
-
thread.stop(*args, **kwargs)
|
|
12
|
-
|
|
13
|
-
def register_threaded_operation(self, op):
|
|
14
|
-
self.threads.append(op)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
thread_manager = ThreadedOperationsManager()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ControlledThreadedOperation:
|
|
21
|
-
def __init__(self):
|
|
22
|
-
self._running = False
|
|
23
|
-
self._thread = threading.Thread(target=self.run)
|
|
24
|
-
thread_manager.register_threaded_operation(self)
|
|
25
|
-
|
|
26
|
-
def start(self):
|
|
27
|
-
self._running = True
|
|
28
|
-
self._thread.start()
|
|
29
|
-
|
|
30
|
-
def stop(self, *args, **kwargs):
|
|
31
|
-
self._running = False
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def is_running(self):
|
|
35
|
-
return self._running
|
|
36
|
-
|
|
37
|
-
@property
|
|
38
|
-
def is_completed(self):
|
|
39
|
-
return self._running is False
|
|
40
|
-
|
|
41
|
-
def run(self):
|
|
42
|
-
while self._running:
|
|
43
|
-
try:
|
|
44
|
-
self._run_once()
|
|
45
|
-
except Exception:
|
|
46
|
-
time.sleep(0.001)
|
|
47
|
-
|
|
48
|
-
def _run_once(self):
|
|
49
|
-
time.sleep(0.001)
|
|
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
|