# -*- coding: utf-8 -*-
import copy
import json
import logging
import os
import shutil
from collections import OrderedDict, Mapping, Sequence
import click
from colorama import Style
from ruamel.yaml.comments import CommentedMap, CommentedSeq
from six import string_types
from treelib import Tree
from freckles.utils.runs import write_runs_log
from frutils.frutils_cli import output_to_terminal
from frutils.tasks.callback import load_callback
from .frecklet.vars import (
VarsInventory,
is_var_adapter,
get_resolved_var_adapter_object,
)
from .context.run_config import FrecklesRunConfig
from frutils import (
replace_strings_in_obj,
get_template_keys,
can_passwordless_sudo,
dict_merge,
special_dict_to_dict,
readable,
)
from frutils.exceptions import FrklException
from frutils.tasks.tasks import Tasks
from ting.defaults import TingValidator
from .defaults import (
FRECKLET_KEY_NAME,
VARS_KEY,
TASK_KEY_NAME,
DEFAULT_FRECKLES_JINJA_ENV,
FRECKLES_DESC_METADATA_KEY,
FRECKLES_PROPERTIES_METADATA_KEY,
FRECKLES_PROPERTIES_IDEMPOTENT_METADATA_KEY,
FRECKLES_PROPERTIES_ELEVATED_METADATA_KEY,
DEFAULT_RUN_CONFIG_JINJA_ENV,
)
from .exceptions import FrecklesVarException
from .output_callback import FrecklesRunRecord, FrecklesResultCallback
log = logging.getLogger("freckles")
[docs]def ask_password(prompt):
pw = click.prompt(prompt, type=str, hide_input=True)
return pw
[docs]class FrecklecutableMixin(object):
def __init__(self, *args, **kwargs):
pass
[docs] def create_frecklecutable(self, context):
return Frecklecutable(frecklet=self, context=context)
[docs]def is_duplicate_task(new_task, idempotency_cache):
if (
not new_task[FRECKLET_KEY_NAME]
.get(FRECKLES_PROPERTIES_METADATA_KEY, {})
.get(FRECKLES_PROPERTIES_IDEMPOTENT_METADATA_KEY, False)
):
return False
temp = {}
temp[FRECKLET_KEY_NAME] = copy.copy(new_task[FRECKLET_KEY_NAME])
temp[FRECKLET_KEY_NAME].pop(FRECKLES_DESC_METADATA_KEY, None)
temp[FRECKLET_KEY_NAME].pop(FRECKLES_PROPERTIES_METADATA_KEY, None)
temp[FRECKLET_KEY_NAME].pop("skip", None)
temp[TASK_KEY_NAME] = copy.copy(new_task[TASK_KEY_NAME])
temp[VARS_KEY] = copy.copy(new_task[VARS_KEY])
if temp in idempotency_cache:
return True
else:
idempotency_cache.append(temp)
return False
[docs]def remove_none_values(input):
if isinstance(input, (list, tuple, set, CommentedSeq)):
result = []
for item in input:
temp = remove_none_values(item)
if temp is not None and temp != "":
result.append(temp)
return result
elif isinstance(input, (dict, OrderedDict, CommentedMap)):
result = CommentedMap()
for k, v in input.items():
if v is not None:
temp = remove_none_values(v)
if temp is not None and temp != "":
result[k] = temp
return result
else:
return input
[docs]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
[docs]class Frecklecutable(object):
def __init__(self, frecklet, context):
self._frecklet = frecklet
self._context = context
self._callbacks = context.callbacks
@property
def frecklet(self):
return self._frecklet
@property
def context(self):
return self._context
def _retrieve_var_value_from_inventory(
self, inventory, var_value, template_keys=None
):
"""Retrieves all template keys contained in a value from the inventory.
Args:
var_value: the value of a var
Returns:
dict: a dict with keyname/inventory_value pairs
"""
if template_keys is None:
template_keys = get_template_keys(
var_value, jinja_env=DEFAULT_FRECKLES_JINJA_ENV
)
if not template_keys:
return {}
result = {}
for tk in template_keys:
val = inventory.retrieve_value(tk)
result[tk] = val
return result
def _replace_templated_var_value(self, var_value, repl_dict=None, inventory=None):
"""Replace a templated (or not) var value using a replacement dict or the inventory.
Args:
var_value: the value of a var
repl_dict: the key/value pairs to use for the templating
Returns:
The processed object.
"""
if repl_dict is None:
repl_dict = self._retrieve_var_value_from_inventory(
inventory=inventory, var_value=var_value
)
processed = replace_strings_in_obj(
var_value, replacement_dict=repl_dict, jinja_env=DEFAULT_FRECKLES_JINJA_ENV
)
return processed
def _generate_schema(self, var_value_map, args, const_args, template_keys=None):
if template_keys is None:
template_keys = get_template_keys(
var_value_map, jinja_env=DEFAULT_FRECKLES_JINJA_ENV
)
schema = {}
secret_keys = set()
for key in template_keys:
arg_obj = const_args.get(key, None)
if arg_obj is None:
arg_obj = args[key]
schema[key] = copy.copy(arg_obj.schema)
# schema[key].pop("doc", None)
# schema[key].pop("cli", None)
secret = arg_obj.secret
if secret is True:
secret_keys.add(key)
return schema, secret_keys
def _validate_processed_vars(
self,
var_value_map,
schema,
allow_unknown=False,
purge_unknown=True,
task_path=None,
vars_pre_clean=None,
task=None,
):
_schema = copy.deepcopy(schema)
_var_value_map = copy.deepcopy(var_value_map)
_schema = special_dict_to_dict(schema)
_var_value_map = special_dict_to_dict(_var_value_map)
validator = TingValidator(
_schema, purge_unknown=purge_unknown, allow_unknown=allow_unknown
)
valid = validator.validated(_var_value_map)
if valid is None:
if vars_pre_clean is None:
vars_pre_clean = var_value_map
raise FrecklesVarException(
frecklet=self.frecklet,
errors=validator.errors,
task_path=task_path,
vars=vars_pre_clean,
task=task,
)
return valid
[docs] def process_tasks(self, inventory):
"""Calculates the tasklist for a given inventory."""
processed_tree = self._calculate_task_plan(inventory=inventory)
task_nodes = processed_tree.leaves()
result = []
task_id = 0
for t in task_nodes:
if t.data["processed"][FRECKLET_KEY_NAME].get("skip", False):
continue
task = t.data["processed"]
task[FRECKLET_KEY_NAME]["_task_id"] = task_id
task_id = task_id + 1
result.append(task)
return result
def _calculate_task_plan(self, inventory):
task_tree = self.frecklet.task_tree
processed_tree = Tree()
root_frecklet = task_tree.get_node(0)
task_path = []
const_cache = {}
for tn in task_tree.all_nodes():
task_id = tn.identifier
if task_id == 0:
processed_tree.create_node(
identifier=0,
tag=task_tree.get_node(0).tag,
data={"frecklet": root_frecklet.data, "inventory": inventory},
)
continue
task_node = tn.data["task"]
root_vars = task_tree.get_node(task_id).data["root_frecklet"].vars_frecklet
const_vars = task_tree.get_node(task_id).data["root_frecklet"].vars_const
const = task_tree.get_node(task_id).data["root_frecklet"].const
parent_id = task_tree.parent(task_id).identifier
task_level = task_tree.level(task_id)
root_frecklet_id = task_tree.get_node(task_id).data["root_frecklet"].id
if parent_id == 0:
parent = {}
# template_keys = task_tree.get_node(0).data.template_keys
repl_vars = {}
# import pp
# print("INV")
# pp(inventory.get_vars())
for k, v in inventory.get_vars(hide_secrets=False).items():
repl_vars[k] = v
# for tk in template_keys:
# v = inventory.retrieve_value(tk)
# if v is not None:
# repl_vars[tk] = v
task_path = []
parent_secret_keys = set()
parent_desc = {}
else:
parent = processed_tree.get_node(parent_id).data
repl_vars = parent["processed"].get("vars", {})
parent_secret_keys = parent["processed"][FRECKLET_KEY_NAME].get(
"secret_vars", set()
)
parent_desc = parent["processed"][FRECKLET_KEY_NAME].get(
FRECKLES_DESC_METADATA_KEY, {}
)
if (
parent.get("processed", {})
.get(FRECKLET_KEY_NAME, {})
.get("skip", False)
):
processed_tree.create_node(
identifier=task_id,
tag=task_tree.get_node(task_id).tag,
data={
"frecklet": root_frecklet.data,
"inventory": inventory,
"processed_vars": {},
"processed": {FRECKLET_KEY_NAME: {"skip": True}},
},
parent=parent_id,
)
continue
if parent_id > 0:
if is_var_adapter(const):
if (
const_cache.get(task_level, {}).get(root_frecklet_id, None)
is not None
):
const = const_cache[task_level][root_frecklet_id]
else:
resolved_const = {}
for k, v in const.items():
if is_var_adapter(v):
arg = const_vars.get(k, None)
if arg is None:
arg = root_vars[k]
new_value, is_sec, changed = get_resolved_var_adapter_object(
value=v,
key=k,
arg=arg,
frecklet=self.frecklet,
is_secret=arg.secret,
inventory=None,
)
v = new_value
resolved_const[k] = v
const_cache.setdefault(task_level, {})[
root_frecklet_id
] = resolved_const
const = resolved_const
for k, v in const.items():
tks = get_template_keys(v, jinja_env=DEFAULT_FRECKLES_JINJA_ENV)
if tks:
v = replace_strings_in_obj(
v,
replacement_dict=repl_vars,
jinja_env=DEFAULT_FRECKLES_JINJA_ENV,
)
repl_vars[k] = v
for k, v in repl_vars.items():
if is_var_adapter(v):
arg = const_vars.get(k, None)
if arg is None:
arg = root_vars[k]
new_value, is_sec, changed = get_resolved_var_adapter_object(
value=v,
key=k,
arg=arg,
root_arg=True,
frecklet=self.frecklet,
is_secret=arg.secret,
inventory=inventory,
)
repl_vars[k] = new_value
# output(task_node, output_type="yaml")
vars = copy.copy(task_node.get(VARS_KEY, {}))
frecklet = copy.copy(task_node[FRECKLET_KEY_NAME])
task = copy.copy(task_node.get(TASK_KEY_NAME, {}))
target = frecklet.get("target", None)
# first we get our target variable, as this will most likely determine the value of the var later on
if target is not None:
template_keys = get_template_keys(
target, jinja_env=DEFAULT_FRECKLES_JINJA_ENV
)
if template_keys:
target_value = self._replace_templated_var_value(
var_value=target, repl_dict=repl_vars, inventory=inventory
)
else:
target_value = target
# TODO: 'resolve' target
# TODO: validate target schema
frecklet["target"] = target_value
# then we check if we can skip the task. For that we already need the target variable ready, as it might
# be used for variable selection
skip = frecklet.get("skip", None)
if skip is not None:
# TODO: validate vars against schema
skip_tks = get_template_keys(skip, jinja_env=DEFAULT_FRECKLES_JINJA_ENV)
skip_schema, _ = self._generate_schema(
var_value_map=task,
args=root_vars,
const_args=const_vars,
template_keys=skip_tks,
)
val_map = {}
for tk in skip_tks:
val = repl_vars.get(tk, None)
if val is not None:
val_map[tk] = val
for k, v in inventory.get_vars().items():
if k not in val_map.keys() and v is not None and v != "":
val_map[k] = v
validated_val_map = self._validate_processed_vars(
var_value_map=val_map,
schema=skip_schema,
task_path=task_path,
vars_pre_clean=repl_vars,
task=task_node,
)
skip_value = self._replace_templated_var_value(
var_value=skip, repl_dict=validated_val_map, inventory=inventory
)
frecklet["skip"] = skip_value
if isinstance(skip_value, bool) and skip_value:
processed_tree.create_node(
identifier=task_id,
tag=task_tree.get_node(task_id).tag,
data={
"frecklet": root_frecklet.data,
"inventory": inventory,
"processed_vars": {},
"processed": {FRECKLET_KEY_NAME: {"skip": True}},
},
parent=parent_id,
)
# print("SKIPPPPED")
continue
# now we replace the whole rest of the task
desc = frecklet.get(FRECKLES_DESC_METADATA_KEY, {})
if parent_desc:
spd = parent_desc.get("short", None)
if spd:
desc["short"] = spd
lpd = parent_desc.get("long", None)
if lpd:
desc["long"] = lpd
frecklet[FRECKLES_DESC_METADATA_KEY] = desc
task = {FRECKLET_KEY_NAME: frecklet, TASK_KEY_NAME: task, VARS_KEY: vars}
template_keys = get_template_keys(
task, jinja_env=DEFAULT_FRECKLES_JINJA_ENV
)
schema, secret_keys = self._generate_schema(
var_value_map=task,
args=root_vars,
const_args=const_vars,
template_keys=template_keys,
)
secret_keys.update(parent_secret_keys)
val_map = {}
for tk in template_keys:
val = repl_vars.get(tk, None)
if val is not None:
val_map[tk] = val
for k, v in inventory.get_vars().items():
if k not in val_map.keys() and v is not None and v != "":
val_map[k] = v
validated_val_map = self._validate_processed_vars(
var_value_map=val_map,
schema=schema,
task_path=task_path,
vars_pre_clean=repl_vars,
task=task_node,
)
new_secret_keys = set()
for var_name, var in task.get(VARS_KEY, {}).items():
tk = get_template_keys(var, jinja_env=DEFAULT_FRECKLES_JINJA_ENV)
intersection = secret_keys.intersection(tk)
if intersection:
new_secret_keys.add(var_name)
task_processed = self._replace_templated_var_value(
var_value=task, repl_dict=validated_val_map, inventory=inventory
)
task_processed = remove_none_values(task_processed)
task_processed[FRECKLET_KEY_NAME]["secret_vars"] = list(new_secret_keys)
processed_tree.create_node(
identifier=task_id,
tag=task_tree.get_node(task_id).tag,
data={
"frecklet": root_frecklet.data,
"inventory": inventory,
"processed": task_processed,
},
parent=parent_id,
)
return processed_tree
[docs] def check_become_pass(
self, run_config, run_secrets, parent_task, password_callback
):
if parent_task is not None:
return
if run_config.get("host", None) != "localhost":
return
if run_config.get("no_run", False):
return
if can_passwordless_sudo():
return
if run_secrets.get("become_pass", None) is not None:
return
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)
[docs] def create_run_inventory(self, inventory=None, run_config=None, run_vars=None):
inventory, run_config, run_vars = set_run_defaults(
inventory=inventory, run_config=run_config, run_vars=run_vars
)
run_vars.setdefault("__freckles_run__", {})["pwd"] = os.path.realpath(
os.getcwd()
)
secret_args = []
for arg_name, arg in self.frecklet.vars_frecklet.items():
if arg.secret:
secret_args.append(arg_name)
# 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::"
# ):
# # we need to pause our task callback because of user input
# parent_task.pause()
# paused = True
run_inventory = VarsInventory()
# process constants
const_schema, sk = self._generate_schema(
var_value_map=self.frecklet.const,
args={},
const_args=self.frecklet.vars_const,
)
new_vars = self._validate_processed_vars(
var_value_map=inventory.get_vars(hide_secrets=False), schema=const_schema
)
consts_processed = self._replace_templated_var_value(
var_value=copy.deepcopy(self.frecklet.const),
repl_dict=new_vars,
inventory=inventory,
)
inventory_secrets = inventory.secret_keys()
for k, v in consts_processed.items():
arg = self.frecklet.vars_const.get(k, None)
if arg is None:
arg = self.frecklet.vars_frecklet[k]
secret = k in secret_args or k in inventory_secrets
is_va = is_var_adapter(v)
if not is_va:
run_inventory.set_value(k, v, is_secret=secret)
continue
# otherwise, we load the var adapter and execute its 'retrive' method
var_value, var_is_sec, _ = get_resolved_var_adapter_object(
value=v,
key=k,
arg=arg,
frecklet=self.frecklet,
is_secret=secret,
inventory=inventory,
)
run_inventory.set_value(k, var_value, is_secret=secret)
for key, arg in self.frecklet.vars_frecklet.items():
value = inventory.retrieve_value(key)
if value is None:
continue
secret = key in secret_args or key in inventory_secrets
is_va = is_var_adapter(value)
if not is_va:
run_inventory.set_value(key, value, is_secret=secret)
continue
# otherwise, we load the var adapter and execute its 'retrive' method
var_value, var_is_sec, _ = get_resolved_var_adapter_object(
value=value,
key=key,
arg=arg,
frecklet=self.frecklet,
is_secret=secret,
inventory=inventory,
)
run_inventory.set_value(key, var_value, is_secret=var_is_sec)
return run_inventory, secret_args
[docs] 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