Parser: fix template inheritance

This commit is contained in:
Akemi Izuko 2024-05-26 21:38:58 -06:00
parent fab1f94faa
commit bd65674875
Signed by: akemi
GPG key ID: 8DE0764E1809E9FC
2 changed files with 60 additions and 27 deletions

View file

@ -169,10 +169,12 @@ orca:
range: range:
from: 9026 from: 9026
to: 9042 to: 9042
template: LocalForward ${port} localhost:${port} property: LocalForward
template: ${port} localhost:${port}
- variable: env - variable: env
range: ["FOO=bar", "BAR=foo"] range: ["FOO=bar", "BAR=foo"]
template: SendEnv ${env} property: SendEnv
template: ${env}
``` ```
Expands to: Expands to:

View file

@ -6,7 +6,7 @@ from pathlib import Path
parser = argparse.ArgumentParser(description="Process some integers.") parser = argparse.ArgumentParser(description="Process some integers.")
parser.add_argument("file", type=Path, help="yaml config") parser.add_argument("file", type=Path, help="yaml config")
parser.add_argument("--json", action="store_true", help="output as json") parser.add_argument("--json", action="store_true", help="output parse as json")
parser.add_argument("--out", type=Path, help="write into file") parser.add_argument("--out", type=Path, help="write into file")
args = parser.parse_args() args = parser.parse_args()
@ -66,23 +66,57 @@ def expand_for_loop(props):
return expand return expand
def get_ssh_props(data, hosts): def get_ssh_props(data):
props = list() props = dict()
if data.get('template'):
props = list(hosts[data.get('template')])
for k, v in data["ssh_props"].items(): for k, v in data["ssh_props"].items():
if isinstance(v, list): props[k] = v if isinstance(v, list) else [v]
for x in v:
props.append(f"{k} {x}")
else:
props.append(f"{k} {v}")
return props return props
def remove_templates(expanded, data): def parse_props(host, props, hosts):
parsed = dict()
# Standard ssh properties
for k, v in props.get("ssh_props", {}).items():
parsed[k] = v if isinstance(v, list) else [v]
# For-loops in config
for loop in props.get("for", []):
prop = loop["property"]
r = loop["range"]
if isinstance(r, dict):
iterations = range(r["from"], r["to"])
elif isinstance(r, list):
iterations = r
else:
raise TypeError("Expected list or dict in `for` loop `range`")
if not parsed.get(prop):
parsed[prop] = list()
pattern = re.compile(rf'\$\{{{loop["variable"]}\}}')
for i in iterations:
sub = pattern.sub(str(i), loop["template"])
parsed[prop].append(sub)
# Inherit from template
template = props.get("template")
if template and template not in hosts:
raise SyntaxError(f"Template `{template}` required by `{host}` is not declared above `{host}` in the yaml")
elif template:
for k, v in hosts[props.get("template")].items():
if k not in parsed:
parsed[k] = v
return parsed
def remove_templates(expanded):
hosts = dict(expanded) hosts = dict(expanded)
for host, props in data.items(): for host, props in data.items():
@ -109,21 +143,14 @@ def stringify_booleans(props):
def parse_yaml(data): def parse_yaml(data):
expanded = dict() hosts = dict()
check_duplicate_hosts(data) check_duplicate_hosts(data)
for host, props in data.items(): for host, props in data.items():
parsed = list() hosts[host] = parse_props(host, props, hosts)
parsed.extend(get_ssh_props(props, expanded))
parsed.extend(expand_for_loop(props))
parsed = stringify_booleans(parsed) hosts = remove_templates(hosts)
run_assertions(host, parsed) return hosts
expanded[host] = sorted(parsed)
expanded = remove_templates(expanded, data)
return expanded
# ╔───────────────────────────────────────────────────────────────────────────╗ # ╔───────────────────────────────────────────────────────────────────────────╗
@ -134,8 +161,12 @@ def to_ssh_config_string(parse):
for host, keys in parse.items(): for host, keys in parse.items():
s += f"Host {host}\n" s += f"Host {host}\n"
for key in keys: for key, values in keys.items():
s += f"\t{key}\n" for value in values:
if isinstance(value, bool):
s += f"\t{key} {'true' if value else 'no'}\n"
else:
s += f"\t{key} {value}\n"
s += "\n" s += "\n"
return s return s