|  |  | @ -19,12 +19,54 @@ from . import tasks | 
			
		
	
		
		
			
				
					
					|  |  |  | log = logging.getLogger("shepherd.agent") |  |  |  | log = logging.getLogger("shepherd.agent") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | core_interface = plugin.PluginInterface() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | confspec = ConfigSpecification() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | # Relative pathnames here are all relative to "root_dir". `root_dir` itself is relative to | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | # the directory the default config is loaded from | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | confspec.add_specs({ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     "name": StringSpec(helptext="Identifying name for this device"), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | }) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | confspec.add_specs(optional=True, spec_dict={ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     "root_dir": | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         (StringSpec(helptext="Operating directory for shepherd to place working files." | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     " Relative to the directory containing the default config file."), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             "./"), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     "custom_config_path": | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         StringSpec(helptext="Path to custom config layer TOML file."), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     "compiled_config_path": | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         (StringSpec(helptext="Path to custom file Shepherd will generate to show compiled" | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     " config that was used and any errors in validation."), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             "compiled-config.toml"), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     "plugin_dir": | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         (StringSpec(helptext="Optional directory for Shepherd to look for plugins in."), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             "./shepherd-plugins") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | }) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | core_interface.register_confspec(confspec) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | # Allows plugins to add delay for system time to stabilise | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | core_interface.register_hook("wait_for_stable_time") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | # Allow other modules to add to the core interface (confspec, hooks, interface functions) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | # Having modules modify a confspec after it's registered here is a bit of a hack. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | tasks.register_on(core_interface) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | control.register_on(core_interface) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | @ plugin.plugin_class | 
			
		
	
		
		
			
				
					
					|  |  |  | class Agent(): |  |  |  | class Agent(): | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |     """ | 
			
		
	
		
		
			
				
					
					|  |  |  |     Holds the main state required to run Shepherd Agent |  |  |  |     Holds the main state required to run Shepherd Agent | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |     """ | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     def __init__(self): |  |  |  |     def __init__(self): | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         # Make sure the plugin system uses this instance rather making its own | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         core_interface._plugin_obj = self | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         # The config defined by the device (everything before the Control layer) |  |  |  |         # The config defined by the device (everything before the Control layer) | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.local_config = None |  |  |  |         self.local_config = None | 
			
		
	
		
		
			
				
					
					|  |  |  |         # The config actually being used |  |  |  |         # The config actually being used | 
			
		
	
	
		
		
			
				
					|  |  | @ -38,22 +80,11 @@ class Agent(): | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.restart_args = None |  |  |  |         self.restart_args = None | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Setup core interface |  |  |  |     @ plugin.plugin_function | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         self.core_interface = plugin.PluginInterface() |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.core_interface.register_confspec(core_confspec()) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.core_interface.register_function(self.root_dir) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.core_interface.register_function(self.device_name) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Allows plugins to add delay for system time to stabilise |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.core_interface.register_hook("wait_for_stable_time") |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Allow other modules to add to the core interface (confspec, hooks, interface functions) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Having modules modify a confspec after it's registered here is a bit of a hack. |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         tasks.register_on(self.core_interface) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         control.register_on(self.core_interface) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     def root_dir(self): |  |  |  |     def root_dir(self): | 
			
		
	
		
		
			
				
					
					|  |  |  |         return self.core_config["root_dir"] |  |  |  |         return self.core_config["root_dir"] | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     @ plugin.plugin_function | 
			
		
	
		
		
			
				
					
					|  |  |  |     def device_name(self): |  |  |  |     def device_name(self): | 
			
		
	
		
		
			
				
					
					|  |  |  |         return self.core_config["name"] |  |  |  |         return self.core_config["name"] | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -74,9 +105,6 @@ class Agent(): | 
			
		
	
		
		
			
				
					
					|  |  |  |                              use_custom_config, control_enabled, new_device_mode] |  |  |  |                              use_custom_config, control_enabled, new_device_mode] | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.control_enabled = control_enabled |  |  |  |         self.control_enabled = control_enabled | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Because the plugin module caches interfaces, this will then get used when loading |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         # config layers and validating them |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         plugin.load_plugin_interface("shepherd", self.core_interface) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Compile the config layers |  |  |  |         # Compile the config layers | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -84,7 +112,7 @@ class Agent(): | 
			
		
	
		
		
			
				
					
					|  |  |  |         # Pre-seed confman with core confspec to bootstrap 'plugin_dir'. This is required even |  |  |  |         # Pre-seed confman with core confspec to bootstrap 'plugin_dir'. This is required even | 
			
		
	
		
		
			
				
					
					|  |  |  |         # though 'load_config_layer_and_plugins()' will get the core confspec from the cached |  |  |  |         # though 'load_config_layer_and_plugins()' will get the core confspec from the cached | 
			
		
	
		
		
			
				
					
					|  |  |  |         # interface, as it needs 'plugin_dir' to list the plugins to load first. |  |  |  |         # interface, as it needs 'plugin_dir' to list the plugins to load first. | 
			
		
	
		
		
			
				
					
					|  |  |  |         confman.add_confspec("shepherd", self.core_interface._confspec) |  |  |  |         confman.add_confspec("shepherd", core_interface.confspec) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         compile_local_config(confman, default_config_path, use_custom_config) |  |  |  |         compile_local_config(confman, default_config_path, use_custom_config) | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.local_config = deepcopy(confman.get_config_bundles()) |  |  |  |         self.local_config = deepcopy(confman.get_config_bundles()) | 
			
		
	
	
		
		
			
				
					|  |  | @ -131,12 +159,13 @@ class Agent(): | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.plugin_interfaces = plugin.init_plugins(self.applied_config) |  |  |  |         self.plugin_interfaces = plugin.init_plugins(self.applied_config) | 
			
		
	
		
		
			
				
					
					|  |  |  |         # After this point, plugins may already have their own threads running if they created |  |  |  |         # After this point, plugins may already have their own threads running if they created | 
			
		
	
		
		
			
				
					
					|  |  |  |         # them during init |  |  |  |         # them during init | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.interface_functions = self.core_interface.plugins |  |  |  |         self.interface_functions = core_interface.plugins | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         cmd_runner = control.CommandRunner(self.interface_functions) |  |  |  |         cmd_runner = control.CommandRunner(self.interface_functions) | 
			
		
	
		
		
			
				
					
					|  |  |  |         core_update_state = control.CoreUpdateState(cmd_runner.cmd_reader, |  |  |  |         core_update_state = control.CoreUpdateState(cmd_runner.cmd_reader, | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     cmd_runner.cmd_result_writer) |  |  |  |                                                     cmd_runner.cmd_result_writer) | 
			
		
	
		
		
			
				
					
					|  |  |  |         core_update_state.set_static_state(self.local_config, self.applied_config, core_confspec()) |  |  |  |         core_update_state.set_static_state( | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             self.local_config, self.applied_config, core_interface.confspec) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         plugin_update_states = {name: iface._update_state |  |  |  |         plugin_update_states = {name: iface._update_state | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 for name, iface in self.plugin_interfaces.items()} |  |  |  |                                 for name, iface in self.plugin_interfaces.items()} | 
			
		
	
	
		
		
			
				
					|  |  | @ -155,7 +184,7 @@ class Agent(): | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         # TODO Any time stabilisation or waiting for Control |  |  |  |         # TODO Any time stabilisation or waiting for Control | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         tasks.start_tasks(self.core_interface, task_session) |  |  |  |         tasks.start_tasks(core_interface, task_session) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         # tasks.init_tasks(self.core_config)  # seperate tasks.start? |  |  |  |         # tasks.init_tasks(self.core_config)  # seperate tasks.start? | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -270,37 +299,6 @@ def compile_remote_config(confman): | 
			
		
	
		
		
			
				
					
					|  |  |  |         log.warning("No cached Shepherd Control config layer available.") |  |  |  |         log.warning("No cached Shepherd Control config layer available.") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | # Relative pathnames here are all relative to "root_dir". `root_dir` itself is relative to |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | # the directory the default config is loaded from |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | def core_confspec(): |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     Returns the core config specification |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     confspec = ConfigSpecification() |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     confspec.add_specs({ |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         "name": StringSpec(helptext="Identifying name for this device"), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     }) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     confspec.add_specs(optional=True, spec_dict={ |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         "root_dir": |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |             (StringSpec(helptext="Operating directory for shepherd to place working files." |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         " Relative to the directory containing the default config file."), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |              "./"), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         "custom_config_path": |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |             StringSpec(helptext="Path to custom config layer TOML file."), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         "compiled_config_path": |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |             (StringSpec(helptext="Path to custom file Shepherd will generate to show compiled" |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         " config that was used and any errors in validation."), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |              "compiled-config.toml"), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         "plugin_dir": |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |             (StringSpec(helptext="Optional directory for Shepherd to look for plugins in."), |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                 "./shepherd-plugins") |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     }) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     return confspec |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | def resolve_core_conf_paths(core_conf, relative_dir): |  |  |  | def resolve_core_conf_paths(core_conf, relative_dir): | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |     """ | 
			
		
	
		
		
			
				
					
					|  |  |  |     Set the cwd to ``root_dir`` and resolve other core config paths relative to that. |  |  |  |     Set the cwd to ``root_dir`` and resolve other core config paths relative to that. | 
			
		
	
	
		
		
			
				
					|  |  | 
 |