clonebox 1.1.12__py3-none-any.whl → 1.1.14__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.
- clonebox/audit.py +448 -0
- clonebox/cli.py +398 -5
- clonebox/cloner.py +58 -22
- clonebox/orchestrator.py +568 -0
- clonebox/plugins/__init__.py +24 -0
- clonebox/plugins/base.py +319 -0
- clonebox/plugins/manager.py +438 -0
- {clonebox-1.1.12.dist-info → clonebox-1.1.14.dist-info}/METADATA +1 -1
- {clonebox-1.1.12.dist-info → clonebox-1.1.14.dist-info}/RECORD +13 -8
- {clonebox-1.1.12.dist-info → clonebox-1.1.14.dist-info}/WHEEL +0 -0
- {clonebox-1.1.12.dist-info → clonebox-1.1.14.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.12.dist-info → clonebox-1.1.14.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.12.dist-info → clonebox-1.1.14.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin manager for CloneBox.
|
|
3
|
+
Handles plugin discovery, loading, and lifecycle.
|
|
4
|
+
"""
|
|
5
|
+
import importlib
|
|
6
|
+
import importlib.util
|
|
7
|
+
import sys
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Dict, Any, List, Type, Set
|
|
11
|
+
import threading
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
from clonebox.plugins.base import Plugin, PluginHook, PluginContext, PluginMetadata
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class LoadedPlugin:
|
|
19
|
+
"""Information about a loaded plugin."""
|
|
20
|
+
plugin: Plugin
|
|
21
|
+
metadata: PluginMetadata
|
|
22
|
+
config: Dict[str, Any]
|
|
23
|
+
enabled: bool = True
|
|
24
|
+
load_order: int = 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PluginManager:
|
|
28
|
+
"""
|
|
29
|
+
Manages CloneBox plugins.
|
|
30
|
+
|
|
31
|
+
Plugins can be loaded from:
|
|
32
|
+
- Built-in plugins (clonebox.plugins.builtin.*)
|
|
33
|
+
- User plugins (~/.clonebox.d/plugins/)
|
|
34
|
+
- Project plugins (.clonebox.d/plugins/)
|
|
35
|
+
- Python packages (clonebox_plugin_*)
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
manager = PluginManager()
|
|
39
|
+
manager.discover()
|
|
40
|
+
manager.load_all()
|
|
41
|
+
|
|
42
|
+
# Trigger a hook
|
|
43
|
+
ctx = PluginContext(hook=PluginHook.POST_VM_CREATE, vm_name="my-vm")
|
|
44
|
+
manager.trigger(PluginHook.POST_VM_CREATE, ctx)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
DEFAULT_PLUGIN_DIRS = [
|
|
48
|
+
Path.home() / ".clonebox.d" / "plugins",
|
|
49
|
+
Path(".clonebox.d") / "plugins",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
plugin_dirs: Optional[List[Path]] = None,
|
|
55
|
+
config_path: Optional[Path] = None,
|
|
56
|
+
):
|
|
57
|
+
self._plugins: Dict[str, LoadedPlugin] = {}
|
|
58
|
+
self._hooks: Dict[PluginHook, List[str]] = {hook: [] for hook in PluginHook}
|
|
59
|
+
self._lock = threading.Lock()
|
|
60
|
+
self._load_order = 0
|
|
61
|
+
|
|
62
|
+
# Plugin directories
|
|
63
|
+
self.plugin_dirs = plugin_dirs or self.DEFAULT_PLUGIN_DIRS
|
|
64
|
+
|
|
65
|
+
# Plugin configuration
|
|
66
|
+
self.config_path = config_path or Path.home() / ".clonebox.d" / "plugins.yaml"
|
|
67
|
+
self._plugin_config = self._load_config()
|
|
68
|
+
|
|
69
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
70
|
+
"""Load plugin configuration."""
|
|
71
|
+
if self.config_path.exists():
|
|
72
|
+
try:
|
|
73
|
+
with open(self.config_path) as f:
|
|
74
|
+
return yaml.safe_load(f) or {}
|
|
75
|
+
except Exception:
|
|
76
|
+
return {}
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
def _save_config(self) -> None:
|
|
80
|
+
"""Save plugin configuration."""
|
|
81
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
with open(self.config_path, "w") as f:
|
|
83
|
+
yaml.dump(self._plugin_config, f, default_flow_style=False)
|
|
84
|
+
|
|
85
|
+
def discover(self) -> List[str]:
|
|
86
|
+
"""
|
|
87
|
+
Discover available plugins.
|
|
88
|
+
Returns list of discovered plugin names.
|
|
89
|
+
"""
|
|
90
|
+
discovered: List[str] = []
|
|
91
|
+
|
|
92
|
+
# Discover from plugin directories
|
|
93
|
+
for plugin_dir in self.plugin_dirs:
|
|
94
|
+
if plugin_dir.exists() and plugin_dir.is_dir():
|
|
95
|
+
for item in plugin_dir.iterdir():
|
|
96
|
+
if item.is_file() and item.suffix == ".py" and not item.name.startswith("_"):
|
|
97
|
+
name = item.stem
|
|
98
|
+
discovered.append(f"file:{name}")
|
|
99
|
+
elif item.is_dir() and (item / "__init__.py").exists():
|
|
100
|
+
discovered.append(f"file:{item.name}")
|
|
101
|
+
|
|
102
|
+
# Discover installed packages (clonebox_plugin_*)
|
|
103
|
+
try:
|
|
104
|
+
import pkg_resources
|
|
105
|
+
for ep in pkg_resources.iter_entry_points("clonebox.plugins"):
|
|
106
|
+
discovered.append(f"pkg:{ep.name}")
|
|
107
|
+
except ImportError:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
return discovered
|
|
111
|
+
|
|
112
|
+
def load(self, plugin_name: str, config: Optional[Dict[str, Any]] = None) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Load a specific plugin.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
plugin_name: Plugin name (can be prefixed with file: or pkg:)
|
|
118
|
+
config: Plugin-specific configuration
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if loaded successfully
|
|
122
|
+
"""
|
|
123
|
+
with self._lock:
|
|
124
|
+
if plugin_name in self._plugins:
|
|
125
|
+
return True # Already loaded
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
plugin_class = self._import_plugin(plugin_name)
|
|
129
|
+
if plugin_class is None:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
plugin = plugin_class()
|
|
133
|
+
metadata = plugin.metadata
|
|
134
|
+
|
|
135
|
+
# Get config
|
|
136
|
+
plugin_config = config or self._plugin_config.get(metadata.name, {})
|
|
137
|
+
|
|
138
|
+
# Check dependencies
|
|
139
|
+
for dep in metadata.dependencies:
|
|
140
|
+
if dep not in self._plugins:
|
|
141
|
+
# Try to load dependency
|
|
142
|
+
if not self.load(dep):
|
|
143
|
+
raise RuntimeError(f"Missing dependency: {dep}")
|
|
144
|
+
|
|
145
|
+
# Initialize plugin
|
|
146
|
+
plugin.initialize(plugin_config)
|
|
147
|
+
|
|
148
|
+
# Register plugin
|
|
149
|
+
self._load_order += 1
|
|
150
|
+
loaded = LoadedPlugin(
|
|
151
|
+
plugin=plugin,
|
|
152
|
+
metadata=metadata,
|
|
153
|
+
config=plugin_config,
|
|
154
|
+
enabled=True,
|
|
155
|
+
load_order=self._load_order,
|
|
156
|
+
)
|
|
157
|
+
self._plugins[metadata.name] = loaded
|
|
158
|
+
|
|
159
|
+
# Register hooks
|
|
160
|
+
for hook in metadata.hooks:
|
|
161
|
+
self._hooks[hook].append(metadata.name)
|
|
162
|
+
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
import sys
|
|
167
|
+
print(f"Failed to load plugin {plugin_name}: {e}", file=sys.stderr)
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
def _import_plugin(self, plugin_name: str) -> Optional[Type[Plugin]]:
|
|
171
|
+
"""Import a plugin class."""
|
|
172
|
+
if plugin_name.startswith("file:"):
|
|
173
|
+
return self._import_file_plugin(plugin_name[5:])
|
|
174
|
+
elif plugin_name.startswith("pkg:"):
|
|
175
|
+
return self._import_package_plugin(plugin_name[4:])
|
|
176
|
+
else:
|
|
177
|
+
# Try both
|
|
178
|
+
plugin_class = self._import_package_plugin(plugin_name)
|
|
179
|
+
if plugin_class:
|
|
180
|
+
return plugin_class
|
|
181
|
+
return self._import_file_plugin(plugin_name)
|
|
182
|
+
|
|
183
|
+
def _import_file_plugin(self, name: str) -> Optional[Type[Plugin]]:
|
|
184
|
+
"""Import plugin from file."""
|
|
185
|
+
for plugin_dir in self.plugin_dirs:
|
|
186
|
+
# Try as single file
|
|
187
|
+
plugin_file = plugin_dir / f"{name}.py"
|
|
188
|
+
if plugin_file.exists():
|
|
189
|
+
return self._load_module_from_file(name, plugin_file)
|
|
190
|
+
|
|
191
|
+
# Try as package
|
|
192
|
+
plugin_pkg = plugin_dir / name / "__init__.py"
|
|
193
|
+
if plugin_pkg.exists():
|
|
194
|
+
return self._load_module_from_file(name, plugin_pkg)
|
|
195
|
+
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
def _load_module_from_file(self, name: str, path: Path) -> Optional[Type[Plugin]]:
|
|
199
|
+
"""Load a module from file and find Plugin class."""
|
|
200
|
+
try:
|
|
201
|
+
spec = importlib.util.spec_from_file_location(f"clonebox_plugin_{name}", path)
|
|
202
|
+
if spec is None or spec.loader is None:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
module = importlib.util.module_from_spec(spec)
|
|
206
|
+
sys.modules[spec.name] = module
|
|
207
|
+
spec.loader.exec_module(module)
|
|
208
|
+
|
|
209
|
+
# Find Plugin subclass
|
|
210
|
+
for attr_name in dir(module):
|
|
211
|
+
attr = getattr(module, attr_name)
|
|
212
|
+
if (
|
|
213
|
+
isinstance(attr, type)
|
|
214
|
+
and issubclass(attr, Plugin)
|
|
215
|
+
and attr is not Plugin
|
|
216
|
+
):
|
|
217
|
+
return attr
|
|
218
|
+
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
except Exception:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
def _import_package_plugin(self, name: str) -> Optional[Type[Plugin]]:
|
|
225
|
+
"""Import plugin from installed package."""
|
|
226
|
+
try:
|
|
227
|
+
import pkg_resources
|
|
228
|
+
for ep in pkg_resources.iter_entry_points("clonebox.plugins"):
|
|
229
|
+
if ep.name == name:
|
|
230
|
+
plugin_class = ep.load()
|
|
231
|
+
if issubclass(plugin_class, Plugin):
|
|
232
|
+
return plugin_class
|
|
233
|
+
except ImportError:
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
# Try direct import
|
|
237
|
+
try:
|
|
238
|
+
module = importlib.import_module(f"clonebox_plugin_{name}")
|
|
239
|
+
for attr_name in dir(module):
|
|
240
|
+
attr = getattr(module, attr_name)
|
|
241
|
+
if (
|
|
242
|
+
isinstance(attr, type)
|
|
243
|
+
and issubclass(attr, Plugin)
|
|
244
|
+
and attr is not Plugin
|
|
245
|
+
):
|
|
246
|
+
return attr
|
|
247
|
+
except ImportError:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
def load_all(self, only_enabled: bool = True) -> int:
|
|
253
|
+
"""
|
|
254
|
+
Load all discovered plugins.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
only_enabled: Only load plugins marked as enabled in config
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Number of plugins loaded
|
|
261
|
+
"""
|
|
262
|
+
discovered = self.discover()
|
|
263
|
+
loaded_count = 0
|
|
264
|
+
|
|
265
|
+
# Get enabled plugins from config
|
|
266
|
+
enabled = set(self._plugin_config.get("enabled", []))
|
|
267
|
+
disabled = set(self._plugin_config.get("disabled", []))
|
|
268
|
+
|
|
269
|
+
for plugin_name in discovered:
|
|
270
|
+
# Extract base name
|
|
271
|
+
base_name = plugin_name.split(":", 1)[-1]
|
|
272
|
+
|
|
273
|
+
# Check if enabled/disabled
|
|
274
|
+
if only_enabled:
|
|
275
|
+
if base_name in disabled:
|
|
276
|
+
continue
|
|
277
|
+
if enabled and base_name not in enabled:
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
if self.load(plugin_name):
|
|
281
|
+
loaded_count += 1
|
|
282
|
+
|
|
283
|
+
return loaded_count
|
|
284
|
+
|
|
285
|
+
def unload(self, plugin_name: str) -> bool:
|
|
286
|
+
"""Unload a plugin."""
|
|
287
|
+
with self._lock:
|
|
288
|
+
if plugin_name not in self._plugins:
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
loaded = self._plugins[plugin_name]
|
|
292
|
+
|
|
293
|
+
# Check for dependents
|
|
294
|
+
for name, other in self._plugins.items():
|
|
295
|
+
if plugin_name in other.metadata.dependencies:
|
|
296
|
+
raise RuntimeError(f"Cannot unload: {name} depends on {plugin_name}")
|
|
297
|
+
|
|
298
|
+
# Shutdown plugin
|
|
299
|
+
try:
|
|
300
|
+
loaded.plugin.shutdown()
|
|
301
|
+
except Exception:
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
# Remove from hooks
|
|
305
|
+
for hook in loaded.metadata.hooks:
|
|
306
|
+
if plugin_name in self._hooks[hook]:
|
|
307
|
+
self._hooks[hook].remove(plugin_name)
|
|
308
|
+
|
|
309
|
+
# Remove plugin
|
|
310
|
+
del self._plugins[plugin_name]
|
|
311
|
+
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
def unload_all(self) -> None:
|
|
315
|
+
"""Unload all plugins in reverse load order."""
|
|
316
|
+
# Sort by load order descending
|
|
317
|
+
plugins = sorted(
|
|
318
|
+
self._plugins.values(),
|
|
319
|
+
key=lambda p: p.load_order,
|
|
320
|
+
reverse=True,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
for loaded in plugins:
|
|
324
|
+
try:
|
|
325
|
+
self.unload(loaded.metadata.name)
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
def trigger(self, hook: PluginHook, ctx: PluginContext) -> PluginContext:
|
|
330
|
+
"""
|
|
331
|
+
Trigger a hook on all registered plugins.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
hook: The hook to trigger
|
|
335
|
+
ctx: Context to pass to plugins
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
The context (possibly modified by plugins)
|
|
339
|
+
"""
|
|
340
|
+
ctx.hook = hook
|
|
341
|
+
|
|
342
|
+
for plugin_name in self._hooks.get(hook, []):
|
|
343
|
+
if not ctx.should_continue:
|
|
344
|
+
break
|
|
345
|
+
|
|
346
|
+
loaded = self._plugins.get(plugin_name)
|
|
347
|
+
if loaded and loaded.enabled:
|
|
348
|
+
try:
|
|
349
|
+
loaded.plugin.handle_hook(hook, ctx)
|
|
350
|
+
except Exception as e:
|
|
351
|
+
ctx.add_warning(f"Plugin {plugin_name} error: {e}")
|
|
352
|
+
|
|
353
|
+
return ctx
|
|
354
|
+
|
|
355
|
+
def enable(self, plugin_name: str) -> bool:
|
|
356
|
+
"""Enable a plugin."""
|
|
357
|
+
with self._lock:
|
|
358
|
+
if plugin_name in self._plugins:
|
|
359
|
+
self._plugins[plugin_name].enabled = True
|
|
360
|
+
|
|
361
|
+
# Update config
|
|
362
|
+
disabled = set(self._plugin_config.get("disabled", []))
|
|
363
|
+
disabled.discard(plugin_name)
|
|
364
|
+
self._plugin_config["disabled"] = list(disabled)
|
|
365
|
+
|
|
366
|
+
enabled = set(self._plugin_config.get("enabled", []))
|
|
367
|
+
enabled.add(plugin_name)
|
|
368
|
+
self._plugin_config["enabled"] = list(enabled)
|
|
369
|
+
|
|
370
|
+
self._save_config()
|
|
371
|
+
return True
|
|
372
|
+
|
|
373
|
+
def disable(self, plugin_name: str) -> bool:
|
|
374
|
+
"""Disable a plugin."""
|
|
375
|
+
with self._lock:
|
|
376
|
+
if plugin_name in self._plugins:
|
|
377
|
+
self._plugins[plugin_name].enabled = False
|
|
378
|
+
|
|
379
|
+
# Update config
|
|
380
|
+
enabled = set(self._plugin_config.get("enabled", []))
|
|
381
|
+
enabled.discard(plugin_name)
|
|
382
|
+
self._plugin_config["enabled"] = list(enabled)
|
|
383
|
+
|
|
384
|
+
disabled = set(self._plugin_config.get("disabled", []))
|
|
385
|
+
disabled.add(plugin_name)
|
|
386
|
+
self._plugin_config["disabled"] = list(disabled)
|
|
387
|
+
|
|
388
|
+
self._save_config()
|
|
389
|
+
return True
|
|
390
|
+
|
|
391
|
+
def list_plugins(self) -> List[Dict[str, Any]]:
|
|
392
|
+
"""List all loaded plugins."""
|
|
393
|
+
return [
|
|
394
|
+
{
|
|
395
|
+
**loaded.metadata.to_dict(),
|
|
396
|
+
"enabled": loaded.enabled,
|
|
397
|
+
"load_order": loaded.load_order,
|
|
398
|
+
}
|
|
399
|
+
for loaded in sorted(self._plugins.values(), key=lambda p: p.load_order)
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
def get_plugin(self, name: str) -> Optional[Plugin]:
|
|
403
|
+
"""Get a loaded plugin by name."""
|
|
404
|
+
loaded = self._plugins.get(name)
|
|
405
|
+
return loaded.plugin if loaded else None
|
|
406
|
+
|
|
407
|
+
def has_plugin(self, name: str) -> bool:
|
|
408
|
+
"""Check if a plugin is loaded."""
|
|
409
|
+
return name in self._plugins
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# Global plugin manager
|
|
413
|
+
_plugin_manager: Optional[PluginManager] = None
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def get_plugin_manager() -> PluginManager:
|
|
417
|
+
"""Get the global plugin manager."""
|
|
418
|
+
global _plugin_manager
|
|
419
|
+
if _plugin_manager is None:
|
|
420
|
+
_plugin_manager = PluginManager()
|
|
421
|
+
return _plugin_manager
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def set_plugin_manager(manager: PluginManager) -> None:
|
|
425
|
+
"""Set the global plugin manager (useful for testing)."""
|
|
426
|
+
global _plugin_manager
|
|
427
|
+
_plugin_manager = manager
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def trigger_hook(hook: PluginHook, **kwargs: Any) -> PluginContext:
|
|
431
|
+
"""
|
|
432
|
+
Convenience function to trigger a hook.
|
|
433
|
+
|
|
434
|
+
Usage:
|
|
435
|
+
ctx = trigger_hook(PluginHook.POST_VM_CREATE, vm_name="my-vm")
|
|
436
|
+
"""
|
|
437
|
+
ctx = PluginContext(hook=hook, **kwargs)
|
|
438
|
+
return get_plugin_manager().trigger(hook, ctx)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
|
|
2
2
|
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
|
-
clonebox/
|
|
4
|
-
clonebox/
|
|
3
|
+
clonebox/audit.py,sha256=67V2ap4QYVXH41b2GmNeRw0-0BilPPTgbDJ0eA5xv2A,14119
|
|
4
|
+
clonebox/cli.py,sha256=7QCjprKNRguPVHhxXzd2ej-t61W8kOcxlSMSNxYoXag,153643
|
|
5
|
+
clonebox/cloner.py,sha256=Hf4ugJ0lZ52etrnY2RoopwbMaQsPctZp5d1i3_qQKsU,97470
|
|
5
6
|
clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
|
|
6
7
|
clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
|
|
7
8
|
clonebox/detector.py,sha256=vS65cvFNPmUBCX1Y_TMTnSRljw6r1Ae9dlVtACs5XFc,23075
|
|
@@ -11,6 +12,7 @@ clonebox/importer.py,sha256=Q9Uk1IOA41mgGhU4ynW2k-h9GEoGxRKI3c9wWE4uxcA,7097
|
|
|
11
12
|
clonebox/logging.py,sha256=jD_WgkHmt_h99EmX82QWK7D0OKWyxqcLwgT4UDQFp2g,3675
|
|
12
13
|
clonebox/models.py,sha256=13B0lVAuaGnpY4h4iYUPigIvfxYx2pWaAEM1cYF_Hbo,8263
|
|
13
14
|
clonebox/monitor.py,sha256=zlIarNf8w_i34XI8hZGxxrg5PVZK_Yxm6FQnkhLavRI,9181
|
|
15
|
+
clonebox/orchestrator.py,sha256=LvoDZIX47g2pdvMYfCW3NJ5sAiRYKKa45KGlujnJZuI,19871
|
|
14
16
|
clonebox/p2p.py,sha256=6o0JnscKqF9-BftQhW5fF1W6YY1wXshY9LEklNcHGJc,5913
|
|
15
17
|
clonebox/profiles.py,sha256=UP37fX_rhrG_O9ehNFJBUcULPmUtN1A8KsJ6cM44oK0,1986
|
|
16
18
|
clonebox/resource_monitor.py,sha256=lDR9KyPbVtImeeOkOBPPVP-5yCgoL5hsVFPZ_UqsY0w,5286
|
|
@@ -29,14 +31,17 @@ clonebox/interfaces/disk.py,sha256=F7Xzj2dq5UTZ2KGCuThDM8bwTps6chFbquOUmfLREjI,9
|
|
|
29
31
|
clonebox/interfaces/hypervisor.py,sha256=8ms4kZLA-5Ba1e_n68mCucwP_K9mufbmTBlo7XzURn4,1991
|
|
30
32
|
clonebox/interfaces/network.py,sha256=YPIquxEB7sZHczbpuopcZpffTjWYI6cKmAu3wAEFllk,853
|
|
31
33
|
clonebox/interfaces/process.py,sha256=njvAIZw_TCjw01KpyVQKIDoRvhTwl0FfVGbQ6mxTROk,1024
|
|
34
|
+
clonebox/plugins/__init__.py,sha256=3cxlz159nokZCOL2c017WqTwt5z00yyn-o-SemP1g6c,416
|
|
35
|
+
clonebox/plugins/base.py,sha256=A2H-2vrYUczNZCDioQ8cAtvaSob4YpXutx7FWMjksC4,10133
|
|
36
|
+
clonebox/plugins/manager.py,sha256=gotjHloAiiADLJjATOvdBxrasU3Rc8h2fW0MwVGIhCI,14105
|
|
32
37
|
clonebox/snapshots/__init__.py,sha256=ndlrIavPAiA8z4Ep3-D_EPhOcjNKYFnP3rIpEKaGdb8,273
|
|
33
38
|
clonebox/snapshots/manager.py,sha256=hGzM8V6ZJPXjTqj47c4Kr8idlE-c1Q3gPUvuw1HvS1A,11393
|
|
34
39
|
clonebox/snapshots/models.py,sha256=sRnn3OZE8JG9FZJlRuA3ihO-JXoPCQ3nD3SQytflAao,6206
|
|
35
40
|
clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
|
|
36
41
|
clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
|
|
37
|
-
clonebox-1.1.
|
|
38
|
-
clonebox-1.1.
|
|
39
|
-
clonebox-1.1.
|
|
40
|
-
clonebox-1.1.
|
|
41
|
-
clonebox-1.1.
|
|
42
|
-
clonebox-1.1.
|
|
42
|
+
clonebox-1.1.14.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
43
|
+
clonebox-1.1.14.dist-info/METADATA,sha256=MCTVRt4sr9hDpl7N0x_DwR0067cNXqL-Ti1huPFVU60,48916
|
|
44
|
+
clonebox-1.1.14.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
45
|
+
clonebox-1.1.14.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
46
|
+
clonebox-1.1.14.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
47
|
+
clonebox-1.1.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|