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

[Resolve #1212] Conditional Stacks via "ignore" and "obsolete" #1229

Merged
merged 109 commits into from
Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
ded588c
creating conditional stacks with launch_action
jfalkenstein May 28, 2022
b12db2d
adding launch action to __eq__ and __repr__
jfalkenstein May 28, 2022
3c60ded
integration-testing conditional stacks
jfalkenstein May 28, 2022
ae1fb09
testing and fixing
jfalkenstein May 28, 2022
efb584d
fixing docstrings
jfalkenstein May 28, 2022
dd85b41
stubbing out tests
jfalkenstein May 28, 2022
e80d22d
more stubbing tests
jfalkenstein May 28, 2022
e78f003
Merge remote-tracking branch 'sceptre/master' into 1212-conditional-s…
jfalkenstein Jun 19, 2022
629e725
improving docstrings
jfalkenstein Jun 19, 2022
39d5b74
fixing unit tests
jfalkenstein Jun 19, 2022
e9c438a
filling out test
jfalkenstein Jun 19, 2022
3c616fe
fixing docs warnings
jfalkenstein Jun 19, 2022
7e17858
fixing linter
jfalkenstein Jun 19, 2022
3841f82
removing unnecessary test
jfalkenstein Jun 19, 2022
17cff42
import organization
jfalkenstein Jun 19, 2022
d3d9519
fixing repr
jfalkenstein Jun 19, 2022
3a89ca9
handling excluded dependencies
jfalkenstein Jul 18, 2022
cc7c371
failing if dependency is excluded
jfalkenstein Jul 18, 2022
f081119
import organization
jfalkenstein Jul 18, 2022
4691e3d
Merge remote-tracking branch 'sceptre/master' into 1212-conditional-s…
jfalkenstein Jul 22, 2022
da49b6a
taking suggestion for docs
jfalkenstein Jul 22, 2022
f9fde0d
making it an f-string
jfalkenstein Jul 22, 2022
a39e46c
renaming LaunchAction enums and skipping skipped stacks
jfalkenstein Jul 22, 2022
3fac144
updating docs
jfalkenstein Jul 22, 2022
ea89606
changing exclude to remove
jfalkenstein Jul 23, 2022
93ee69d
skipping and deleting better
jfalkenstein Jul 23, 2022
4e6b9c4
renaming launch actions
jfalkenstein Jul 23, 2022
a8e6e8b
getting prototype for deletion working
jfalkenstein Jul 23, 2022
bfc1926
making launching a bit smoother with Launcher utility
jfalkenstein Jul 23, 2022
7839357
fixing method reference
jfalkenstein Jul 23, 2022
b8961a1
handling dependencies and skip actions better
jfalkenstein Jul 23, 2022
17e2845
making SceptrePlan work a bit smoother
jfalkenstein Jul 23, 2022
1a4abe0
returning early
jfalkenstein Jul 23, 2022
138a84b
updating docs
jfalkenstein Jul 23, 2022
710f16f
making diff command exclude unlaunchable stacks
jfalkenstein Jul 23, 2022
56c0757
renaming variable
jfalkenstein Jul 23, 2022
945435a
deduping dependency list
jfalkenstein Jul 23, 2022
8de3bf4
cloning better
jfalkenstein Jul 23, 2022
c31f158
removing launch_action tests
jfalkenstein Jul 23, 2022
d632063
fixing stack repr
jfalkenstein Jul 23, 2022
8b64667
fixing code to get tests to pass
jfalkenstein Jul 23, 2022
32d807f
making test_cli into package
jfalkenstein Jul 23, 2022
0e61747
stubbing out tests on launcher
jfalkenstein Jul 23, 2022
9cdfa29
testing Launcher class
jfalkenstein Jul 24, 2022
c623e1d
adding faq note about marking stacks for deletion
jfalkenstein Jul 24, 2022
4f6c1ca
improving docs
jfalkenstein Jul 24, 2022
f6390be
updating steps to use Launcher
jfalkenstein Jul 24, 2022
19393fa
fixing launch tests for new launch actions
jfalkenstein Jul 24, 2022
f621b5c
fixing integration tests for launching a stack with different launch …
jfalkenstein Jul 24, 2022
7e6007b
making launchable-only diffing opt-in rather than opt-out
jfalkenstein Jul 24, 2022
9fd7abd
fixing diff docstring
jfalkenstein Jul 24, 2022
d2a73f4
simplifying launcher code
jfalkenstein Jul 24, 2022
90437b7
removing deletions
jfalkenstein Jul 24, 2022
2ff5aa2
returning early
jfalkenstein Jul 24, 2022
2eafe93
creating docstring on clone
jfalkenstein Jul 24, 2022
8fcc6e2
testing clone functionality
jfalkenstein Jul 24, 2022
ed15c90
organizing imports
jfalkenstein Jul 24, 2022
652a95f
appeasing the linter
jfalkenstein Jul 27, 2022
b4b9040
filtering objects correctly
jfalkenstein Jul 28, 2022
2a65282
fixing the executor for empty batches
jfalkenstein Aug 1, 2022
2323ba9
any more --> anymore
jfalkenstein Aug 1, 2022
d3fc46b
stages --> steps
jfalkenstein Aug 1, 2022
d0ba0ca
docs cleanup
jfalkenstein Aug 1, 2022
5133f7c
clarifying steps to delete stacks via launch
jfalkenstein Aug 1, 2022
d166e74
clarifying launch action options
jfalkenstein Aug 1, 2022
558e3c4
adding test for bad launch action
jfalkenstein Aug 1, 2022
7d89c5d
import organization
jfalkenstein Aug 1, 2022
6fd836f
diffing only launchable stacks by default
jfalkenstein Aug 1, 2022
7a4f6b7
transitioning to obsolete and ignore
jfalkenstein Aug 8, 2022
4e68ddb
fixing tests
jfalkenstein Aug 8, 2022
e6d010d
creating prune command
jfalkenstein Aug 11, 2022
707480b
reverting back to old way of gathering command stacks
jfalkenstein Aug 13, 2022
4e9ff3c
removing __eq__ check on resolvable properties
jfalkenstein Aug 13, 2022
930ef9d
creating prune cli command
jfalkenstein Aug 13, 2022
a77d9b7
adding docs
jfalkenstein Aug 13, 2022
7850c45
documenting the ignore configuration
jfalkenstein Aug 13, 2022
29b4163
updating docs
jfalkenstein Aug 13, 2022
488ae97
updating cli docs
jfalkenstein Aug 13, 2022
29a6216
fixing integration tests
jfalkenstein Aug 13, 2022
bf59ac5
import organization
jfalkenstein Aug 14, 2022
2c8c415
using newer version of cfn-lint
jfalkenstein Aug 14, 2022
6fa07a3
using new runtime
jfalkenstein Aug 14, 2022
884de96
cleaning up all mention of launch_action
jfalkenstein Aug 14, 2022
656aabc
improving docs
jfalkenstein Aug 14, 2022
7605e11
docs update
jfalkenstein Aug 14, 2022
b0416f4
docs update
jfalkenstein Aug 14, 2022
119a7ad
removing comment
jfalkenstein Aug 14, 2022
c83cc1a
fixing annotations and docstring for diff action
jfalkenstein Aug 14, 2022
cd8b581
adding comment to explain operation
jfalkenstein Aug 14, 2022
eaf9662
small docs improvement
jfalkenstein Aug 14, 2022
3813fea
adding docstrings
jfalkenstein Aug 14, 2022
2fb8a22
fixing warning
jfalkenstein Aug 14, 2022
056774e
docs update
jfalkenstein Aug 23, 2022
c837132
updating feature name
jfalkenstein Aug 23, 2022
daa2d40
bumping sphinx & autodoc-typehints versions to handle TYPE_CHECKING
jfalkenstein Aug 23, 2022
14df230
regarding ignore_dependencies flag
jfalkenstein Aug 23, 2022
322b759
using the pruner in the launcher
jfalkenstein Aug 23, 2022
2bf9522
separating out confirmation/printing from action actions
jfalkenstein Aug 28, 2022
c249c46
fixing launcher tests
jfalkenstein Aug 28, 2022
a0426e0
fixing pruner tests
jfalkenstein Aug 28, 2022
0adcadd
returning from prune operation early
jfalkenstein Aug 28, 2022
3044741
not confirming if there is nothing to prune
jfalkenstein Aug 28, 2022
ee0c518
fixing integration tests
jfalkenstein Aug 28, 2022
6b0de1f
creating integration tests on prune command
jfalkenstein Aug 28, 2022
d3d16cd
fixing comment
jfalkenstein Aug 28, 2022
eabdbc0
adding stack validations for ignore and obsolete
jfalkenstein Aug 28, 2022
32d03bc
adding stack init validation tests
jfalkenstein Aug 28, 2022
d16f3f2
ignoring dependencies properly in launcher
jfalkenstein Aug 28, 2022
d5ac454
indicating which stack had the bad config
jfalkenstein Sep 16, 2022
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
19 changes: 19 additions & 0 deletions docs/_source/docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,22 @@ Sceptre project, see the `sceptre-sam-handler`_ page on PyPI.


.. _sceptre-sam-handler: https://pypi.org/project/sceptre-sam-handler/

My CI/CD process uses ``sceptre launch``. How do I delete stacks that aren't needed any more?
---------------------------------------------------------------------------------------------

Running the ``launch`` command is a very useful "1-stop-shop" to apply changes from Stack Configs,
creating stacks that don't exist and updating stacks that do exist. This makes it a very useful
command to configure your CI/CD system to invoke. However, sometimes you need to delete a stack that
isn't needed any more and you want this automatically applied by the same process.
jfalkenstein marked this conversation as resolved.
Show resolved Hide resolved

This "clean up" is complicated by the fact that Sceptre doesn't know about anything that isn't
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
in its Stack and StackGroup Configs; If you delete a Stack Config, Sceptre won't know to clean it up.

Therefore, the way to accomplish this "clean up" operation is to perform the change in 2 stages:
jfalkenstein marked this conversation as resolved.
Show resolved Hide resolved

1. First, add ``launch_action: "exclude"`` to the Stack Config(s) you want to clean up. This will
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
cause that stack to be deleted (if it exists) when you run ``sceptre launch``. For more information
jfalkenstein marked this conversation as resolved.
Show resolved Hide resolved
on ``launch_action``, see the :ref:`Stack Config entry on it<launch_action>`.
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
2. Once you've launched the Stack(s) to have them deleted this way, only **then** should you
delete the Stack Configs.
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
jfalkenstein marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 44 additions & 1 deletion docs/_source/docs/stack_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ particular Stack. The available keys are listed below.
- `template_path`_ or `template`_ *(required)*
- `dependencies`_ *(optional)*
- `hooks`_ *(optional)*
- `launch_action`_ *(optional)*
- `notifications`_ *(optional)*
- `on_failure`_ *(optional)*
- `parameters`_ *(optional)*
Expand Down Expand Up @@ -100,6 +101,48 @@ hooks
A list of arbitrary shell or Python commands or scripts to run. Find out more
in the :doc:`hooks` section.


.. _launch_action:

launch_action
~~~~~~~~~~~~~
* Resolvable: No
* Can be inherited from StackGroup: Yes
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
* Inheritance strategy: Overrides parent if set

This setting determines whether or not the stack should be included or excluded when running
``sceptre launch``. This must be one of two values: "include" or "exclude". The default value for
this (which doesn't need to be specified) is "include". This **only** applies to the ``launch``
command.

If the ``launch_action`` is set to ``"exclude"``, it means that:
* If the stack does NOT exist, the Stack will not be created and will be skipped over
* If the stack *does* currently exist, it will be deleted.
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved

This setting is especially useful in two situations:

1. You can use it to make conditional stacks that may or may not be included in a Stack Group based
upon some Jinja2 logic.
2. If your CI/CD deployment process runs ``sceptre launch``, you can use this to have stacks deleted
when it runs. Once the stack(s) have been deleted, you can then delete the StackConfig file.

For Example:

.. code-block:: yaml

template:
path: "my/test/resources.yaml"

{% if not var.use_test_resources %}
launch_action: "exclude"
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
{% endif %}

.. note::

The ``launch_action`` configuration only applies to the **launch** command. You can still run
``create``, ``update``, or ``delete`` commands on a stack marked with ``launch_action: "exclude"``.
If you do, the command will operate just like it normally does.
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved

notifications
~~~~~~~~~~~~~
* Resolvable: Yes
Expand Down Expand Up @@ -245,7 +288,7 @@ For more information on this configuration, its implications, and its uses, see
:ref:`Sceptre and IAM: iam_role <iam_role_permissions>`.

iam_role_session_duration
~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~
* Resolvable: No
* Can be inherited from StackGroup: Yes
* Inheritance strategy: Overrides parent if set
Expand Down
29 changes: 21 additions & 8 deletions integration-tests/features/launch-stack-group.feature
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ Feature: Launch stack_group

Scenario: launch a stack_group with updates that partially exists
Given stack "2/A" exists in "CREATE_COMPLETE" state
and stack "2/B" does not exist
and stack "2/C" does not exist
and the template for stack "2/A" is "updated_template.json"
And stack "2/B" does not exist
And stack "2/C" does not exist
And the template for stack "2/A" is "updated_template.json"
When the user launches stack_group "2"
Then stack "2/A" exists in "UPDATE_COMPLETE" state
and stack "2/B" exists in "CREATE_COMPLETE" state
and stack "2/C" exists in "CREATE_COMPLETE" state
And stack "2/B" exists in "CREATE_COMPLETE" state
And stack "2/C" exists in "CREATE_COMPLETE" state

Scenario: launch a stack_group with updates that already exists
Given all the stacks in stack_group "2" are in "CREATE_COMPLETE"
and the template for stack "2/A" is "updated_template.json"
and the template for stack "2/B" is "updated_template.json"
and the template for stack "2/C" is "updated_template.json"
And the template for stack "2/A" is "updated_template.json"
And the template for stack "2/B" is "updated_template.json"
And the template for stack "2/C" is "updated_template.json"
When the user launches stack_group "2"
Then all the stacks in stack_group "2" are in "UPDATE_COMPLETE"

Expand All @@ -47,3 +47,16 @@ Feature: Launch stack_group
Given stack_group "2" does not exist
When the user launches stack_group "2" with ignore dependencies
Then all the stacks in stack_group "2" are in "CREATE_COMPLETE"

Scenario: launch a StackGroup with stacks with launch_type = excluded that has not been launched
Given stack_group "launch-actions" does not exist
When the user launches stack_group "launch-actions"
Then stack "launch-actions/excluded" does not exist
And stack "launch-actions/included" exists in "CREATE_COMPLETE" state

Scenario: launch a StackGroup with stacks with launch_type = excluded that currently exist
Given stack "launch-actions/excluded" exists using "valid_template.json"
And stack "launch-actions/included" exists using "valid_template.json"
When the user launches stack_group "launch-actions"
Then stack "launch-actions/excluded" does not exist
And stack "launch-actions/included" exists in "CREATE_COMPLETE" state
18 changes: 14 additions & 4 deletions integration-tests/features/launch-stack.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@ Feature: Launch stack

Scenario: launch a new stack
Given stack "1/A" does not exist
and the template for stack "1/A" is "valid_template.json"
And the template for stack "1/A" is "valid_template.json"
When the user launches stack "1/A"
Then stack "1/A" exists in "CREATE_COMPLETE" state

Scenario: launch a stack that was newly created
Given stack "1/A" exists in "CREATE_COMPLETE" state
and the template for stack "1/A" is "updated_template.json"
And the template for stack "1/A" is "updated_template.json"
When the user launches stack "1/A"
Then stack "1/A" exists in "UPDATE_COMPLETE" state

Scenario: launch a stack that has been previously updated
Given stack "1/A" exists in "UPDATE_COMPLETE" state
and the template for stack "1/A" is "valid_template.json"
And the template for stack "1/A" is "valid_template.json"
When the user launches stack "1/A"
Then stack "1/A" exists in "UPDATE_COMPLETE" state

Scenario: launch a new stack with ignore dependencies
Given stack "1/A" does not exist
and the template for stack "1/A" is "valid_template.json"
And the template for stack "1/A" is "valid_template.json"
When the user launches stack "1/A" with ignore dependencies
Then stack "1/A" exists in "CREATE_COMPLETE" state

Scenario: launch a stack with launch_type = exclude that doesn't exist
Given stack "launch-actions/excluded" does not exist
When the user launches stack "launch-actions/excluded"
Then stack "launch-actions/excluded" does not exist

Scenario: launch a stack with launch_type = exclude that does exist
Given stack "launch-actions/excluded" exists using "valid_template.json"
When the user launches stack "launch-actions/excluded"
Then stack "launch-actions/excluded" does not exist
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This stack shouldn't ever be launched
launch_action: exclude

# Since the launch type is exclude, we don't need any other attributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This is the default so it isn't required, but for testing let's explicitly set it.
launch_action: include

template:
path: valid_template.yaml
1 change: 1 addition & 0 deletions integration-tests/steps/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def set_template_path(context, stack_name, template_name):
if "template_path" in stack_config:
stack_config["template_path"] = template_path
if "template" in stack_config:
stack_config["template"]["type"] = stack_config["template"].get("type", "file")
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
template_handler_type = stack_config["template"]["type"]
if template_handler_type.lower() == 's3':
segments = stack_config["template"]["path"].split('/')
Expand Down
31 changes: 27 additions & 4 deletions sceptre/cli/launch.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import click
from click import Context
from colorama import Fore, Style

from sceptre.context import SceptreContext
from sceptre.cli.helpers import catch_exceptions
from sceptre.cli.helpers import confirmation
from sceptre.cli.helpers import stack_status_exit_code
from sceptre.plan.plan import SceptrePlan
from sceptre.stack import LaunchAction


@click.command(name="launch", short_help="Launch a Stack or StackGroup.")
Expand All @@ -14,15 +17,21 @@
)
@click.pass_context
@catch_exceptions
def launch_command(ctx, path, yes):
def launch_command(ctx: Context, path: str, yes: bool):
"""
Launch a Stack or StackGroup for a given config PATH.
Launch a Stack or StackGroup for a given config PATH. This command is intended as a catch-all
command that will apply any changes from Stack Configs indicated via the path.

\b
* Any Stacks that do not exist will be created
* Any stacks that already exist will be updated (if there are any changes)
* If any stacks are marked with launch_type: exclude, they will NOT be created and, if they exist,
will be deleted.

\f

:param path: The path to launch. Can be a Stack or StackGroup.
:type path: str
:param yes: A flag to answer 'yes' to all CLI questions.
:type yes: bool
"""
context = SceptreContext(
command_path=path,
Expand All @@ -33,6 +42,20 @@ def launch_command(ctx, path, yes):
)

plan = SceptrePlan(context)
plan.resolve(plan.launch.__name__)

stacks_to_exclude = []
for stacks in plan.launch_order:
for stack in stacks:
if stack.launch_action == LaunchAction.exclude:
stacks_to_exclude.append(stack)
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved

if stacks_to_exclude:
delete_message = "The following stacks are excluded from the launch. They will be deleted, if they exist:\n"
for stack in stacks_to_exclude:
delete_message += f"{Fore.YELLOW}{stack.name}{Style.RESET_ALL}\n"

print(delete_message)

confirmation(plan.launch.__name__, yes, command_path=path)
responses = plan.launch()
Expand Down
6 changes: 4 additions & 2 deletions sceptre/config/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from sceptre.exceptions import VersionIncompatibleError
from sceptre.exceptions import ConfigFileNotFoundError
from sceptre.helpers import sceptreise_path
from sceptre.stack import Stack
from sceptre.stack import Stack, LaunchAction
from sceptre.config import strategies

ConfigAttributes = collections.namedtuple("Attributes", "required optional")
Expand All @@ -58,7 +58,8 @@
"template_bucket_name": strategies.child_wins,
"template_key_value": strategies.child_wins,
"template_path": strategies.child_wins,
"template": strategies.child_wins
"template": strategies.child_wins,
"launch_action": strategies.child_wins
}

