Skip to content

Commit

Permalink
Merge pull request #121 from seqeralabs/114-support-stdin
Browse files Browse the repository at this point in the history
feat: add support for stdin to cli
  • Loading branch information
ejseqera authored Apr 25, 2024
2 parents 17ed8e4 + 7b60445 commit 8eb8e85
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 24 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ Use `--version` or `-v` to retrieve the current version of your seqerakit instal
```bash
seqerakit --version
```
### Input
`seqerakit` supports input through either file paths to YAMLs or directly from standard input (stdin).

#### Using File Path
```bash
seqerakit /path/to/file.yaml
```
#### Using stdin
```console
$ cat file.yaml | seqerakit -
```
See the [Defining your YAML file using CLI options](#defining-your-yaml-file-using-cli-options) section for guidance on formatting your input YAML file(s).

### Dryrun

Expand Down
14 changes: 8 additions & 6 deletions seqerakit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import logging
import sys

from pathlib import Path
from seqerakit import seqeraplatform, helper, overwrite
from seqerakit.seqeraplatform import ResourceExistsError, ResourceCreationError
from seqerakit import __version__
Expand Down Expand Up @@ -66,7 +65,6 @@ def parse_args(args=None):
yaml_processing = parser.add_argument_group("YAML Processing Options")
yaml_processing.add_argument(
"yaml",
type=Path,
nargs="*",
help="One or more YAML files with Seqera Platform resource definitions.",
)
Expand Down Expand Up @@ -154,10 +152,14 @@ def main(args=None):
return

if not options.yaml:
logging.error(
" No YAML(s) provided. Please provide atleast one YAML configuration file."
)
sys.exit(1)
if sys.stdin.isatty():
logging.error(
" No YAML(s) provided and no input from stdin. Please provide "
"at least one YAML configuration file or pipe input from stdin."
)
sys.exit(1)
else:
options.yaml = [sys.stdin]

# Parse CLI arguments into a list
cli_args_list = options.cli_args.split() if options.cli_args else []
Expand Down
57 changes: 41 additions & 16 deletions seqerakit/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""
import yaml
from seqerakit import utils
import sys


def parse_yaml_block(yaml_data, block_name):
Expand Down Expand Up @@ -57,24 +58,48 @@ def parse_all_yaml(file_paths, destroy=False):
# If multiple yamls, merge them into one dictionary
merged_data = {}

for file_path in file_paths:
with open(file_path, "r") as f:
data = yaml.safe_load(f)

# Check if the YAML file is empty or contains no valid data
if data is None or not data:
raise ValueError(
f" The file '{file_path}' is empty or does not contain valid data."
)
# Special handling for stdin represented by "-"
if not file_paths or "-" in file_paths:
# Read YAML directly from stdin
data = yaml.safe_load(sys.stdin)
if not data:
raise ValueError(
" The input from stdin is empty or does not contain valid YAML data."
)
merged_data.update(data)

for key, value in data.items():
for file_path in file_paths:
if file_path == "-":
continue
try:
with open(file_path, "r") as f:
data = yaml.safe_load(f)
if not data:
raise ValueError(
f" The file '{file_path}' is empty or "
"does not contain valid data."
)

for key, new_value in data.items():
if key in merged_data:
try:
merged_data[key].extend(value)
except AttributeError:
merged_data[key] = [merged_data[key], value]
if isinstance(new_value, list) and all(
isinstance(i, dict) for i in new_value
):
# Handle list of dictionaries & merge without duplication
existing_items = {
tuple(sorted(d.items())) for d in merged_data[key]
}
for item in new_value:
if tuple(sorted(item.items())) not in existing_items:
merged_data[key].append(item)
else:
# override if not list of dictionaries
merged_data[key] = new_value
else:
merged_data[key] = value
merged_data[key] = new_value
except FileNotFoundError:
print(f"Error: The file '{file_path}' was not found.")
sys.exit(1)

block_names = list(merged_data.keys())

Expand All @@ -95,7 +120,7 @@ def parse_all_yaml(file_paths, destroy=False):
"launch",
]

# Reverse the order of resources if destroy is True
# Reverse the order of resources to delete if destroy is True
if destroy:
resource_order = resource_order[:-1][::-1]

Expand Down
52 changes: 50 additions & 2 deletions tests/unit/test_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from unittest.mock import mock_open
from unittest.mock import patch, mock_open
from seqerakit import helper
import yaml
import pytest
from io import StringIO


# Fixture to mock a YAML file
Expand Down Expand Up @@ -48,8 +49,8 @@ def test_create_mock_organization_yaml(mock_yaml_file):
"overwrite": True,
}
]

file_path = mock_yaml_file(test_data)
print(f"debug - file_path: {file_path}")
result = helper.parse_all_yaml([file_path])

assert "organizations" in result
Expand Down Expand Up @@ -343,6 +344,53 @@ def test_empty_yaml_file(mock_yaml_file):
)


def test_empty_stdin_file():
# Prepare the mock to simulate empty stdin
with patch("sys.stdin", StringIO("")):
# Use '-' to indicate that stdin should be read
with pytest.raises(ValueError) as e:
helper.parse_all_yaml(["-"])
assert (
"The input from stdin is empty or does not contain valid YAML data."
in str(e.value)
)


def test_stdin_yaml_file():
# Prepare the mock to simulate stdin
yaml_data = """
compute-envs:
- name: test_computeenv
config-mode: forge
workspace: my_organization/my_workspace
credentials: my_credentials
type: aws-batch
wait: AVAILABLE
"""
with patch("sys.stdin", StringIO(yaml_data)):
result = helper.parse_all_yaml(["-"])

expected_block_output = [
{
"cmd_args": [
"aws-batch",
"forge",
"--name",
"test_computeenv",
"--workspace",
"my_organization/my_workspace",
"--credentials",
"my_credentials",
"--wait",
"AVAILABLE",
],
"overwrite": False,
}
]
assert "compute-envs" in result
assert result["compute-envs"] == expected_block_output


def test_error_type_yaml_file(mock_yaml_file):
test_data = {
"compute-envs": [
Expand Down

0 comments on commit 8eb8e85

Please sign in to comment.