|
|
|
|
@ -30,12 +30,20 @@ class Agent():
|
|
|
|
|
self.applied_config = None
|
|
|
|
|
# Split the applied_config up into core and plugins
|
|
|
|
|
self.core_config = None
|
|
|
|
|
self.plugin_configs = None
|
|
|
|
|
|
|
|
|
|
self.interface_functions = None
|
|
|
|
|
|
|
|
|
|
self.control_enabled = control_enabled
|
|
|
|
|
|
|
|
|
|
self.core_interface = plugin.PluginInterface()
|
|
|
|
|
self.plugin_interfaces = None
|
|
|
|
|
|
|
|
|
|
def root_dir(self):
|
|
|
|
|
return self.core_config["root_dir"]
|
|
|
|
|
|
|
|
|
|
def device_name(self):
|
|
|
|
|
return self.core_config["name"]
|
|
|
|
|
|
|
|
|
|
def load(self, default_config_path, use_custom_config=True, new_device_mode=False):
|
|
|
|
|
"""
|
|
|
|
|
Load in the Shepherd Agent config and associated plugins.
|
|
|
|
|
@ -48,18 +56,45 @@ class Agent():
|
|
|
|
|
of ID, as if it were being run on a fresh system.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Setup core interface
|
|
|
|
|
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(self.core_interface)
|
|
|
|
|
control.register(self.core_interface)
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
confman = ConfigManager()
|
|
|
|
|
# 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
|
|
|
|
|
# interface, as it needs 'plugin_dir' to list the plugins to load first.
|
|
|
|
|
confman.add_confspec("shepherd", self.core_interface._confspec)
|
|
|
|
|
|
|
|
|
|
compile_local_config(confman, default_config_path, use_custom_config)
|
|
|
|
|
self.local_config = deepcopy(confman.get_config_bundles())
|
|
|
|
|
|
|
|
|
|
# Check for new device mode
|
|
|
|
|
core_conf = confman.get_config_bundle('shepherd')
|
|
|
|
|
local_core_conf = confman.get_config_bundle('shepherd')
|
|
|
|
|
|
|
|
|
|
if new_device_mode or check_new_device_file(core_conf["custom_config_path"]):
|
|
|
|
|
# Check for new device mode
|
|
|
|
|
if new_device_mode or check_new_device_file(local_core_conf["custom_config_path"]):
|
|
|
|
|
log.info("'new device' mode enabled, clearing old state...")
|
|
|
|
|
control.generate_device_identity(core_conf["root_dir"])
|
|
|
|
|
control.clear_cached_config(core_conf["root_dir"])
|
|
|
|
|
control.generate_device_identity(local_core_conf["root_dir"])
|
|
|
|
|
control.clear_cached_config(local_core_conf["root_dir"])
|
|
|
|
|
|
|
|
|
|
if local_core_conf["control"] is None:
|
|
|
|
|
self.control_enabled = False
|
|
|
|
|
log.warning("Shepherd control config section not present. Will not attempt to"
|
|
|
|
|
" connect to Shepherd Control server.")
|
|
|
|
|
|
|
|
|
|
if self.control_enabled:
|
|
|
|
|
compile_remote_config(confman)
|
|
|
|
|
@ -67,21 +102,23 @@ class Agent():
|
|
|
|
|
log.info("Shepherd Control config layer disabled")
|
|
|
|
|
|
|
|
|
|
self.applied_config = confman.get_config_bundles()
|
|
|
|
|
self.plugin_configs = confman.get_config_bundles()
|
|
|
|
|
self.core_config = self.plugin_configs.pop('shepherd')
|
|
|
|
|
self.core_config = confman.get_config_bundle('shepherd')
|
|
|
|
|
|
|
|
|
|
log.debug("Compiled config: %s", confman.root_config)
|
|
|
|
|
if core_conf["compiled_config_path"]:
|
|
|
|
|
if self.core_config["compiled_config_path"]:
|
|
|
|
|
message = F"Compiled Shepherd config at {datetime.now()}"
|
|
|
|
|
confman.dump_to_file(core_conf["compiled_config_path"], message=message)
|
|
|
|
|
log.info(F"Saved compiled config to {core_conf['compiled_config_path']}")
|
|
|
|
|
confman.dump_to_file(self.core_config["compiled_config_path"], message=message)
|
|
|
|
|
log.info(F"Saved compiled config to {self.core_config['compiled_config_path']}")
|
|
|
|
|
|
|
|
|
|
def restart(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
|
|
|
|
|
|
# After this point, plugins may already have their own threads running if they create
|
|
|
|
|
# them during init
|
|
|
|
|
plugin_interfaces, self.interface_functions = plugin.init_plugins(
|
|
|
|
|
self.plugin_configs, self.core_config, {})
|
|
|
|
|
self.plugin_interfaces = plugin.init_plugins(self.applied_config)
|
|
|
|
|
self.interface_functions = self.core_interface.plugins
|
|
|
|
|
|
|
|
|
|
cmd_runner = control.CommandRunner(self.interface_functions)
|
|
|
|
|
core_update_state = control.CoreUpdateState(cmd_runner.cmd_reader,
|
|
|
|
|
@ -89,22 +126,11 @@ class Agent():
|
|
|
|
|
core_update_state.set_static_state(self.local_config, self.applied_config, core_confspec())
|
|
|
|
|
|
|
|
|
|
plugin_update_states = {name: iface._update_state
|
|
|
|
|
for name, iface in plugin_interfaces.items()}
|
|
|
|
|
for name, iface in self.plugin_interfaces.items()}
|
|
|
|
|
|
|
|
|
|
if self.control_enabled:
|
|
|
|
|
if self.core_config["control"] is not None:
|
|
|
|
|
control.start_control(self.core_config["control"], self.core_config["root_dir"],
|
|
|
|
|
control.start_control(self.core_config["control"], self.root_dir(),
|
|
|
|
|
core_update_state, plugin_update_states)
|
|
|
|
|
else:
|
|
|
|
|
log.warning("Shepherd control config section not present. Will not attempt to"
|
|
|
|
|
" connect to Shepherd Control server.")
|
|
|
|
|
|
|
|
|
|
# Shift Control check to when it actually tries to find config, and just set
|
|
|
|
|
# control_enabled to false.
|
|
|
|
|
# Does all the other cmd_runner and update state stuff need to happen still if
|
|
|
|
|
# control is disabled?
|
|
|
|
|
# Should Core really need to know anything about the command runner, or should it
|
|
|
|
|
# just hand the interface functions in directly to Control?
|
|
|
|
|
|
|
|
|
|
# Need somewhere to eventually pass in the hooks Tasks will need for the lowpower stuff,
|
|
|
|
|
# probably just another init_plugins arg.
|
|
|
|
|
@ -139,8 +165,6 @@ def compile_local_config(confman, default_config_path, use_custom_config):
|
|
|
|
|
As part of this, load the required plugins into cache (required to validate their config).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
confman.add_confspec("shepherd", core_confspec())
|
|
|
|
|
|
|
|
|
|
# ====Default Local Config Layer====
|
|
|
|
|
# This must validate to continue.
|
|
|
|
|
default_config_path = Path(default_config_path).expanduser().resolve()
|
|
|
|
|
@ -202,8 +226,8 @@ def compile_remote_config(confman):
|
|
|
|
|
# ====Control Remote Config Layer====
|
|
|
|
|
|
|
|
|
|
# Freeze Shepherd Control related config.
|
|
|
|
|
confman.freeze_value("shepherd", "control_server")
|
|
|
|
|
confman.freeze_value("shepherd", "control_api_key")
|
|
|
|
|
confman.freeze_value("shepherd", "control", "server")
|
|
|
|
|
confman.freeze_value("shepherd", "control", "intro_key")
|
|
|
|
|
|
|
|
|
|
# Save current good local config
|
|
|
|
|
confman.save_fallback()
|
|
|
|
|
@ -255,7 +279,6 @@ def core_confspec():
|
|
|
|
|
"./shepherd-plugins")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
confspec.add_spec("control", control.control_confspec(), optional=True)
|
|
|
|
|
return confspec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -300,7 +323,6 @@ def load_config_layer_and_plugins(confman: ConfigManager, config_source):
|
|
|
|
|
|
|
|
|
|
# List other bundle names to get plugins we need to load
|
|
|
|
|
plugin_names = confman.get_bundle_names()
|
|
|
|
|
plugin_names.remove("shepherd")
|
|
|
|
|
|
|
|
|
|
# Load plugins to get their config specifications
|
|
|
|
|
plugin_interfaces = {name: plugin.load_plugin(name, plugin_dir) for name in plugin_names}
|
|
|
|
|
|