def get_config_type(self, config_obj): if isinstance(config_obj, string_types): config_obj = config_obj.strip() if config_obj.startswith("{") and config_obj.endswith("}"): return "json" elif "=" in config_obj: return "key_value" elif config_obj in self.default_config_dicts.keys(): return "default_config_dict" elif config_obj in self.keys(): return "user_config_dict" else: all_context_names = list( self.default_config_dicts.keys()) + list(self.keys()) raise FrklException( msg="Can't determine config type for string '{}'".format( config_obj), solution= "Value needs to be either json string, key/value pair (separated with '='), or the name of a default or user context config: {}" .format(", ".join(all_context_names)), ) elif isinstance(config_obj, Mapping): return "dict" else: raise FrklException( msg="Invalid config type '{}' for object: {}".format( type(config_obj), config_obj), solution="Needs to be either string, or dict.", )
def get_value_from_var_adapter(var_adapter_name, key, arg, root_arg, frecklet=None, is_secret=None, inventory=None): if not isinstance(var_adapter_name, six.string_types): raise FrklException( msg="Internal error", reason="Not a var adapter: {}".format(var_adapter_name), ) if var_adapter_name.startswith("value_from_"): if inventory is None: raise FrklException( "Can't retrieve value for var_adapter '{}'.".format( var_adapter_name), reason="No inventory provided.", solution= "Don't use the '{}' var adapter in a non parent frecklet.", ) inventory_secrets = inventory.secret_keys() copy_var_name = var_adapter_name[11:] value = inventory.retrieve_value(copy_var_name) secret = is_secret or copy_var_name in inventory_secrets return value, secret else: if var_adapter_name not in VAR_ADAPTERS.keys(): raise FrecklesVarException( frecklet=frecklet, var_name=key, errors={key: "No var adapter '{}'.".format(var_adapter_name)}, solution= "Double-check the var adapter name '{}', maybe there's a typo?\n\nIf the name is correct, make sure the python library that contains the var-adapter is installed in the same environment as freckles." .format(var_adapter_name), ) var_adapter_obj = VAR_ADAPTERS[var_adapter_name] if is_secret is None: is_secret = arg.secret value = var_adapter_obj.retrieve_value( key_name=key, arg=arg, root_arg=root_arg, frecklet=frecklet, is_secret=is_secret, ) return value, is_secret
def parse_target_string(target_string): if target_string == "vagrant" or target_string.startswith("vagrant:"): details = get_vagrant_details(target_string) elif "find-pi" in target_string: details = get_pi_details(target_string) elif "::" in target_string: c_type, target_string = target_string.split("::", 1) if c_type == "lxd": details = get_lxd_details(target_string=target_string) elif c_type == "docker": details = get_docker_details(target_string=target_string) elif c_type == "buildah": details = get_buildah_details(target_string=target_string) else: raise FrklException( msg="Can't parse target string.", reason="Unknown connection type: {}".format(c_type), ) return details else: details = get_host_details(target_string) return details
def load_frecklet(self, frecklet_full_path_or_name_or_content, validate=False): """Loads a frecklet. First, checksi if argument is a path and exists. If that is the case, uses that to create the frecklet. If not, tries to find a frecklet with the provided name. If that doesn't exists either, it tries to interprete the string as frecklet content. """ if isinstance(frecklet_full_path_or_name_or_content, string_types): full_path = os.path.realpath( os.path.expanduser(frecklet_full_path_or_name_or_content)) if os.path.isfile(frecklet_full_path_or_name_or_content): frecklet_name = self.add_dynamic_frecklet( path_or_frecklet_content=full_path, validate=False) frecklet = self.get_frecklet(frecklet_name, validate=validate) return (frecklet, frecklet_name) if is_url_or_abbrev(frecklet_full_path_or_name_or_content): if not self.config_value("allow_remote"): raise FrklException( msg="Loading remote frecklet from not allowed: {}". format(frecklet_full_path_or_name_or_content), reason= "Context config value 'allow_remote' set to 'false'.", solution= "Set config value 'allow_remote' to 'true', e.g. via the '--context allow_remote=true' cli argument.", references={ "freckles remote configuration": "https://freckles.io/doc/security#remote-repo-permission-config" }, ) content = content_from_url( frecklet_full_path_or_name_or_content, update=True, cache_base=os.path.join(FRECKLES_CACHE_BASE, "remote_frecklets"), ) frecklet_name = self.add_dynamic_frecklet( path_or_frecklet_content=content, validate=validate) frecklet = self.get_frecklet(frecklet_name, validate=validate) return (frecklet, frecklet_name) if frecklet_full_path_or_name_or_content in self.get_frecklet_names( ): frecklet = self.get_frecklet( frecklet_full_path_or_name_or_content, validate=validate) return (frecklet, frecklet_full_path_or_name_or_content) frecklet_name = self.add_dynamic_frecklet( frecklet_full_path_or_name_or_content, validate=validate) if frecklet_name: frecklet = self.get_frecklet(frecklet_name, validate=validate) return (frecklet, frecklet_name) return None, None
def __init__(self, watch_path=None, adapter_log=False): if adapter_log and watch_path: raise FrklException( msg="Can only watch either the adapter log, or a specific path." ) self._watch_path = watch_path self._adapter_log = adapter_log self._log_file_printers = {}
def __init__( self, run_alias, watch_path=None, created_callback=None, callback=None, finished_callback=None, adapter_log=None, index=0, ): if adapter_log and watch_path: raise FrklException( msg="Can only watch either the adapter log, or a specific path." ) if watch_path is None: watch_path = "run_log.json" if run_alias.startswith("__dyn_"): run_alias = "_no_name_" self._alias = run_alias self._index = index self._watch_path = watch_path self._created_callback = created_callback self._callback = callback self._finished_callback = finished_callback self._last_file_pos = 0 self._adapter_log = adapter_log if not self._adapter_log: self._log_file = os.path.join(self._env_dir, self._watch_path) else: if self._adapter == "nsbl": self._log_file = os.path.join(self._env_dir, "nsbl/logs/ansible_run_log") else: raise FrklException( msg="Watching logs for adapter '{}' not supported.") self._watch_dir = os.path.dirname(self._log_file)
def add_result(self, result_id, result): try: registered = self._registers.get(result_id, None) if registered is None: raise FrklException( msg= "Result for id '{}' not registered, this is most likely a bug." .format(result_id)) value_template = registered.get("value", None) if value_template is not None: new_value = replace_strings_in_obj( value_template, replacement_dict={"__result__": result}, jinja_env=DEFAULT_RUN_CONFIG_JINJA_ENV, ) else: new_value = result target = registered.get("target", None) if target is None: if not isinstance(result, Mapping): raise FrklException( msg="Can't merge result id '{}'".format(result_id), reason= "Value for result-id '{}' not a mapping, and no 'target' provided.", solution= "Either provide a 'target' value, or use the 'value' template to convert the result.", ) dict_merge(self._result, new_value, copy_dct=False) else: temp = {} dpath.new(temp, target, new_value, separator=".") dict_merge(self._result, temp, copy_dct=False) except (Exception) as e: log.error("Could not register result '{}': {}".format( result_id, e))
def __init__(self, repo_name, tingsets, cnf, default_config_dicts=None, **kwargs): if default_config_dicts is None: default_config_dicts = DEFAULT_CONFIG_DICTS self.default_config_dicts = copy.deepcopy(default_config_dicts) invalid = [] for tings in tingsets: for ting in tings.values(): if ting.id in self.default_config_dicts.keys(): invalid.append(ting.id) if invalid: raise FrklException( msg="Invalid context name(s) for '{}': {}".format( repo_name, ", ".join(invalid)), reason= "Context config files named after reserved context names in repo: {}" .format(", ".join(repo_name)), solution="Rename affected context configs.", ) if cnf is None: raise Exception("Base configuration object can't be None.") if "profile_load" not in cnf.get_interpreter_names(): raise Exception("No 'profile_load' cnf interpreter available.") load_config = cnf.get_interpreter("profile_load") if "root_config" not in cnf.get_interpreter_names(): raise Exception("No root_config profile interpreter in cnf.") self._cnf = cnf self._root_config = cnf.get_interpreter("root_config").config super(ContextConfigs, self).__init__( repo_name=repo_name, tingsets=tingsets, load_config=load_config, indexes=["filename_no_ext"], )
def retrieve_value(self, key_name, arg, root_arg, frecklet, is_secret=False, profile_names=None): if not hasattr(frecklet, "full_path"): raise FrklException( msg="Can't resolve variable value 'frecklet_dir'.", reason="frecklet in question is dynamic.", solution= "Only use the '::frecklet_dir::' var adapter in combination with local frecklets.", ) return os.path.dirname(frecklet.full_path)
def get_frecklet(self, frecklet_name, validate=False, index_name=None): if frecklet_name is None: raise FrklException( "Can't retrieve frecklet, no 'frecklet_name' provided.") result = self.frecklet_index.get_from_index(frecklet_name, index_name=index_name) if validate: valid = result.valid if not valid: raise FreckletException( frecklet=result, parent_exception=result.invalid_exception, frecklet_name=frecklet_name, ) return result
def register_task(self, frecklet_metadata): register = frecklet_metadata.get("register", None) if register is None: return if isinstance(register, string_types): register = {"target": register} if not isinstance(register, Mapping): raise TypeError("Invalid type for 'register' value: {}".format( type(register))) if not register.get("target", None): frecklet_metadata.pop("register", None) return if "value" not in register.keys(): register["value"] = None if "id" not in register.keys(): register_id = str(uuid.uuid4()) register_id = slugify("result_" + register_id, separator="_") register_id.replace("-", "_") register["id"] = register_id if register["id"] in self._registers.keys(): raise FrklException( "Can't register result with 'id': {}".format(register["id"]), reason="Id already registered.", solution="Specify a different id.", ) self._registers[register["id"]] = register frecklet_metadata["register"] = register
def resolve_context_config_dict(self, config_chain, current_config_dict=None): if current_config_dict is None: current_config_dict = {} for config in config_chain: config_type = self.get_config_type(config) if config_type == "dict": config_dict = copy.deepcopy(config) elif config_type == "json": try: config_dict = json.loads(config) except (Exception): raise FrklException( msg="Can't parse json config object: {}".format( config), solution="Check json format.", ) elif config_type == "key_value": key, value = config.split("=", 1) if value == "": raise FrklException( msg="No value provided for key/value config object: {}" .format(config)) if value.lower() in ["true", "yes"]: value = True elif value.lower() in ["false", "no"]: value = False else: try: value = int(value) except (Exception): # fine, we'll just use the string # TODO: support lists pass config_dict = {key: value} elif config_type == "default_config_dict": config_dict = copy.deepcopy(self.default_config_dicts[config]) elif config_type == "user_config_dict": config_dict = copy.deepcopy(self.get(config).config_dict) else: raise FrklException( msg="Invalid config type: {}".format(config_type)) config_parents = config_dict.pop("parents", []) config_extra_repos = config_dict.pop("extra_repos", []) if isinstance(config_extra_repos, string_types) or not isinstance( config_extra_repos, Sequence): config_extra_repos = [config_extra_repos] self.resolve_context_config_dict( config_chain=config_parents, current_config_dict=current_config_dict) dict_merge(current_config_dict, config_dict, copy_dct=False) if config_extra_repos: current_config_dict.setdefault("repos", []).extend(config_extra_repos) return current_config_dict