Exemple #1
0
    def __init__(self, vars=None, secret_keys=None):

        super(VarsInventory, self).__init__()

        if vars is None:
            vars = []

        if not isinstance(vars, Sequence):
            vars = [vars]

        self._init_vars_list = vars
        if secret_keys is None:
            secret_keys = []
        self._secrets = secret_keys

        self._vars = {}

        for v in self._init_vars_list:
            dict_merge(self._vars, v, copy_dct=False)
Exemple #2
0
    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))
Exemple #3
0
def set_run_defaults(inventory=None, run_config=None, run_vars=None):

    if inventory is None:
        inventory = VarsInventory()

    if isinstance(inventory, Mapping):
        inventory = VarsInventory(vars=inventory)

    if run_config is None:
        run_config = FrecklesRunConfig()

    if isinstance(run_config, string_types):
        run_config = FrecklesRunConfig(target_string=run_config)

    if isinstance(run_config, FrecklesRunConfig):
        run_config = run_config.config

    default_run_config = {"host": "localhost"}
    run_config = dict_merge(default_run_config, run_config, copy_dct=False)

    if run_vars is None:
        run_vars = {}

    return inventory, run_config, run_vars
Exemple #4
0
    def _init_config(self):

        if self._target_string is not None:
            self._target_dict_base = parse_target_string(self._target_string)
        else:
            self._target_dict_base = {}

        self._config = dict_merge(self._target_dict_base,
                                  self._target_dict,
                                  copy_dct=False)

        if "host" not in self._config.keys():
            self._config["host"] = "localhost"

        self._protocol = self._config.get("protocol", None)
        self._user = self._config.get("user", None)
        self._port = self._config.get("port", None)
        self._host = self._config.get("host", None)
        self._connection_type = self._config.get("connection_type", None)
        self._ssh_key = self._config.get("ssh_key", None)
        self._become_pass = self._config.get("become_pass", None)
        self._login_pass = self._config.get("login_pass", None)

        if self._connection_type == "lxd":
            if self._user is None:
                self._user = "******"

            if self._user != "root":
                if not self._warning_showed:
                    log.warning(
                        "lxd connection type does not support different user than 'root', ignoring specified user: {}"
                        .format(self._user))
                    self._warning_showed = True
                self._user = "******"

        self._invalidated = False
