Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for skip, skip_from, and start_from for flows #3832

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7aaf348
Change version naming on PackageUpload task to use the predicted version
jlantz Dec 13, 2023
e5dbbd6
Simple plugin framework that uses a top level plugins: key in
jlantz Dec 13, 2023
1873edd
Add support for outputting markdown of test results including a default
jlantz Dec 13, 2023
22de0cc
Remove testing return
jlantz Dec 13, 2023
a1d0527
Working parent branch resolver, need to write test coverage
jlantz Dec 17, 2023
85e108e
Merge pull request #1 from muselab-d2x/feature/parent-branch-resolver
jlantz Dec 17, 2023
129cba4
Merge pull request #2 from muselab-d2x/feature/version-naming
jlantz Dec 17, 2023
e7851ac
Merge pull request #3 from muselab-d2x/feature/test-output-github
jlantz Dec 17, 2023
2e78be9
Merge pull request #4 from muselab-d2x/feature/plugins
jlantz Dec 17, 2023
73c931b
Added support for `--load-yml` option on cci task and cci flow commands
jlantz Jan 2, 2024
a636dd1
Update docs
jlantz Jan 2, 2024
d28571f
Merge pull request #5 from muselab-d2x/feature/load-yml
jlantz Jan 2, 2024
952fdf5
Add BuildNumber to soql order by when retrieving package versions and…
Jan 8, 2024
16ff525
Merge pull request #6 from muselab-d2x/feature/version-naming
jlantz Jan 8, 2024
959acab
Merge pull request #7 from muselab-d2x/main
jlantz Mar 19, 2024
739908b
Update create_package_version.py
jlantz Mar 19, 2024
58c7dab
Merge pull request #8 from muselab-d2x/feature/fix-2gp-creation-for-o…
jlantz Mar 19, 2024
0987670
Merge pull request #9 from muselab-d2x/main
jlantz May 7, 2024
216820f
Never set <fullName> in 2gp package generation manifests
jlantz May 9, 2024
f9f30c6
Merge pull request #10 from muselab-d2x/feature/no-fullname-2gp
jlantz May 9, 2024
2fa70a9
fix: added comma, removed additional None param
agaudoncr May 9, 2024
126da0f
Update create_package_version.py
jlantz May 9, 2024
a998c79
Merge branch 'd2x' into feature/no-fullname-2gp-fix
agaudoncr May 9, 2024
0af8037
Merge pull request #11 from agaudoncr/feature/no-fullname-2gp-fix
jlantz May 9, 2024
31daa4c
Set package name to None
jlantz May 9, 2024
793c181
Merge branch 'SFDO-Tooling:main' into d2x
jlantz May 30, 2024
4f5f1f0
Initial CumulusCI task for managing Scratch Org Snapshots via the Org…
jlantz Sep 17, 2024
372683f
Merge pull request #13 from muselab-d2x/main
jlantz Sep 17, 2024
e81cd85
Merge branch 'SFDO-Tooling:main' into feature/snapshots
jlantz Sep 17, 2024
35b90f8
Merge pull request #14 from verunainc/feature/snapshots
jlantz Sep 17, 2024
726dd81
Added `create_snapshot` and `github_pull_request_snapshot` tasks to u…
jlantz Sep 17, 2024
8fbad0a
Merge pull request #15 from muselab-d2x/feature/snapshots-enhancements
jlantz Sep 18, 2024
5e11a18
Add support for `skip`, `skip_from`, and `start_from` when running an…
jlantz Sep 19, 2024
7e01275
Merge branch 'd2x' into feature/flow-skip-start
jlantz Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions additional.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
flows:
my_custom_flow:
description: A custom flow loaded via --load-yml
group: Loaded YAML
steps:
1:
task: my_custom_task
tasks:
my_custom_task:
description: A custom task loaded via --load-yml
group: Loaded YAML
class_path: cumulusci.tasks.util.Sleep
options:
seconds: 1
18 changes: 17 additions & 1 deletion cumulusci/cli/cci.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import code
import contextlib
import os
import pdb
import runpy
import sys
Expand Down Expand Up @@ -52,6 +53,7 @@ def main(args=None):

