Skip to content

Commit 9d650ac

Browse files
committed
feat(cli): add resolve_extra_yaml helper for --extra-yaml flag
Introduces a pure function that reads one or more paths (from the --extra-yaml flag or CUMULUSCI_EXTRA_YAML env var) and returns the concatenated YAML content as a single string, suitable for passing as BaseProjectConfig's additional_yaml kwarg. Implements the motivating use case from #3725 (thanks @jlantz) with a different API: flag name, multi-file support, env var fallback, and proper Click wiring are all different.
1 parent a836976 commit 9d650ac

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

cumulusci/cli/extra_yaml.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Resolve ``--extra-yaml`` CLI flag and ``CUMULUSCI_EXTRA_YAML`` env var.
2+
3+
The returned string is passed as ``BaseProjectConfig``'s ``additional_yaml``
4+
kwarg, which already merges into the project config via the existing YAML
5+
merge stack.
6+
"""
7+
import os
8+
from typing import Optional, Tuple
9+
10+
import click
11+
12+
from cumulusci.core.exceptions import CumulusCIUsageError
13+
14+
ENV_VAR = "CUMULUSCI_EXTRA_YAML"
15+
16+
17+
def resolve_extra_yaml(paths: Tuple[str, ...]) -> Optional[str]:
18+
"""Read extra-yaml paths from the CLI flag (preferred) or env var (fallback).
19+
20+
Args:
21+
paths: Tuple of paths from Click's ``multiple=True`` option. Empty
22+
means the flag was not supplied; fall back to
23+
``CUMULUSCI_EXTRA_YAML`` (colon-separated paths).
24+
25+
Returns:
26+
Concatenated YAML content with ``\\n---\\n`` separators between files,
27+
or ``None`` if no paths were resolved.
28+
29+
Raises:
30+
CumulusCIUsageError: If any listed path does not exist or is unreadable.
31+
"""
32+
effective_paths = paths
33+
if not effective_paths:
34+
env_value = os.environ.get(ENV_VAR)
35+
if env_value:
36+
effective_paths = tuple(p for p in env_value.split(":") if p)
37+
38+
if not effective_paths:
39+
return None
40+
41+
click.echo(
42+
f"Loading extra YAML from: {', '.join(effective_paths)}. "
43+
"Extra YAML can redefine task class_path entries and run arbitrary "
44+
"Python code; only load files you trust.",
45+
err=True,
46+
)
47+
48+
contents = []
49+
for path in effective_paths:
50+
if not os.path.isfile(path):
51+
raise CumulusCIUsageError(f"--extra-yaml file not found: {path}")
52+
try:
53+
with open(path, "r", encoding="utf-8") as f:
54+
contents.append(f.read())
55+
except OSError as e:
56+
raise CumulusCIUsageError(f"--extra-yaml could not read {path}: {e}")
57+
return "\n---\n".join(contents)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from cumulusci.cli.extra_yaml import resolve_extra_yaml
2+
3+
4+
def test_resolve_extra_yaml__none_when_no_input(monkeypatch):
5+
monkeypatch.delenv("CUMULUSCI_EXTRA_YAML", raising=False)
6+
assert resolve_extra_yaml(()) is None
7+
8+
9+
def test_resolve_extra_yaml__single_file(tmp_path, monkeypatch):
10+
monkeypatch.delenv("CUMULUSCI_EXTRA_YAML", raising=False)
11+
p = tmp_path / "extra.yml"
12+
p.write_text("tasks:\n foo:\n description: from file\n")
13+
result = resolve_extra_yaml((str(p),))
14+
assert result is not None
15+
assert "from file" in result
16+
assert result.startswith("tasks:")

0 commit comments

Comments
 (0)