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
3 changes: 1 addition & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ services:
image: torero-local
container_name: torero
ports:
- "2222:22"
- "8001:8001"
- "8080:8080"
volumes:
Expand All @@ -25,4 +24,4 @@ services:
timeout: 10s
retries: 3
start_period: 5s
...
...
1 change: 1 addition & 0 deletions img/execute.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 122 additions & 0 deletions opt/torero-helpers/enhanced_input_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Enhanced input resolver with custom path support."""

from pathlib import Path
from typing import Optional, Dict
import os

class EnhancedPathResolver:
"""Resolves input file paths with support for multiple base directories."""

# Define path shortcuts
PATH_SHORTCUTS = {
"@": "/home/admin/data", # Default data directory
"@data": "/home/admin/data", # Explicit data directory
"@inputs": "/home/admin/data/inputs", # Direct to inputs
"@states": "/home/admin/data/states", # Direct to states
"@configs": "/home/admin/data/configs", # Direct to configs
"@repo": "/home/admin/repos", # Repository directory
"@temp": "/tmp", # Temporary files
"@project": os.environ.get("PROJECT_DIR", "/home/admin/project"), # Project-specific
"@workspace": os.environ.get("WORKSPACE", "/home/admin/workspace"), # Workspace
}

@classmethod
def resolve_path(cls, input_path: str) -> Path:
"""Resolve a path string to an absolute Path object.

Args:
input_path: Path string that may contain shortcuts

Returns:
Resolved absolute Path object

Examples:
"@/inputs/vpc.yaml" -> /home/admin/data/inputs/vpc.yaml
"@inputs/vpc.yaml" -> /home/admin/data/inputs/vpc.yaml
"@repo/showtime/config.yaml" -> /home/admin/repos/showtime/config.yaml
"@temp/dynamic.tfvars" -> /tmp/dynamic.tfvars
"/absolute/path/file.yaml" -> /absolute/path/file.yaml
"./relative/file.yaml" -> <cwd>/relative/file.yaml
"""

# Handle shortcuts
if input_path.startswith("@"):
# Check for specific shortcuts
for shortcut, base_path in cls.PATH_SHORTCUTS.items():
if input_path.startswith(shortcut + "/"):
# Replace shortcut with base path
relative_part = input_path[len(shortcut) + 1:]
return Path(base_path) / relative_part
elif input_path == shortcut:
# Just the shortcut itself
return Path(base_path)

# Default @ handling (backward compatibility)
if input_path.startswith("@/"):
return Path("/home/admin/data") / input_path[2:]
else:
# @filename.yaml -> /home/admin/data/filename.yaml
return Path("/home/admin/data") / input_path[1:]

# Handle environment variable expansion
if "$" in input_path:
input_path = os.path.expandvars(input_path)

# Handle home directory expansion
if input_path.startswith("~"):
return Path(input_path).expanduser().resolve()

# Handle absolute and relative paths
path = Path(input_path)
if path.is_absolute():
return path
else:
# Relative to current working directory
return Path.cwd() / path

@classmethod
def add_custom_shortcut(cls, shortcut: str, base_path: str):
"""Add a custom path shortcut.

Args:
shortcut: The shortcut string (should start with @)
base_path: The base path it resolves to
"""
if not shortcut.startswith("@"):
shortcut = "@" + shortcut
cls.PATH_SHORTCUTS[shortcut] = base_path


# Example usage functions
def demonstrate_path_resolution():
"""Show examples of path resolution."""

examples = [
"@inputs/vpc.yaml",
"@states/terraform.tfstate",
"@repo/showtime/config.yaml",
"@temp/generated.tfvars",
"@project/settings.yaml",
"/absolute/path/to/file.yaml",
"./relative/path/file.json",
"~/user/configs/personal.yaml",
"$HOME/configs/env.tfvars",
]

resolver = EnhancedPathResolver()

print("Path Resolution Examples:")
print("-" * 60)

for example in examples:
resolved = resolver.resolve_path(example)
print(f"{example:<35} -> {resolved}")

# Add custom shortcut
resolver.add_custom_shortcut("@custom", "/opt/custom/configs")
custom_path = resolver.resolve_path("@custom/special.yaml")
print(f"{'@custom/special.yaml':<35} -> {custom_path}")


if __name__ == "__main__":
demonstrate_path_resolution()
173 changes: 173 additions & 0 deletions opt/torero-helpers/torero-wrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""
Wrapper for torero CLI that adds support for --input-file parameter.
Translates input file contents to appropriate --set and --set-secret flags.
"""

