Skip to content

Commit

Permalink
Add support for skip, skip_from, and start_from when running an…
Browse files Browse the repository at this point in the history
…d defining flows
  • Loading branch information
jlantz committed Sep 19, 2024
1 parent da60446 commit 5e11a18
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 19 deletions.
79 changes: 71 additions & 8 deletions cumulusci/cli/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ def flow_doc(runtime, project=False):
flows_by_group = group_items(flows)
flow_groups = sorted(
flows_by_group.keys(),
key=lambda group: flow_info_groups.index(group)
if group in flow_info_groups
else 100,
key=lambda group: (
flow_info_groups.index(group) if group in flow_info_groups else 100
),
)

for group in flow_groups:
Expand Down Expand Up @@ -106,10 +106,39 @@ def flow_list(runtime, plain, print_json):

@flow.command(name="info", help="Displays information for a flow")
@click.argument("flow_name")
@click.option(
"--skip", help="Specify a comma separated list of task and flow names to skip."
)
@click.option(
"--skip-from",
help="Specify a task or flow name to skip and all steps that follow it.",
)
@click.option(
"--start-from",
help="Specify a task or flow name to start from. All prior steps will be skippped.",
)
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_keychain=True)
def flow_info(runtime, flow_name):
def flow_info(
runtime,
flow_name,
skip=None,
skip_from=None,
start_from=None,
load_yml=None,
):
if skip:
skip = skip.split(",")
try:
coordinator = runtime.get_flow(flow_name)
coordinator = runtime.get_flow(
flow_name,
skip=skip,
skip_from=skip_from,
start_from=start_from,
)
output = coordinator.get_summary(verbose=True)
click.echo(output)
except FlowNotFoundError as e:
Expand Down Expand Up @@ -141,9 +170,37 @@ def flow_info(runtime, flow_name):
is_flag=True,
help="Disables all prompts. Set for non-interactive mode use such as calling from scripts or CI systems",
)
@click.option(
"--skip", help="Specify a comma separated list of task and flow names to skip."
)
@click.option(
"--skip-from",
help="Specify a task or flow name to skip and all steps that follow it.",
)
@click.option(
"--start-from",
help="Specify a task or flow name to start from. All prior steps will be skippped.",
)
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_keychain=True)
def flow_run(runtime, flow_name, org, delete_org, debug, o, no_prompt):

def flow_run(
runtime,
flow_name,
org,
delete_org,
debug,
o,
no_prompt,
skip=None,
skip_from=None,
start_from=None,
load_yml=None,
):
if skip:
skip = skip.split(",")
# Get necessary configs
org, org_config = runtime.get_org(org)
if delete_org and not org_config.scratch:
Expand All @@ -163,7 +220,13 @@ def flow_run(runtime, flow_name, org, delete_org, debug, o, no_prompt):

# Create the flow and handle initialization exceptions
try:
coordinator = runtime.get_flow(flow_name, options=options)
coordinator = runtime.get_flow(
flow_name,
options=options,
skip=skip,
skip_from=skip_from,
start_from=start_from,
)
start_time = datetime.now()
coordinator.run(org_config)
duration = datetime.now() - start_time
Expand Down
48 changes: 40 additions & 8 deletions cumulusci/core/flowrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class StepSpec:
"allow_failure",
"path",
"skip",
"skip_steps",
"skip_from",
"start_from",
"when",
)

Expand All @@ -122,6 +125,9 @@ class StepSpec:
allow_failure: bool
path: str
skip: bool
skip_steps: List[str]
skip_from: str
start_from: str
when: Optional[str]