Exemple #5
0
    def run_frecklecutable(
        self,
        inventory=None,
        run_config=None,
        run_vars=None,
        parent_task=None,
        result_callback=None,
        elevated=None,
        env_dir=None,
        password_callback=None,
        extra_callbacks=None,
    ):

        if password_callback is None:
            password_callback = ask_password

        inventory, run_config, run_vars = set_run_defaults(
            inventory=inventory, run_config=run_config, run_vars=run_vars)

        run_inventory, secret_args = self.create_run_inventory(
            inventory=inventory, run_config=run_config, run_vars=run_vars)

        if parent_task is None:
            i_am_root = True
            result_callback = FrecklesResultCallback()
        else:
            i_am_root = False
            if result_callback is None:
                raise Exception("No result callback. This is a bug")

        # paused = False
        # if parent_task is not None and (
        #     secret_args
        #     or run_config.get("become_pass", None) == "::ask::"
        #     or run_config.get("login_pass", None) == "::ask::"
        #     ):
        #     paused = True
        asked = False

        run_secrets = {}

        # if parent_task is not None:
        #     parent_task.pause()

        run_secrets["become_pass"] = run_config.pop("become_pass", None)
        if (not run_config.get("no_run", False)
                and run_secrets["become_pass"] == "::ask::"):
            msg = ""
            if run_config.get("user", None):
                msg = "{}@".format(run_config["user"])
            msg = msg + run_config.get("host", "localhost")

            prompt = "SUDO PASS (for '{}')".format(msg)

            run_secrets["become_pass"] = password_callback(prompt)
            asked = True

        run_secrets["login_pass"] = run_config.pop("login_pass", None)
        if (not run_config.get("no_run", False)
                and run_secrets["login_pass"] == "::ask::"):
            msg = ""
            if run_config.get("user", None):
                msg = "{}@".format(run_config["user"])
            msg = msg + run_config.get("host", "localhost")

            prompt = "LOGIN/SSH PASS (for '{}')".format(msg)

            run_secrets["login_pass"] = password_callback(prompt)
            asked = True

        # if paused:
        #     parent_task.resume()

        if asked:
            click.echo()

        frecklet_name = self.frecklet.id
        log.debug("Running frecklecutable: {}".format(frecklet_name))

        tasks = self.process_tasks(inventory=run_inventory)

        current_tasklist = []
        idempotent_cache = []
        current_adapter = None

        # all_resources = {}
        tasks_elevated = False

        task_lists = []

        for task in tasks:

            elv = (task[FRECKLET_KEY_NAME].get(
                FRECKLES_PROPERTIES_METADATA_KEY,
                {}).get(FRECKLES_PROPERTIES_ELEVATED_METADATA_KEY, False))

            # just converting from string to boolean
            if isinstance(elv, string_types):
                if elv.lower() in ["true", "1", "yes"]:
                    elv = True

                task[FRECKLET_KEY_NAME][FRECKLES_PROPERTIES_METADATA_KEY][
                    FRECKLES_PROPERTIES_ELEVATED_METADATA_KEY] = True

            if elv:
                tasks_elevated = True

            tt = task[FRECKLET_KEY_NAME]["type"]

            adapter_name = self.context._adapter_tasktype_map.get(tt, None)

            if adapter_name is None:
                raise Exception(
                    "No adapter registered for task type: {}".format(tt))
            if len(adapter_name) > 1:
                raise Exception(
                    "Multiple adapters registered for task type '{}', that is not supported yet."
                    .format(tt))

            adapter_name = adapter_name[0]

            if current_adapter is None:
                current_adapter = adapter_name

            if current_adapter != adapter_name:

                if elevated is not None:
                    tasks_elevated = elevated

                new_tasklist = {
                    "tasklist": current_tasklist,
                    "adapter": current_adapter,
                    "elevated": tasks_elevated,
                }
                if tasks_elevated:
                    self.check_become_pass(
                        run_config,
                        run_secrets,
                        parent_task,
                        password_callback=password_callback,
                    )
                task_lists.append(new_tasklist)
                current_adapter = adapter_name
                idempotent_cache = []
                current_tasklist = []
                tasks_elevated = False

            if is_duplicate_task(task, idempotent_cache):
                log.debug("Idempotent, duplicate task, ignoring: {}".format(
                    task[FRECKLET_KEY_NAME]["name"]))
                continue

            current_tasklist.append(task)

        if elevated is not None:
            tasks_elevated = elevated
        new_tasklist = {
            "tasklist": current_tasklist,
            "adapter": current_adapter,
            "elevated": tasks_elevated,
        }
        if tasks_elevated:
            self.check_become_pass(
                run_config,
                run_secrets,
                parent_task,
                password_callback=password_callback,
            )
        task_lists.append(new_tasklist)

        current_run_result = None
        root_task = None
        run_env_properties = None

        try:
            for run_nr, tl_details in enumerate(task_lists):

                current_adapter = tl_details["adapter"]
                current_tasklist = tl_details["tasklist"]
                run_elevated = tl_details["elevated"]

                if not current_tasklist:
                    continue

                # augmenting result properties
                for t in current_tasklist:
                    if "register" in t.get(FRECKLET_KEY_NAME, {}).keys():
                        result_callback.register_task(t[FRECKLET_KEY_NAME])

                adapter = self.context._adapters[current_adapter]
                run_env_properties = self.context.create_run_environment(
                    adapter, env_dir=env_dir)
                run_env_properties["frecklet_name"] = self.frecklet.id
                run_env_properties["run_metadata"] = run_config.get(
                    "metadata", {})

                # preparing execution environment...
                self._context._run_info.get("prepared_execution_environments",
                                            {}).get(current_adapter, None)

                if extra_callbacks:
                    if not isinstance(extra_callbacks, Sequence):
                        extra_callbacks = [extra_callbacks]
                    cbs = extra_callbacks + self._callbacks
                else:
                    cbs = self._callbacks

                if self.context.config_value("write_run_log"):
                    c_config = {
                        "path":
                        os.path.join(run_env_properties["env_dir"],
                                     "run_log.json")
                    }
                    log_file_callback = load_callback("logfile",
                                                      callback_config=c_config)

                    cbs = cbs + [log_file_callback]

                if parent_task is None:
                    root_task = Tasks(
                        "env_prepare_adapter_{}".format(adapter_name),
                        msg="starting run",
                        category="run",
                        callbacks=cbs,
                        is_utility_task=False,
                    )
                    parent_task = root_task.start()

                prepare_root_task = parent_task.add_subtask(
                    task_name="env_prepare_adapter_{}".format(adapter_name),
                    msg="preparing adapter: {}".format(adapter_name),
                )

                try:

                    adapter.prepare_execution_requirements(
                        run_config=run_config,
                        task_list=current_tasklist,
                        parent_task=prepare_root_task,
                    )
                    prepare_root_task.finish(success=True)

                except (Exception) as e:
                    prepare_root_task.finish(success=False, error_msg=str(e))
                    raise e

                host = run_config["host"]

                if adapter_name == "freckles":
                    msg = "running frecklecutable: {}".format(frecklet_name)
                else:
                    msg = "running frecklet: {} (on: {})".format(
                        frecklet_name, host)
                root_run_task = parent_task.add_subtask(
                    task_name=frecklet_name, msg=msg)

                run_config["elevated"] = run_elevated

                run_vars = dict_merge(result_callback.result,
                                      run_vars,
                                      copy_dct=True)

                if not i_am_root:
                    r_tks = get_template_keys(
                        run_config, jinja_env=DEFAULT_RUN_CONFIG_JINJA_ENV)
                    if r_tks:
                        for k in r_tks:
                            if k not in result_callback.result.keys():
                                raise Exception(
                                    "Could not find result key for subsequent run: {}"
                                    .format(k))

                        run_config = replace_strings_in_obj(
                            run_config,
                            replacement_dict=result_callback.result,
                            jinja_env=DEFAULT_RUN_CONFIG_JINJA_ENV,
                        )

                run_properties = None
                try:
                    write_runs_log(
                        properties=run_env_properties,
                        adapter_name=adapter_name,
                        state="started",
                    )
                    # from random import randint
                    # from time import sleep
                    # sec = randint(1, 10)
                    # print("SLEEPING: {}".format(sec))
                    # sleep(sec)

                    run_properties = adapter._run(
                        tasklist=current_tasklist,
                        run_vars=run_vars,
                        run_config=run_config,
                        run_secrets=run_secrets,
                        run_env=run_env_properties,
                        result_callback=result_callback,
                        parent_task=root_run_task,
                    )

                    if not root_run_task.finished:
                        root_run_task.finish()

                    run_result = FrecklesRunRecord(
                        run_id=run_nr,
                        adapter_name=adapter_name,
                        task_list=current_tasklist,
                        run_vars=run_vars,
                        run_config=run_config,
                        run_env=run_env_properties,
                        run_properties=run_properties,
                        result=copy.deepcopy(result_callback.result),
                        success=root_run_task.success,
                        root_task=root_run_task,
                        parent_result=current_run_result,
                    )
                    current_run_result = run_result
                    write_runs_log(
                        properties=run_env_properties,
                        adapter_name=adapter_name,
                        state="success",
                    )

                    if not root_run_task.success:
                        break

                except (Exception) as e:

                    # import traceback
                    # traceback.print_exc()
                    write_runs_log(
                        properties=run_env_properties,
                        adapter_name=adapter_name,
                        state="failed",
                    )

                    if isinstance(e, FrklException):
                        msg = e.message
                    else:
                        msg = str(e)
                    if not root_run_task.finished:
                        root_run_task.finish(success=False, error_msg=msg)
                    # click.echo("frecklecutable run failed: {}".format(e))
                    log.debug(e, exc_info=1)

                    run_result = FrecklesRunRecord(
                        run_id=run_nr,
                        adapter_name=adapter_name,
                        task_list=current_tasklist,
                        run_vars=run_vars,
                        run_config=run_config,
                        run_env=run_env_properties,
                        run_properties=run_properties,
                        result=copy.deepcopy(result_callback.result),
                        success=root_run_task.success,
                        root_task=root_run_task,
                        parent_result=current_run_result,
                        exception=e,
                    )
                    current_run_result = run_result

                    break
        except (Exception) as e:
            log.error(e)
        finally:
            if root_task is None:
                return current_run_result

            if i_am_root:
                root_task.finish()

            keep_run_folder = self.context.config_value(
                "keep_run_folder", "context")

            if i_am_root and not keep_run_folder:

                env_dir = run_env_properties["env_dir"]
                env_dir_link = run_env_properties.get("env_dir_link", None)

                if env_dir_link and os.path.realpath(env_dir_link) == env_dir:
                    try:
                        log.debug("removing env dir symlink: {}".format(
                            env_dir_link))
                        os.unlink(env_dir_link)
                    except (Exception):
                        log.debug("Could not remove symlink.")

                try:
                    log.debug("removing env dir: {}".format(env_dir))
                    shutil.rmtree(env_dir)
                except (Exception) as e:
                    log.warning(
                        "Could not remove environment folder '{}': {}".format(
                            env_dir, e))

        if current_run_result:
            result_dict = current_run_result.result
            success = current_run_result.success
        else:
            success = False

        if success:
            if not result_dict:
                result_dict = {}
            else:
                result_dict = special_dict_to_dict(result_dict)
            for cb in self.context.result_callback:
                if cb[0] == "pretty":
                    click.echo()
                    if result_dict:
                        result_yaml = readable(result_dict,
                                               out="yaml",
                                               indent=4)
                        msg = (Style.BRIGHT + "Result:" + Style.RESET_ALL +
                               "\n\n" + Style.DIM + result_yaml +
                               Style.RESET_ALL)
                        output_to_terminal(msg)
                        click.echo()
                elif cb[0] == "json":
                    result_json = json.dumps({
                        "output_type": "freckles_run_result",
                        "value": result_dict
                    })
                    output_to_terminal(result_json, nl=True)
                elif str(cb[0].lower()) in ["false", "no", "silent"]:
                    continue
                else:
                    log.warning("Result callback '{}' not supported.".format(
                        cb[0]))

        return current_run_result