import sys
import os
import json
import yaml
import subprocess
import argparse
from pathlib import Path
from typing import Dict, List, Any

def resolve_path(path: str) -> Path:
"""Resolve @ notation and relative paths."""

if path.startswith('@'):

# @ notation relative to /home/admin/data
return Path('/home/admin/data') / path[1:]
return Path(path).resolve()

def parse_tfvars(content: str) -> Dict[str, Any]:
"""Parse Terraform .tfvars format."""

variables = {}
lines = content.strip().split('\n')

current_key = None
current_value = []
in_multiline = False

for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue

if '=' in line and not in_multiline:
parts = line.split('=', 1)
key = parts[0].strip()
value = parts[1].strip()

# Handle multiline values (like objects/maps)
if value.startswith('{') and not value.endswith('}'):
current_key = key
current_value = [value]
in_multiline = True
else:

# Simple value
if value.startswith('"') and value.endswith('"'):
value = value[1:-1] # Remove quotes
variables[key] = value
elif in_multiline:
current_value.append(line)
if line.endswith('}'):

# End of multiline value
variables[current_key] = ' '.join(current_value)
current_key = None
current_value = []
in_multiline = False

return {'variables': variables}

def load_input_file(file_path: Path) -> Dict[str, Any]:
"""Load and parse input file based on extension."""

if not file_path.exists():
print(f"Error: Input file not found: {file_path}", file=sys.stderr)
sys.exit(1)

suffix = file_path.suffix.lower()

try:
if suffix in ['.yaml', '.yml']:
with open(file_path, 'r') as f:
return yaml.safe_load(f) or {}
elif suffix == '.json':
with open(file_path, 'r') as f:
return json.load(f)
elif suffix == '.tfvars':
with open(file_path, 'r') as f:
return parse_tfvars(f.read())
else:
print(f"Error: Unsupported input file format: {suffix}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error parsing input file: {e}", file=sys.stderr)
sys.exit(1)

def flatten_value(value: Any) -> str:
"""Convert complex values to string format for CLI."""

if isinstance(value, (dict, list)):
return json.dumps(value, separators=(',', ':'))
elif isinstance(value, bool):
return 'true' if value else 'false'
elif value is None:
return ''
else:
return str(value)

def inputs_to_cli_args(inputs: Dict[str, Any]) -> List[str]:
"""Convert inputs dictionary to CLI arguments."""

args = []

# Handle variables
variables = inputs.get('variables', {})
for key, value in variables.items():
# torero uses --set for variables
flat_value = flatten_value(value)
args.extend(['--set', f'{key}={flat_value}'])

# Handle secrets
secrets = inputs.get('secrets', [])
for secret in secrets:
# torero uses --set-secret for secrets
args.extend(['--set-secret', secret])

# Handle state file
if 'files' in inputs and 'state_file' in inputs['files']:
state_path = resolve_path(inputs['files']['state_file'])
args.extend(['--state', f'@{state_path}'])

return args

def main():
"""Main wrapper function."""

# Parse arguments to find --input-file
args = sys.argv[1:]

# Look for --input-file in arguments
input_file_idx = None
for i, arg in enumerate(args):
if arg == '--input-file' and i + 1 < len(args):
input_file_idx = i
break

if input_file_idx is not None:
# Extract input file path
input_file_path = args[input_file_idx + 1]

# Remove --input-file and its value from args
del args[input_file_idx:input_file_idx + 2]

# Load and parse input file
resolved_path = resolve_path(input_file_path)
inputs = load_input_file(resolved_path)

# Convert to CLI arguments
cli_args = inputs_to_cli_args(inputs)

# Insert CLI arguments at the position where --input-file was
args[input_file_idx:input_file_idx] = cli_args

# Execute real torero with modified arguments
torero_cmd = ['torero'] + args

# Debug output if verbose
if '--verbose' in args or os.environ.get('TORERO_DEBUG'):
print(f"Executing: {' '.join(torero_cmd)}", file=sys.stderr)

# Run torero
result = subprocess.run(torero_cmd, capture_output=False)
sys.exit(result.returncode)

if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions opt/torero-mcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies = [
"pydantic>=2.0.0",
"pyyaml>=6.0",
"click>=8.0.0",
"jsonschema>=4.20.0",
]

[project.optional-dependencies]
Expand Down
Loading