def __init__(
Expand All @@ -134,6 +140,9 @@ def __init__(
allow_failure: bool = False,
from_flow: Optional[str] = None,
skip: bool = False,
skip_from: Optional[str] = None,
skip_steps: Optional[List[str]] = None,
start_from: Optional[str] = None,
when: Optional[str] = None,
):
self.step_num = step_num
Expand All @@ -143,6 +152,9 @@ def __init__(
self.project_config = project_config
self.allow_failure = allow_failure
self.skip = skip
self.skip_from = skip_from
self.skip_steps = skip_steps or []
self.start_from = start_from
self.when = when

# Store the dotted path to this step.
Expand Down Expand Up @@ -326,6 +338,7 @@ class FlowCoordinator:
callbacks: FlowCallback
logger: logging.Logger
skip: List[str]
skip_from: List[str]
flow_config: FlowConfig
runtime_options: dict
name: Optional[str]
Expand All @@ -338,6 +351,8 @@ def __init__(
name: Optional[str] = None,
options: Optional[dict] = None,
skip: Optional[List[str]] = None,
skip_from: Optional[List[str]] = None,
start_from: Optional[str] = None,
callbacks: Optional[FlowCallback] = None,
):
self.project_config = project_config
Expand All @@ -352,6 +367,10 @@ def __init__(
self.runtime_options = options or {}

self.skip = skip or []
self.skip_from = skip_from or None
self.start_from = start_from
self.skip_beginning = start_from is not None
self.skip_remaining = False
self.results = []

self.logger = self._init_logger()
Expand Down Expand Up @@ -426,7 +445,11 @@ def get_flow_steps(
if for_docs:
source = ""

lines.append(f"{' ' * i}{steps[i]}) flow: {flow_name}{source}")
line = f"{' ' * i}{steps[i]}) flow: {flow_name}{source}"
if step.skip:
line += " [skip]"
line = f"\033[90m{line}\033[0m" # Gray color
lines.append(line)
if source:
new_source = ""

Expand All @@ -444,9 +467,11 @@ def get_flow_steps(
for option, value in options.items():
options_info += f"\n{padding} {option}: {value}"

lines.append(
f"{' ' * (i + 1)}{steps[i + 1]}) task: {task_name}{new_source}"
)
line = f"{' ' * (i + 1)}{steps[i + 1]}) task: {task_name}{new_source}"
if step.skip:
line += " [skip]"
line = f"\033[90m{line}\033[0m" # Gray color
lines.append(line)

if when:
lines.append(when)
Expand Down Expand Up @@ -600,13 +625,20 @@ def _visit_step(
assert step_config.keys() != {"task", "flow"}

# Skips
# - either in YAML (with the None string)
# - either in YAML (with the None string for task or flow on a step) or in the skip list on a flow step
# - or by providing a skip list to the FlowRunner at initialization.
task_or_flow = step_config.get("task", step_config.get("flow"))
if task_or_flow and task_or_flow == self.start_from:
self.skip_beginning = False
if (
("flow" in step_config and step_config["flow"] == "None")
or ("task" in step_config and step_config["task"] == "None")
or ("task" in step_config and step_config["task"] in self.skip)
task_or_flow == "None"
or task_or_flow in self.skip
or task_or_flow == self.skip_from
or self.skip_remaining
or self.skip_beginning
):
if task_or_flow == self.skip_from:
self.skip_remaining = True
visited_steps.append(
StepSpec(
step_num=step_number,
Expand Down
15 changes: 12 additions & 3 deletions cumulusci/core/runtime.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys
from abc import abstractmethod
from typing import Optional, Type
from typing import List, Optional, Type

from cumulusci.core.config import BaseProjectConfig, UniversalConfig
from cumulusci.core.debug import DebugMode, get_debug_mode
Expand Down Expand Up @@ -98,7 +98,14 @@ def _load_keychain(self):
self.keychain = self.keychain_cls(self.project_config, keychain_key)
self.project_config.keychain = self.keychain

def get_flow(self, name: str, options: Optional[dict] = None) -> FlowCoordinator:
def get_flow(
self,
name: str,
options: Optional[dict] = None,
skip: Optional[List[str]] = None,
skip_from: Optional[str] = None,
start_from: Optional[str] = None,
) -> FlowCoordinator:
"""Get a primed and ready-to-go flow coordinator."""
if not self.project_config:
raise ProjectConfigNotFound
Expand All @@ -109,7 +116,9 @@ def get_flow(self, name: str, options: Optional[dict] = None) -> FlowCoordinator
flow_config,
name=flow_config.name,
options=options,
skip=None,
skip=skip or flow_config.skip,
skip_from=skip_from or flow_config.skip_from,
start_from=start_from or flow_config.start_from,
callbacks=callbacks,
)
return coordinator
15 changes: 15 additions & 0 deletions cumulusci/schema/cumulusci.jsonschema.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,21 @@
"group": {
"title": "Group",
"type": "string"
},
"skip_steps": {
"title": "Skip Steps",
"type": "array",
"items": {
"type": "string"
}
},
"skip_from": {
"title": "Skip From",
"type": "string"
},
"start_from": {
"title": "Start From",
"type": "string"
}
},
"additionalProperties": false
Expand Down
3 changes: 3 additions & 0 deletions cumulusci/utils/yaml/cumulusci_yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class Flow(CCIDictModel):
description: str = None
steps: Dict[str, Step] = None
group: str = None
skip_steps: Optional[List[str]] = None
skip_from: Optional[str] = None
start_from: Optional[str] = None


class Package(CCIDictModel):
Expand Down

0 comments on commit 5e11a18

Please sign in to comment.