|  |  | @ -4,10 +4,12 @@ Core shepherd module, tying together main service functionality. | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | import os |  |  |  | import os | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | import sys | 
			
		
	
		
		
			
				
					
					|  |  |  | from pathlib import Path |  |  |  | from pathlib import Path | 
			
		
	
		
		
			
				
					
					|  |  |  | import glob |  |  |  | import glob | 
			
		
	
		
		
			
				
					
					|  |  |  | from datetime import datetime |  |  |  | from datetime import datetime | 
			
		
	
		
		
			
				
					
					|  |  |  | import logging |  |  |  | import logging | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | import chromalog | 
			
		
	
		
		
			
				
					
					|  |  |  | import pkg_resources |  |  |  | import pkg_resources | 
			
		
	
		
		
			
				
					
					|  |  |  | import click |  |  |  | import click | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -18,49 +20,45 @@ from . import plugin | 
			
		
	
		
		
			
				
					
					|  |  |  | from . import control |  |  |  | from . import control | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | chromalog.basicConfig(level=os.environ.get("LOGLEVEL", "INFO")) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | log = logging.getLogger("shepherd-agent") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | @click.group(invoke_without_command=True) |  |  |  | @click.group(invoke_without_command=True) | 
			
		
	
		
		
			
				
					
					|  |  |  | #help="Path to default config TOML file" |  |  |  | @click.option('-c', '--config', 'default_config_path',  type=click.Path(), | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | @click.argument('default_config_path', required=False, type=click.Path()) |  |  |  |               help="Shepherd config TOML file to be used as default config layer." | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | @click.option('-l', '--local-config', 'only_local_layers', is_flag=True, |  |  |  |               " Overrides default './shepherd*.toml' search") | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |               help="Only use the local config layers (default and custom)") |  |  |  | @click.option('-l', '--local', 'local_operation', is_flag=True, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | @click.option('-d', '--default-config', 'only_default_layer', is_flag=True, |  |  |  |               help="Only use the local config layers (default and custom), and disable all" | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |               help="Only use the default config layer (ignore" |  |  |  |               " Shepherd Control remote features") | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |               "custom and control layers)") |  |  |  | @click.option('-d', '--default-config-only', 'only_default_layer', is_flag=True, | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |               help="Ignore the custom config layer (still uses the Control config above that)") | 
			
		
	
		
		
			
				
					
					|  |  |  | @click.pass_context |  |  |  | @click.pass_context | 
			
		
	
		
		
			
				
					
					|  |  |  | def cli(ctx, default_config_path, only_local_layers, only_default_layer): |  |  |  | def cli(ctx, default_config_path, local_operation, only_default_layer): | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |     """ | 
			
		
	
		
		
			
				
					
					|  |  |  |     Core service. DEFAULT_CONFIG_PATH must point to a valid Shepherd config TOML file. If not |  |  |  |     Core service. If default config file is not provided with '-c' option, the first filename | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     provided, the first filename in the current working directory beginning  with "shepherd" and |  |  |  |     in the current working directory beginning  with "shepherd" and | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     ending with ".toml" will be used. |  |  |  |     ending with ".toml" will be used. | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |     """ | 
			
		
	
		
		
			
				
					
					|  |  |  |     version_text = pkg_resources.get_distribution("shepherd") |  |  |  |     version_text = pkg_resources.get_distribution("shepherd") | 
			
		
	
		
		
			
				
					
					|  |  |  |     logging.info(F"Initialising Shepherd Agent {version_text}") |  |  |  |     log.info(F"Initialising Shepherd Agent {version_text}") | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     # argparser = argparse.ArgumentParser(description="Keep track of a mob " |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     #                                                "of roaming Pis") |  |  |  |     if not default_config_path: | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     # argparser.add_argument("configfile", nargs='?', metavar="configfile", |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     #                       help="Path to configfile", default="shepherd.toml") |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     # argparser.add_argument( |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     #    '-e', '--noedit', help="Disable the editable config temporarily", action="store_true", default=False) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     # argparser.add_argument("-t", "--test", help="Test and interface function of the from 'plugin:function'", |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     #                       default=None) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     #args = argparser.parse_args() |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     if default_config_path is None: |  |  |  |  | 
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         default_config_path = sorted(glob.glob("./shepherd*.toml"))[:1] |  |  |  |         default_config_path = sorted(glob.glob("./shepherd*.toml"))[:1] | 
			
		
	
		
		
			
				
					
					|  |  |  |         if default_config_path: |  |  |  |         if default_config_path: | 
			
		
	
		
		
			
				
					
					|  |  |  |             default_config_path = default_config_path[0] |  |  |  |             default_config_path = default_config_path[0] | 
			
		
	
		
		
			
				
					
					|  |  |  |             logging.info(F"No default config file provided, using {default_config_path}") |  |  |  |             log.info(F"No default config file provided, using {default_config_path}") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         else: |  |  |  |         else: | 
			
		
	
		
		
			
				
					
					|  |  |  |             raise Exception("No default config file provided, and no 'shepherd*.toml' could be" |  |  |  |             log.error("No default config file provided, and no 'shepherd*.toml' could be" | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             " found in the current directory") |  |  |  |                       " found in the current directory") | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             sys.exit(1) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     layers_disabled = [] |  |  |  |     layers_disabled = [] | 
			
		
	
		
		
			
				
					
					|  |  |  |     if only_local_layers: |  |  |  |     if local_operation: | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         layers_disabled.append("control") |  |  |  |         layers_disabled.append("control") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         log.info("Running in local only mode") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     if only_default_layer: |  |  |  |     if only_default_layer: | 
			
		
	
		
		
			
				
					
					|  |  |  |         layers_disabled.append("control") |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         layers_disabled.append("custom") |  |  |  |         layers_disabled.append("custom") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     confman = ConfigManager() |  |  |  |     confman = ConfigManager() | 
			
		
	
	
		
		
			
				
					|  |  | @ -88,6 +86,11 @@ def cli(ctx, default_config_path, only_local_layers, only_default_layer): | 
			
		
	
		
		
			
				
					
					|  |  |  |         pass |  |  |  |         pass | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | @cli.command() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | def test(): | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     print("test!") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | def compile_config_and_get_plugins(confman, default_config_path, layers_disabled): |  |  |  | def compile_config_and_get_plugins(confman, default_config_path, layers_disabled): | 
			
		
	
		
		
			
				
					
					|  |  |  |     """ |  |  |  |     """ | 
			
		
	
		
		
			
				
					
					|  |  |  |     Run through the process of assembling the various config layers, falling back to working |  |  |  |     Run through the process of assembling the various config layers, falling back to working | 
			
		
	
	
		
		
			
				
					|  |  | @ -104,10 +107,13 @@ def compile_config_and_get_plugins(confman, default_config_path, layers_disabled | 
			
		
	
		
		
			
				
					
					|  |  |  |     default_config_path = Path(default_config_path).expanduser() |  |  |  |     default_config_path = Path(default_config_path).expanduser() | 
			
		
	
		
		
			
				
					
					|  |  |  |     try: |  |  |  |     try: | 
			
		
	
		
		
			
				
					
					|  |  |  |         plugin_classes = load_config_layer_and_plugins(confman, default_config_path) |  |  |  |         plugin_classes = load_config_layer_and_plugins(confman, default_config_path) | 
			
		
	
		
		
			
				
					
					|  |  |  |         logging.info(F"Loaded default config layer from {default_config_path}") |  |  |  |         log.info(F"Loaded default config layer from {default_config_path}") | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     except Exception: |  |  |  |     except Exception as e: | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         logging.error(F"Failed to load default config from {default_config_path}") |  |  |  |         if isinstance(e, InvalidConfigError): | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         raise |  |  |  |             log.error(F"Failed to load default config from {default_config_path}. {e.args[0]}") | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         else: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             log.error(F"Failed to load default config from {default_config_path}", exc_info=True) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         sys.exit(1) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     # Resolve and freeze local install paths that shouldn't be changed from default config |  |  |  |     # Resolve and freeze local install paths that shouldn't be changed from default config | 
			
		
	
		
		
			
				
					
					|  |  |  |     core_conf = confman.get_config_bundle("shepherd") |  |  |  |     core_conf = confman.get_config_bundle("shepherd") | 
			
		
	
	
		
		
			
				
					|  |  | @ -126,14 +132,19 @@ def compile_config_and_get_plugins(confman, default_config_path, layers_disabled | 
			
		
	
		
		
			
				
					
					|  |  |  |     if "custom" not in layers_disabled: |  |  |  |     if "custom" not in layers_disabled: | 
			
		
	
		
		
			
				
					
					|  |  |  |         try: |  |  |  |         try: | 
			
		
	
		
		
			
				
					
					|  |  |  |             plugin_classes = load_config_layer_and_plugins(confman, custom_config_path) |  |  |  |             plugin_classes = load_config_layer_and_plugins(confman, custom_config_path) | 
			
		
	
		
		
			
				
					
					|  |  |  |             logging.info(F"Loaded custom config layer from {custom_config_path}") |  |  |  |             log.info(F"Loaded custom config layer from {custom_config_path}") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         except Exception as e: |  |  |  |         except Exception as e: | 
			
		
	
		
		
			
				
					
					|  |  |  |             logging.error( |  |  |  |             if isinstance(e, InvalidConfigError): | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                 F"Failed to load custom config layer from {custom_config_path}. Falling back" |  |  |  |                 log.error(F"Failed to load custom config layer from {custom_config_path}." | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                 " to default config.", exc_info=e) |  |  |  |                           F" {e.args[0]}") | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             else: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                 log.error(F"Failed to load custom config layer from {custom_config_path}.", | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                           exc_info=True) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             log.warning("Falling back to default config.") | 
			
		
	
		
		
			
				
					
					|  |  |  |             confman.fallback() |  |  |  |             confman.fallback() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     else: |  |  |  |     else: | 
			
		
	
		
		
			
				
					
					|  |  |  |         logging.info("Custom config layer disabled") |  |  |  |         log.info("Custom config layer disabled") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     # Freeze Shepherd Control related config. |  |  |  |     # Freeze Shepherd Control related config. | 
			
		
	
		
		
			
				
					
					|  |  |  |     core_conf = confman.get_config_bundle("shepherd") |  |  |  |     core_conf = confman.get_config_bundle("shepherd") | 
			
		
	
	
		
		
			
				
					|  |  | @ -151,18 +162,22 @@ def compile_config_and_get_plugins(confman, default_config_path, layers_disabled | 
			
		
	
		
		
			
				
					
					|  |  |  |             control_config = control.get_config(core_conf["root_dir"]) |  |  |  |             control_config = control.get_config(core_conf["root_dir"]) | 
			
		
	
		
		
			
				
					
					|  |  |  |             try: |  |  |  |             try: | 
			
		
	
		
		
			
				
					
					|  |  |  |                 plugin_classes = load_config_layer_and_plugins(confman, control_config) |  |  |  |                 plugin_classes = load_config_layer_and_plugins(confman, control_config) | 
			
		
	
		
		
			
				
					
					|  |  |  |                 logging.info(F"Loaded cached Shepherd Control config layer") |  |  |  |                 log.info(F"Loaded cached Shepherd Control config layer") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |             except Exception as e: |  |  |  |             except Exception as e: | 
			
		
	
		
		
			
				
					
					|  |  |  |                 logging.error( |  |  |  |                 if isinstance(e, InvalidConfigError): | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                     F"Failed to load cached Shepherd Control config layer. Falling back" |  |  |  |                     log.error(F"Failed to load cached Shepherd Control config layer." | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                     " to local config.", exc_info=e) |  |  |  |                               F" {e.args[0]}") | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                 else: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     log.error(F"Failed to load cached Shepherd Control config layer.", | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                               exc_info=True) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                 log.warning("Falling back to local config.") | 
			
		
	
		
		
			
				
					
					|  |  |  |                 confman.fallback() |  |  |  |                 confman.fallback() | 
			
		
	
		
		
			
				
					
					|  |  |  |         except Exception: |  |  |  |         except Exception: | 
			
		
	
		
		
			
				
					
					|  |  |  |             logging.warning("No cached Shepherd Control config layer available.") |  |  |  |             log.warning("No cached Shepherd Control config layer available.") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     else: |  |  |  |     else: | 
			
		
	
		
		
			
				
					
					|  |  |  |         logging.info("Shepherd Control config layer disabled") |  |  |  |         log.info("Shepherd Control config layer disabled") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     logging.debug("Compiled config: %s", confman.root_config) |  |  |  |     log.debug("Compiled config: %s", confman.root_config) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     confman.dump_to_file(core_conf["generated_config_path"]) |  |  |  |     confman.dump_to_file(core_conf["generated_config_path"]) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     return plugin_classes |  |  |  |     return plugin_classes | 
			
		
	
	
		
		
			
				
					|  |  | 
 |