STACK_GROUP_CONFIG_ATTRIBUTES = ConfigAttributes(
Expand Down Expand Up @@ -566,6 +567,7 @@ def _construct_stack(self, rel_path, stack_group_config=None):
notifications=config.get("notifications"),
on_failure=config.get("on_failure"),
stack_timeout=config.get("stack_timeout", 0),
launch_action=LaunchAction[config.get('launch_action', 'include')],
stack_group_config=parsed_stack_group_config
)

Expand Down
20 changes: 13 additions & 7 deletions sceptre/plan/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from sceptre.exceptions import UnknownStackChangeSetStatusError
from sceptre.exceptions import UnknownStackStatusError
from sceptre.hooks import add_stack_hooks
from sceptre.stack import LaunchAction
from sceptre.stack_status import StackChangeSetStatus
from sceptre.stack_status import StackStatus

Expand Down Expand Up @@ -175,7 +176,7 @@ def cancel_stack_update(self):
return self._wait_for_completion()

@add_stack_hooks
def launch(self):
def launch(self) -> StackStatus:
"""
Launches the Stack.

Expand All @@ -185,10 +186,15 @@ def launch(self):
performed, launch exits gracefully.

:returns: The Stack's status.
:rtype: sceptre.stack_status.StackStatus
"""
self._protect_execution()
self.logger.info("%s - Launching Stack", self.stack.name)