Exemple #6
0
def describe_frecklet(context, frecklet, vars, auto_vars=False):

    fx = frecklet.create_frecklecutable(context=context)

    var_all = {}

    if isinstance(vars, Mapping):
        vars = [vars]
    for v in vars:
        dict_merge(var_all, v, copy_dct=False)

    auto_vars_dict = None
    if auto_vars:
        params = fx.frecklet.vars
        auto_vars_dict = create_auto_vars(params,
                                          existing_vars=var_all,
                                          frecklet=fx.frecklet)
        var_all = dict_merge(auto_vars_dict, var_all, copy_dct=True)

    inv = VarsInventory(var_all)
    run_inventory, secret_vars = fx.create_run_inventory(inventory=inv)
    tasks = fx.process_tasks(inventory=run_inventory)

    result = []
    for task in tasks:

        # output(task, output_type="yaml")
        f_type = task[FRECKLET_KEY_NAME]["type"]
        name = task[FRECKLET_KEY_NAME]["name"]

        f_doc = Doc(
            task[FRECKLET_KEY_NAME].get(FRECKLES_DESC_METADATA_KEY, {}),
            short_help_key=FRECKLES_DESC_SHORT_METADATA_KEY,
            help_key=FRECKLES_DESC_LONG_METADATA_KEY,
            further_reading_key=FRECKLES_DESC_REFERENCES_METADATA_KEY,
        )

        title = f_doc.get_short_help(default=None,
                                     use_help=False,
                                     list_item_format=True)
        if title:
            if title.startswith("["):
                title = title[1:]
            if title.endswith("]"):
                title = title[0:-1]
        else:
            title = None

        if f_type == "frecket":
            alt_title = context.get_frecklet(name).doc.get_short_help(
                list_item_format=True)

            alt_desc = context.get_frecklet(name).doc.get_short_help(
                list_item_format=False)
            if alt_desc:
                alt_desc = alt_desc.strip()
        else:
            alt_title = "execute {}: {}".format(f_type, name)
            alt_desc = "Run {} '{}'".format(f_type, name)

        desc = f_doc.get_help(default=None, use_short_help=False)
        if desc:
            desc = desc.strip()

        if not task.get(VARS_KEY):
            alt_desc = alt_desc + " (no variables used)."
        else:
            vars_string = readable(task[VARS_KEY], out="yaml")
            alt_desc = alt_desc + " using variables:\n\n" + reindent(
                vars_string, 2)
            alt_desc = alt_desc.strip()
            if task.get(TASK_KEY_NAME, None):
                task_string = readable(task[TASK_KEY_NAME], out="yaml")
                alt_desc = (alt_desc + "\n\nand task metadata:\n\n" +
                            reindent(task_string, 2))
                alt_desc = alt_desc.strip()

        task_md = {}
        task_md["title"] = title
        task_md["alt_title"] = alt_title
        task_md["desc"] = desc
        task_md["alt_desc"] = alt_desc
        task_md["references"] = f_doc.get_further_reading()

        result.append(task_md)

    return result, auto_vars_dict
