Π·Π΅ΡΠΊΠ°Π»ΠΎ ΠΈΠ· https://github.com/microsoft/lisa.git
π Rename runbook's 'parent' to be 'include', instead
This is so much more familiar and tells exactly what is happening there. PS. Do we want to make the 'strategy' field work, anytime soon?
This commit is contained in:
Π ΠΎΠ΄ΠΈΡΠ΅Π»Ρ
8ed2f05dc9
ΠΠΎΠΌΠΌΠΈΡ
19d7f0db86
|
@ -13,7 +13,7 @@
|
|||
- [test_pass](#test_pass)
|
||||
- [tags](#tags)
|
||||
- [concurrency](#concurrency)
|
||||
- [parent](#parent)
|
||||
- [include](#include)
|
||||
- [path](#path)
|
||||
- [extension](#extension)
|
||||
- [name](#name-1)
|
||||
|
@ -133,24 +133,25 @@ Below three yaml files will be loaded in this sequence.
|
|||
|
||||
```bash
|
||||
loading runbook sample.yml
|
||||
|-- loading parent tier.yml
|
||||
| |-- loading parent t0.yml
|
||||
|-- loading include tier.yml
|
||||
| |-- loading include t0.yml
|
||||
```
|
||||
|
||||
The variable values in its parent yaml file will be overridden by current yaml
|
||||
file. The relative path is always relative to current yaml file.
|
||||
The variable values in the included yaml file(s) will be overridden by
|
||||
the including yaml file(s). The relative path is always relative to
|
||||
the including yaml file.
|
||||
|
||||
Part of sample.yml
|
||||
|
||||
```yaml
|
||||
parent:
|
||||
include:
|
||||
- path: ./tier.yml
|
||||
```
|
||||
|
||||
Part of tier.yml.
|
||||
|
||||
```yaml
|
||||
parent:
|
||||
include:
|
||||
- path: ./t$(tier).yml
|
||||
variable:
|
||||
- name: tier
|
||||
|
@ -235,11 +236,12 @@ type: int, optional, default is 1.
|
|||
The number of concurrent running environments.
|
||||
|
||||
|
||||
### parent
|
||||
### include
|
||||
|
||||
type: list of path, optional, default is empty
|
||||
|
||||
Share the runbook for similar runs.
|
||||
Share runbook parts for similar runs, including the shared content via
|
||||
that yaml primitive.
|
||||
|
||||
#### path
|
||||
|
||||
|
@ -292,12 +294,12 @@ variable:
|
|||
value: subscription id B
|
||||
```
|
||||
|
||||
The variables values in the runbook have higher priority than the same variables
|
||||
defined in its parent runbook file. `${location}` will be replaced with value
|
||||
`northeurope`.
|
||||
The variable values in the runbook have higher priority than the same
|
||||
variables defined in any included runbook file. Thus, `${location}`
|
||||
will be replaced with value `northeurope` in the following example.
|
||||
|
||||
```yaml
|
||||
parent:
|
||||
include:
|
||||
- path: tier.yml
|
||||
variable:
|
||||
- name: location
|
||||
|
|
|
@ -217,24 +217,22 @@ class RunbookBuilder:
|
|||
def _merge_variables(
|
||||
self,
|
||||
merged_path: Path,
|
||||
data_from_parent: Dict[str, Any],
|
||||
data_from_include: Dict[str, Any],
|
||||
data_from_current: Dict[str, Any],
|
||||
) -> List[Any]:
|
||||
variables_from_parent: List[schema.Variable] = []
|
||||
variables_from_include: List[schema.Variable] = []
|
||||
if (
|
||||
constants.VARIABLE in data_from_parent
|
||||
and data_from_parent[constants.VARIABLE]
|
||||
constants.VARIABLE in data_from_include
|
||||
and data_from_include[constants.VARIABLE]
|
||||
):
|
||||
variables_from_parent = [
|
||||
variables_from_include = [
|
||||
schema.Variable.schema().load(variable) # type: ignore
|
||||
for variable in data_from_parent[constants.VARIABLE]
|
||||
for variable in data_from_include[constants.VARIABLE]
|
||||
]
|
||||
# resolve to absolute path
|
||||
for parent_variable in variables_from_parent:
|
||||
if parent_variable.file:
|
||||
parent_variable.file = str(
|
||||
(merged_path / parent_variable.file).resolve()
|
||||
)
|
||||
for included_var in variables_from_include:
|
||||
if included_var.file:
|
||||
included_var.file = str((merged_path / included_var.file).resolve())
|
||||
if (
|
||||
constants.VARIABLE in data_from_current
|
||||
and data_from_current[constants.VARIABLE]
|
||||
|
@ -246,30 +244,28 @@ class RunbookBuilder:
|
|||
|
||||
# remove duplicate items
|
||||
for current_variable in variables_from_current:
|
||||
for parent_variable in variables_from_parent:
|
||||
for included_var in variables_from_include:
|
||||
if (
|
||||
parent_variable.name
|
||||
and parent_variable.name == current_variable.name
|
||||
included_var.name and included_var.name == current_variable.name
|
||||
) or (
|
||||
parent_variable.file
|
||||
and parent_variable.file == current_variable.file
|
||||
included_var.file and included_var.file == current_variable.file
|
||||
):
|
||||
variables_from_parent.remove(parent_variable)
|
||||
variables_from_include.remove(included_var)
|
||||
break
|
||||
variables_from_parent.extend(variables_from_current)
|
||||
variables_from_include.extend(variables_from_current)
|
||||
|
||||
# serialize back for loading together
|
||||
return [
|
||||
variable.to_dict() for variable in variables_from_parent # type: ignore
|
||||
variable.to_dict() for variable in variables_from_include # type: ignore
|
||||
]
|
||||
|
||||
def _merge_extensions(
|
||||
self,
|
||||
merged_path: Path,
|
||||
data_from_parent: Dict[str, Any],
|
||||
data_from_include: Dict[str, Any],
|
||||
data_from_current: Dict[str, Any],
|
||||
) -> List[Any]:
|
||||
old_extensions = self._load_extensions(merged_path, data_from_parent)
|
||||
old_extensions = self._load_extensions(merged_path, data_from_include)
|
||||
extensions = self._load_extensions(merged_path, data_from_current)
|
||||
# remove duplicate paths
|
||||
for old_extension in old_extensions:
|
||||
|
@ -289,27 +285,28 @@ class RunbookBuilder:
|
|||
def _merge_data(
|
||||
self,
|
||||
merged_path: Path,
|
||||
data_from_parent: Dict[str, Any],
|
||||
data_from_include: Dict[str, Any],
|
||||
data_from_current: Dict[str, Any],
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
merge parent data to data_from_current. The current data has higher level.
|
||||
Merge included data to data_from_current. The current data has
|
||||
higher precedence.
|
||||
"""
|
||||
result = data_from_parent.copy()
|
||||
result = data_from_include.copy()
|
||||
|
||||
# merge others
|
||||
result.update(data_from_current)
|
||||
|
||||
# merge variables, latest should be effective last
|
||||
variables = self._merge_variables(
|
||||
merged_path, data_from_parent, data_from_current
|
||||
merged_path, data_from_include, data_from_current
|
||||
)
|
||||
if variables:
|
||||
result[constants.VARIABLE] = variables
|
||||
|
||||
# merge extensions
|
||||
extensions = self._merge_extensions(
|
||||
merged_path, data_from_parent, data_from_current
|
||||
merged_path, data_from_include, data_from_current
|
||||
)
|
||||
if extensions:
|
||||
result[constants.EXTENSION] = extensions
|
||||
|
@ -323,8 +320,9 @@ class RunbookBuilder:
|
|||
higher_level_variables: Union[List[str], Dict[str, VariableEntry]],
|
||||
) -> Any:
|
||||
"""
|
||||
Load runbook, but not to validate. It will be validated after extension
|
||||
imported. To support partial runbooks, it loads recursively.
|
||||
Load runbook, but not to validate. It will be validated after
|
||||
extensions are imported. To support partial runbooks, it loads
|
||||
recursively.
|
||||
"""
|
||||
|
||||
with open(path, "r") as file:
|
||||
|
@ -335,52 +333,53 @@ class RunbookBuilder:
|
|||
)
|
||||
|
||||
if (
|
||||
constants.PARENT in data_from_current
|
||||
and data_from_current[constants.PARENT]
|
||||
constants.INCLUDE in data_from_current
|
||||
and data_from_current[constants.INCLUDE]
|
||||
):
|
||||
parents_config = data_from_current[constants.PARENT]
|
||||
includes = data_from_current[constants.INCLUDE]
|
||||
|
||||
log = _get_init_logger()
|
||||
indent = len(used_path) * 4 * " "
|
||||
|
||||
data_from_parent: Dict[str, Any] = {}
|
||||
for parent_config in parents_config:
|
||||
data_from_include: Dict[str, Any] = {}
|
||||
for include_raw in includes:
|
||||
try:
|
||||
parent: schema.Parent = schema.Parent.schema().load( # type: ignore
|
||||
parent_config
|
||||
)
|
||||
include: schema.Include
|
||||
include = schema.Include.schema().load(include_raw) # type: ignore
|
||||
except Exception as identifer:
|
||||
raise LisaException(
|
||||
f"error on loading parent node [{parent_config}]: {identifer}"
|
||||
f"error on loading include node [{include_raw}]: {identifer}"
|
||||
)
|
||||
if include.strategy:
|
||||
raise NotImplementedError(
|
||||
"include runbook entry doesn't implement Strategy"
|
||||
)
|
||||
if parent.strategy:
|
||||
raise NotImplementedError("Parent doesn't implement Strategy")
|
||||
|
||||
raw_path = parent.path
|
||||
raw_path = include.path
|
||||
if variables:
|
||||
raw_path = replace_variables(raw_path, variables)
|
||||
if raw_path in used_path:
|
||||
raise LisaException(
|
||||
f"cycle reference parent runbook detected: {raw_path}"
|
||||
f"circular reference on runbook includes detected: {raw_path}"
|
||||
)
|
||||
|
||||
# use relative path to parent runbook
|
||||
parent_path = (path.parent / raw_path).resolve().absolute()
|
||||
log.debug(f"{indent}loading parent: {raw_path}")
|
||||
# use relative path to included runbook
|
||||
include_path = (path.parent / raw_path).resolve().absolute()
|
||||
log.debug(f"{indent}loading include: {raw_path}")
|
||||
|
||||
# clone a set to support same path is used in different tree.
|
||||
new_used_path = used_path.copy()
|
||||
new_used_path.add(raw_path)
|
||||
parent_data = self._load_data(
|
||||
parent_path,
|
||||
include_data = self._load_data(
|
||||
include_path,
|
||||
used_path=new_used_path,
|
||||
higher_level_variables=variables,
|
||||
)
|
||||
data_from_parent = self._merge_data(
|
||||
parent_path.parent, parent_data, data_from_parent
|
||||
data_from_include = self._merge_data(
|
||||
include_path.parent, include_data, data_from_include
|
||||
)
|
||||
data_from_current = self._merge_data(
|
||||
path.parent, data_from_parent, data_from_current
|
||||
path.parent, data_from_include, data_from_current
|
||||
)
|
||||
|
||||
return data_from_current
|
||||
|
|
|
@ -249,9 +249,9 @@ class Strategy:
|
|||
|
||||
@dataclass_json()
|
||||
@dataclass
|
||||
class Parent:
|
||||
class Include:
|
||||
"""
|
||||
share runbook for similar runs.
|
||||
Inclusion of runbook logic, for similar runs.
|
||||
"""
|
||||
|
||||
path: str = field(default="", metadata=metadata(required=True))
|
||||
|
@ -860,7 +860,7 @@ class Runbook:
|
|||
test_pass: str = ""
|
||||
tags: Optional[List[str]] = None
|
||||
concurrency: int = 1
|
||||
parent: Optional[List[Parent]] = field(default=None)
|
||||
include: Optional[List[Include]] = field(default=None)
|
||||
extension: Optional[List[Union[str, Extension]]] = field(default=None)
|
||||
variable: Optional[List[Variable]] = field(default=None)
|
||||
transformer: Optional[List[Transformer]] = field(default=None)
|
||||
|
|
|
@ -63,7 +63,7 @@ OPERATION_OVERWRITE = "overwrite"
|
|||
# topologies
|
||||
ENVIRONMENTS_SUBNET = "subnet"
|
||||
|
||||
PARENT = "parent"
|
||||
INCLUDE = "include"
|
||||
EXTENSION = "extension"
|
||||
VARIABLE = "variable"
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ def load_variables(
|
|||
"""
|
||||
Args::
|
||||
higher_level_variables: it has higher level than current variables. It
|
||||
may be from command lines, or parent runbooks.
|
||||
may be from command lines, or included runbooks.
|
||||
"""
|
||||
if higher_level_variables is None:
|
||||
higher_level_variables = {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: azure default
|
||||
parent:
|
||||
include:
|
||||
- path: ./tiers/tier.yml
|
||||
variable:
|
||||
- name: location
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: local default
|
||||
parent:
|
||||
include:
|
||||
- path: ./tiers/tier.yml
|
||||
notifier:
|
||||
- type: html
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: ready default
|
||||
parent:
|
||||
include:
|
||||
- path: ./tiers/tier.yml
|
||||
variable:
|
||||
- name: user_name
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
parent:
|
||||
include:
|
||||
- path: ./t$(tier).yml
|
||||
extension:
|
||||
- ../../testsuites/core
|
||||
|
|
ΠΠ°Π³ΡΡΠ·ΠΊΠ°β¦
Π‘ΡΡΠ»ΠΊΠ° Π² Π½ΠΎΠ²ΠΎΠΉ Π·Π°Π΄Π°ΡΠ΅