if self.stack.launch_action == LaunchAction.exclude:
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved
self.logger.info(f'{self.stack.name} is excluded - Deleting Stack (if it exists)')
return self.delete()

self.logger.info("f{self.stack.name} - Launching Stack")
zaro0508 marked this conversation as resolved.
Show resolved Hide resolved

try:
existing_status = self._get_status()
except StackDoesNotExistError:
Expand Down Expand Up @@ -1008,10 +1014,10 @@ def drift_detect(self) -> Dict[str, str]:
Show stack drift for a running stack.

:returns: The stack drift detection status.
If the stack does not exist, we return a detection and
stack drift status of STACK_DOES_NOT_EXIST.
If drift detection times out after 5 minutes, we return
TIMED_OUT.
If the stack does not exist, we return a detection and
stack drift status of STACK_DOES_NOT_EXIST.
If drift detection times out after 5 minutes, we return
TIMED_OUT.
"""
try:
self._get_status()
Expand Down
4 changes: 2 additions & 2 deletions sceptre/plan/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ def __init__(self, command, launch_order):

def execute(self, *args):
"""
Execute is responsible executing the sets of Stacks in `launch_order`
Execute is responsible executing the sets of Stacks in ``launch_order``
concurrently, in the correct order.

:param *args: Any arguments that should be passed through to the\
:param args: Any arguments that should be passed through to the
StackAction being called.
"""
responses = {}
Expand Down
Loading