Source code for kstlib.pipeline.steps.shell

"""Shell step executor for pipeline.

Executes shell commands via ``subprocess.run(shell=True)`` with
timeout, environment variable injection, and working directory support.

Multi-line commands are supported natively via YAML folded (``>-``) or
literal (``|``) block scalars. The resulting string is passed as-is to
``subprocess.run(shell=True)``, so loops, pipes, and redirections work.
"""

from __future__ import annotations

import logging

from kstlib.pipeline.models import StepConfig, StepResult, StepStatus
from kstlib.pipeline.steps._base import _run_subprocess
from kstlib.pipeline.steps._helpers import _sanitize_command

logger = logging.getLogger(__name__)


[docs] class ShellStep: """Execute a shell command as a pipeline step. Uses ``subprocess.run`` with ``shell=True`` to execute the command string. Supports environment variable injection, working directory, and timeout. Examples: >>> from kstlib.pipeline.models import StepConfig, StepType >>> step = ShellStep() >>> config = StepConfig( ... name="greet", ... type=StepType.SHELL, ... command="echo hello", ... ) >>> result = step.execute(config) # doctest: +SKIP >>> result.status # doctest: +SKIP <StepStatus.SUCCESS: 'success'> """
[docs] def execute( self, config: StepConfig, *, dry_run: bool = False, ) -> StepResult: """Execute a shell command. Args: config: Step configuration with command, env, timeout, etc. dry_run: If True, log the command without executing it. Returns: StepResult with captured stdout, stderr, return code, and duration. """ command = config.command or "" # Best-effort redaction for log output; the unredacted command is # still passed verbatim to subprocess for execution. safe_command = _sanitize_command(command) logger.debug("ShellStep '%s': command=%s", config.name, safe_command) if dry_run: logger.info("[DRY RUN] ShellStep '%s': %s", config.name, safe_command) return StepResult( name=config.name, status=StepStatus.SKIPPED, stdout=f"[dry-run] would execute: {safe_command}", ) return _run_subprocess(command, config, shell=True, log_tag="ShellStep") # noqa: S604
__all__ = [ "ShellStep", ]