Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ engine:
- `plan`, `apply`, `fmt` and `validate` also accept `--strategy parallel` to run independent resource
groups concurrently instead.
- Use `--group-concurrency <n>` to cap how many resource groups run at once under `--strategy parallel`
(defaults to a sane pool size if unset). This is distinct from `--parallelism`, which limits terraform's
(defaults to the number of CPU cores if unset). This is distinct from `--parallelism`, which limits terraform's
own resource-level concurrency inside a single invocation.
- Use `--fail-at-end` to let unaffected resource groups keep running after a failure instead of stopping
the whole run immediately.
Expand Down
10 changes: 2 additions & 8 deletions src/terranova/commands/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
complete_resource_path,
execute_tasks,
expand_with_dependents,
group_concurrency_option,
include_dependents_option,
parse_execution_plan,
read_manifests_and_waves,
Expand Down Expand Up @@ -125,14 +126,7 @@ def run(self) -> None:
type=click.Choice(["sequential", "parallel"], case_sensitive=False),
default="sequential",
)
@click.option(
"--group-concurrency",
help="With `--strategy parallel`, the maximum number of resource groups run "
+ "concurrently within a wave. Distinct from terraform's own per-process "
+ "resource concurrency. Defaults to a sane pool size if unset.",
type=int,
default=None,
)
@group_concurrency_option
@click.pass_obj
def apply(
ctx: AppContext,
Expand Down
4 changes: 2 additions & 2 deletions src/terranova/commands/fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
TerraformTask,
complete_resource_path,
execute_tasks,
flat_group_concurrency_option,
flat_strategy_option,
flat_wave,
group_concurrency_option,
resource_dirs,
)
from terranova.executor import ResourceGroupTask
Expand All @@ -47,7 +47,7 @@ def run(self) -> None:
@click.command("fmt")
@click.argument("path", type=str, required=False, shell_complete=complete_resource_path)
@flat_strategy_option
@flat_group_concurrency_option
@group_concurrency_option
@click.pass_obj
def fmt(
ctx: AppContext, path: str | None, strategy: str, group_concurrency: int | None
Expand Down
7 changes: 4 additions & 3 deletions src/terranova/commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,15 @@
ordering concern (`fmt`, `validate` - see `terranova.commands.execution.flat_wave`).
`plan`/`apply` define their own, worded around dependency order."""

flat_group_concurrency_option = click.option(
group_concurrency_option = click.option(
"--group-concurrency",
help="With `--strategy parallel`, the maximum number of resource groups run "
+ "concurrently. Defaults to a sane pool size if unset.",
+ "concurrently. Distinct from terraform's own per-process resource "
+ "concurrency. Defaults to the number of CPU cores.",
type=int,
default=None,
)
"""Shared `--group-concurrency` option, paired with `flat_strategy_option`."""
"""Shared `--group-concurrency` option, used by `plan`, `apply`, `fmt` and `validate`."""


class SelectorType(click.ParamType[Selector]):
Expand Down
11 changes: 2 additions & 9 deletions src/terranova/commands/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
complete_resource_path,
execute_tasks,
expand_with_dependents,
group_concurrency_option,
include_dependents_option,
read_manifests_and_waves,
resolve_resource_dirs,
Expand Down Expand Up @@ -164,15 +165,7 @@ def run(self) -> None:
type=click.Choice(["sequential", "parallel"], case_sensitive=False),
default="sequential",
)
@click.option(
"--group-concurrency",
help="With `--strategy parallel`, the maximum number of resource groups run "
+ "concurrently within a wave. Distinct from `--parallelism`, which limits "
+ "resource-level concurrency inside a single terraform invocation. "
+ "Defaults to a sane pool size if unset.",
type=int,
default=None,
)
@group_concurrency_option
@click.pass_obj
def plan(
ctx: AppContext,
Expand Down
4 changes: 2 additions & 2 deletions src/terranova/commands/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
complete_resource_path,
discover_resources,
execute_tasks,
flat_group_concurrency_option,
flat_strategy_option,
flat_wave,
group_concurrency_option,
resource_dirs,
)
from terranova.exceptions import InvalidResourcesError
Expand Down Expand Up @@ -74,7 +74,7 @@ def run(self) -> None:
is_flag=True,
)
@flat_strategy_option
@flat_group_concurrency_option
@group_concurrency_option
@click.pass_obj
def validate(
ctx: AppContext,
Expand Down
16 changes: 15 additions & 1 deletion src/terranova/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
from abc import ABC, abstractmethod
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
from dataclasses import dataclass
Expand All @@ -24,6 +25,17 @@
from terranova.process import ErrorReturnCode


def default_group_concurrency() -> int:
"""
Default cap on resource groups run concurrently under `--strategy parallel`.

Each group runs a terraform/tofu process, which is CPU-heavy, so one slot
per usable core (honoring CPU affinity) is a sensible ceiling. Falls back
to 1 when the core count is unknown.
"""
return os.process_cpu_count() or 1


class ResourceGroupTask(ABC):
"""One resource group's unit of work for an executor. Subclasses implement `run()`."""

Expand Down Expand Up @@ -164,7 +176,9 @@ def __init__(
observer: ExecutorObserver | None = None,
) -> None:
"""Init the parallel executor."""
self.__max_workers = max_workers
self.__max_workers = (
max_workers if max_workers is not None else default_group_concurrency()
)
self.__observer: ExecutorObserver = (
observer if observer is not None else _NoopObserver()
)
Expand Down
Loading