This wraps the `click` library in order to do some initialization and centralized error handling.
"""

with contextlib.ExitStack() as stack:
args = args or sys.argv

Expand All @@ -71,13 +73,27 @@ def main(args=None):
logger, tempfile_path = get_tempfile_logger()
stack.enter_context(tee_stdout_stderr(args, logger, tempfile_path))

context_kwargs = {}

# Allow commands to load additional yaml configuration from a file
if "--load-yml" in args:
yml_path_index = args.index("--load-yml") + 1
try:
load_yml_path = args[yml_path_index]
except IndexError:
raise CumulusCIUsageError("No path specified for --load-yml")
if not os.path.isfile(load_yml_path):
raise CumulusCIUsageError(f"File not found: {load_yml_path}")
with open(load_yml_path, "r") as f:
context_kwargs["additional_yaml"] = f.read()

debug = "--debug" in args
if debug:
args.remove("--debug")

with set_debug_mode(debug):
try:
runtime = CliRuntime(load_keychain=False)
runtime = CliRuntime(load_keychain=False, **context_kwargs)
except Exception as e:
handle_exception(e, is_error_command, tempfile_path, debug)
sys.exit(1)
Expand Down
92 changes: 83 additions & 9 deletions cumulusci/cli/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ def flow():
@click.option(
"--project", "project", is_flag=True, help="Include project-specific flows only"
)
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_project=False, require_keychain=True)
def flow_doc(runtime, project=False):
def flow_doc(runtime, project=False, load_yml=None):
flow_info_path = Path(__file__, "..", "..", "..", "docs", "flows.yml").resolve()
with open(flow_info_path, "r", encoding="utf-8") as f:
flow_info = load_yaml_data(f)
Expand All @@ -44,9 +48,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 @@ -79,8 +83,12 @@ def flow_doc(runtime, project=False):
@flow.command(name="list", help="List available flows for the current context")
@click.option("--plain", is_flag=True, help="Print the table using plain ascii.")
@click.option("--json", "print_json", is_flag=True, help="Print a json string")
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_project=False)
def flow_list(runtime, plain, print_json):
def flow_list(runtime, plain, print_json, load_yml=None):
plain = plain or runtime.universal_config.cli__plain_output
flows = runtime.get_available_flows()
if print_json:
Expand All @@ -106,10 +114,40 @@ 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,8 +179,38 @@ 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)
Expand All @@ -163,7 +231,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
22 changes: 19 additions & 3 deletions cumulusci/cli/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ def task():
@task.command(name="list", help="List available tasks for the current context")
@click.option("--plain", is_flag=True, help="Print the table using plain ascii.")
@click.option("--json", "print_json", is_flag=True, help="Print a json string")
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_project=False)
def task_list(runtime, plain, print_json):
def task_list(runtime, plain, print_json, load_yml=None):
tasks = runtime.get_available_tasks()
plain = plain or runtime.universal_config.cli__plain_output

Expand Down Expand Up @@ -60,8 +64,12 @@ def task_list(runtime, plain, print_json):
is_flag=True,
help="If true, write output to a file (./docs/project_tasks.rst or ./docs/cumulusci_tasks.rst)",
)
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_project=False)
def task_doc(runtime, project=False, write=False):
def task_doc(runtime, project=False, write=False, load_yml=None):
if project and runtime.project_config is None:
raise click.UsageError(
"The --project option can only be used inside a project."
Expand Down Expand Up @@ -95,8 +103,12 @@ def task_doc(runtime, project=False, write=False):

@task.command(name="info", help="Displays information for a task")
@click.argument("task_name")
@click.option(
"--load-yml",
help="If set, loads the specified yml file into the the project config as additional config",
)
@pass_runtime(require_project=False, require_keychain=True)
def task_info(runtime, task_name):
def task_info(runtime, task_name, load_yml=None):
task_config = (
runtime.project_config.get_task(task_name)
if runtime.project_config is not None
Expand Down Expand Up @@ -126,6 +138,10 @@ class RunTaskCommand(click.MultiCommand):
"help": "Drops into the Python debugger at task completion.",
"is_flag": True,
},
"load-yml": {
"help": "If set, loads the specified yml file into the the project config as additional config",
"is_flag": False,
},
}

def list_commands(self, ctx):
Expand Down
84 changes: 82 additions & 2 deletions cumulusci/cli/tests/test_cci.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import builtins
import contextlib
import io
import os
Expand All @@ -8,17 +9,20 @@
from unittest import mock

import click
from click import Command
from click.testing import CliRunner
import pkg_resources
import pytest
from requests.exceptions import ConnectionError
from rich.console import Console

import cumulusci
from cumulusci.cli import cci
from cumulusci.cli.task import task_list
from cumulusci.cli.tests.utils import run_click_command
from cumulusci.cli.utils import get_installed_version
from cumulusci.core.config import BaseProjectConfig
from cumulusci.core.exceptions import CumulusCIException
from cumulusci.core.exceptions import CumulusCIException, CumulusCIUsageError
from cumulusci.utils import temporary_dir

MagicMock = mock.MagicMock()
Expand Down Expand Up @@ -209,6 +213,83 @@ def test_main__CliRuntime_error(CliRuntime, get_tempfile_logger, tee):
tempfile.unlink()


@mock.patch("cumulusci.cli.cci.init_logger") # side effects break other tests
@mock.patch("cumulusci.cli.cci.get_tempfile_logger")
@mock.patch("cumulusci.cli.cci.tee_stdout_stderr")
@mock.patch("sys.exit")
def test_cci_load_yml__missing(
exit, tee_stdout_stderr, get_tempfile_logger, init_logger
):
# get_tempfile_logger doesn't clean up after itself which breaks other tests
get_tempfile_logger.return_value = mock.Mock(), ""
runner = CliRunner()
# Mock the contents of the yaml file
with pytest.raises(CumulusCIUsageError):
cci.main(
[
"cci",
"task",
"list",
"--load-yml",
],
)


@mock.patch("cumulusci.cli.cci.init_logger") # side effects break other tests
@mock.patch("cumulusci.cli.cci.get_tempfile_logger")
@mock.patch("cumulusci.cli.cci.tee_stdout_stderr")
@mock.patch("sys.exit")
# @mock.patch("cumulusci.cli.cci.CliRuntime")
def test_cci_load_yml__notfound(
exit, tee_stdout_stderr, get_tempfile_logger, init_logger
):
# get_tempfile_logger doesn't clean up after itself which breaks other tests
get_tempfile_logger.return_value = mock.Mock(), ""
runner = CliRunner()
with pytest.raises(CumulusCIUsageError):
cci.main(
[
"cci",
"task",
"list",
"--load-yml",
"/path/that/does/not/exist/anywhere",
],
)


@mock.patch("cumulusci.cli.cci.init_logger") # side effects break other tests
@mock.patch("cumulusci.cli.cci.get_tempfile_logger")
@mock.patch("cumulusci.cli.cci.tee_stdout_stderr")
@mock.patch("sys.exit")
@mock.patch("cumulusci.cli.cci.CliRuntime")
def test_cci_load_yml(
CliRuntime, exit, tee_stdout_stderr, get_tempfile_logger, init_logger
):
# get_tempfile_logger doesn't clean up after itself which breaks other tests
get_tempfile_logger.return_value = mock.Mock(), ""

load_yml_path = [cumulusci.__path__[0][: -len("/cumulusci")]]
load_yml_path.append("additional.yml")
load_yml = os.path.join(*load_yml_path)

cci.main(
[
"cci",
"org",
"default",
"--load-yml",
load_yml,
]
)

# Check that CliRuntime was called with the correct arguments
with open(load_yml, "r") as f:
CliRuntime.assert_called_once_with(
load_keychain=False, additional_yaml=f.read()
)


@mock.patch("cumulusci.cli.cci.init_logger") # side effects break other tests
@mock.patch("cumulusci.cli.cci.get_tempfile_logger")
@mock.patch("cumulusci.cli.cci.tee_stdout_stderr")
Expand All @@ -217,7 +298,6 @@ def test_main__CliRuntime_error(CliRuntime, get_tempfile_logger, tee):
def test_handle_org_name(
CliRuntime, tee_stdout_stderr, get_tempfile_logger, init_logger
):

# get_tempfile_logger doesn't clean up after itself which breaks other tests
get_tempfile_logger.return_value = mock.Mock(), ""

Expand Down
4 changes: 2 additions & 2 deletions cumulusci/cli/tests/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ def test_format_help(runtime):

def test_get_default_command_options():
opts = task.RunTaskCommand()._get_default_command_options(is_salesforce_task=False)
assert len(opts) == 4
assert len(opts) == 5

opts = task.RunTaskCommand()._get_default_command_options(is_salesforce_task=True)
assert len(opts) == 5
assert len(opts) == 6
assert any([o.name == "org" for o in opts])


Expand Down
Loading
Loading