Exemple #7
0
    def create_parameter(self, var_name, var):

        if not var.cli.get("enabled", True):
            return None

        option_properties = dict_merge(
            CliArgumentsAttribute.DEFAULT_CLI_SCHEMA, var.cli, copy_dct=True)

        param_type = option_properties.pop("param_type")

        if not var.required:
            option_properties["required"] = False
        else:
            if var.schema.get("excludes", []):
                option_properties["required"] = False
            else:
                option_properties["required"] = True

        auto_param_decls = False
        if "param_decls" not in option_properties.keys():
            auto_param_decls = True
            if param_type == "option":
                decls = []
                for a in var.aliases:
                    if len(a) == 1:
                        decls.append("-{}".format(a))
                    else:
                        a = a.replace("_", "-")
                        decls.append("--{}".format(a))
                option_properties["param_decls"] = decls
            else:
                option_properties["param_decls"] = [var.key]

        if "metavar" not in option_properties.keys():
            option_properties["metavar"] = var.key.upper()

        # setting type
        cerberus_type = var.type

        if not isinstance(cerberus_type, string_types) and isinstance(
                cerberus_type, Sequence):
            replacement = None
            cerberus_type = "multi"
        else:
            replacement = FRECKLES_CLICK_CEREBUS_ARG_MAP.get(
                cerberus_type, None)
        if replacement is not None:
            if replacement == bool:
                option_properties["type"] = None
                option_properties["default"] = None
                if "is_flag" not in option_properties.keys():
                    option_properties["is_flag"] = True
                    # we don't add the type here, otherwise click fails for whatever reason

                if "param_decls" not in option_properties.keys(
                ) or auto_param_decls:

                    temp = var.key.replace("_", "-")
                    option_properties["param_decls"] = [
                        "--{}/--no-{}".format(temp, temp)
                    ]
            else:
                option_properties["type"] = replacement

        elif cerberus_type == "list":
            arg_schema = var.schema.get("schema", {})
            schema_type = arg_schema.get("type", "string")
            replacement = FRECKLES_CLICK_CEREBUS_ARG_MAP.get(
                schema_type, click.STRING)
            option_properties["type"] = replacement
            if param_type == "option":
                option_properties["multiple"] = True
            else:
                if ("nargs" not in option_properties.keys()
                        and "default" not in option_properties.keys()):
                    option_properties["nargs"] = -1
        elif cerberus_type != "multi":
            raise Exception(
                "Type '{}' not implemented yet.".format(cerberus_type))

        # if var.secret:
        #     if "default" not in option_properties.keys():
        #         if option_properties["required"]:
        #             option_properties["default"] = "::ask::"
        #             option_properties["show_default"] = True

        if option_properties.get("default", None) is not None:
            default_val = option_properties["default"]

            if is_var_adapter(default_val):
                option_properties["type"] = str

        if var.secret:
            option_properties["show_default"] = False

        if param_type == "option":
            option_properties["help"] = var.doc.get_short_help()
            p = click.Option(**option_properties)
        else:
            option_properties.pop("show_default", None)
            if ("nargs" in option_properties.keys()
                    and "default" in option_properties.keys()):
                log.warning(
                    "Removing 'nargs' property from argument '{}' ('nargs' & 'default' are not allowed together)"
                    .format(var_name))
                option_properties.pop("nargs")
            p = click.Argument(**option_properties)

        return p
Exemple #8
0
    def resolve_vars(self, current_args, rest_path, last_node, tree, ting,
                     is_root_level):

        current_node_id = next(rest_path)

        current_node = tree.get_node(current_node_id)

        if current_node_id == 0:
            vars = {}

            for key in current_args.keys():
                vars[key] = "{{{{:: {} ::}}}}".format(key)

        else:
            vars = current_node.data["task"]["vars"]

        if current_node_id == 0:
            available_args = current_node.data.args
        else:
            available_args = current_node.data["task"]["args"]

        args = {}

        for key, arg in current_args.items():

            # if key in ting.const.keys():
            #     parent_var = ting.const[key]
            # print("---")
            # print(parent_var)
            if key in vars.keys():
                parent_var = vars[key]

            elif key not in vars.keys():

                inherit = ting.meta.get("vars", {}).get("inherit", False)
                if inherit:
                    vars[key] = "{{{{:: {} ::}}}}".format(key)
                    parent_var = vars[key]
                else:

                    if arg.required and arg.default:

                        # relevant frecklet root name: tree.get_node(0).tag,
                        raise FreckletBuildException(
                            frecklet=ting,
                            msg=
                            "Invalid frecklet-task '{}' in '{}': does not provide required/non-defaulted var '{}' for child frecklet '{}'."
                            .format(
                                current_node.tag,
                                tree.get_node(0).tag,
                                key,
                                last_node.tag,
                            ),
                            solution="Check format of frecklet '{}'.".format(
                                current_node.tag),
                            references={
                                "frecklet documentation":
                                "https://freckles.io/doc/frecklets/anatomy"
                            },
                        )
                    else:
                        continue

            new_args = self.create_new_args_map(parent_var=parent_var,
                                                key=key,
                                                available_args=available_args,
                                                arg=arg)

            for k, v in new_args.items():
                # if k in args.keys():
                #     log.warning("Duplicate arg key: {}".format(k))
                args[k] = v

        if current_node_id != 0:

            r = self.resolve_vars(
                current_args=args,
                rest_path=rest_path,
                last_node=current_node,
                tree=tree,
                ting=ting,
                is_root_level=False,
            )

            return r
        else:

            # new_args = {}
            # replaced = []
            # for key, arg in args.items():
            #     if key in ting.const.keys():
            #         replaced.append(key)
            #         parent_var = ting.const[key]
            #         temp_args = self.create_new_args_map(parent_var=parent_var, key=key, available_args=available_args, arg=args[key])
            #         for k, v in temp_args.items():
            #             new_args[k] = v
            #     else:
            #         new_args[key] = arg
            #
            # args = new_args

            root_task = last_node.data["task"]
            try:
                tks = get_template_keys(root_task,
                                        jinja_env=DEFAULT_FRECKLES_JINJA_ENV)
            except (TemplateSyntaxError) as e:
                raise FreckletException(ting, e, ting.id)

            # for r in replaced:
            #     tks.remove(r)

            root_tks = {}
            for tk in tks:
                if tk in args.keys():
                    continue

                # add a template key that is under either 'frecklet' or 'task' of the root task of a frecklet
                arg = available_args.get(tk, None)

                auto_arg = False
                if arg is None:
                    if self.default_argument_description is None:
                        f_name = current_node.data.id
                        raise Exception(
                            "No argument description for argument '{}' in frecklet '{}'"
                            .format(tk, f_name))
                    else:
                        arg = copy.copy(self.default_argument_description)
                        auto_arg = True

                arg = Arg(
                    tk,
                    arg,
                    default_schema=FRECKLES_DEFAULT_ARG_SCHEMA,
                    is_auto_arg=auto_arg,
                )
                root_tks[tk] = arg

            dict_merge(args, root_tks, copy_dct=False)

            return args