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

Fix broken dryrun option #58

Merged
merged 5 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
61 changes: 48 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,54 @@ Create a Tower access token using the [Nextflow Tower](https://tower.nf/) web in
export TOWER_ACCESS_TOKEN=<your access token>
```

## Usage

Use the -h or --help parameter to list the available commands and their associated options:

```
twkit -h
```

### Dryrun

To print the commands that would executed with `tw` when using a YAML file, you can run `twkit` with the `--dryrun` flag:

```
twkit file.yaml --dryrun
```

### Recursively delete

Instead of adding or creating resources, you can recursively delete resources in your YAML file by specifying the `--delete` flag:

```
twkit file.yaml --delete
```

For example, if you have a YAML file that defines an Organization -> Workspace -> Team -> Credentials -> Compute Environment that have already been created, with the `--delete` flag, `twkit` will recursively delete the Compute Environment -> Credentials -> Team -> Workspace -> Organization.

### Using `tw` specific CLI options

The Tower CLI expects to connect to a Tower instance that is secured by a TLS certificate. If your Tower instance does not present a certificate, you will need to qualify and run your `tw` commands with the `--insecure` flag.

To use `tw` specific CLI options such as `--insecure` (you can find the full list of options by running `tw -h`), use the `--cli=` flag, followed by the options you would like to use enclosed in double quotes.

For example:

```
twkit file.yaml --cli="--insecure"
```

To use an SSL certificate that it is not accepted by the default Java certificate authorities and specify a custom `cacerts` store as accepted by the `tw` CLI, you can specify the `-Djavax.net.ssl.trustStore=/absolute/path/to/cacerts` option enclosed in double quotes to `twkit` as you would to `tw`, preceded by `--cli=`.
ejseqera marked this conversation as resolved.
Show resolved Hide resolved

For example:

```
twkit hello-world-config.yml --cli="-Djavax.net.ssl.trustStore=/absolute/path/to/cacerts"
```

<b>Note</b>: Use of `--verbose` option for the `tw` CLI is currently not supported by `twkit`. Supplying `--cli="--verbose"` will raise an error.

## Quick start

You must provide a YAML file that defines the options for each of the entities you would like to create in Nextflow Tower.
Expand All @@ -78,19 +126,6 @@ You will need to have an account on Nextflow Tower (see [Plans and pricing](http
twkit hello-world-config.yml
```

<b>Note</b>: The Tower CLI expects to connect to a Tower instance that is secured by a TLS certificate. If your Tower instance does not present a certificate, you will need to qualify and run your `tw` commands with `--cli` followed by the `--insecure` flag. For example:

```
twkit hello-world-config.yml --cli "--insecure"
```

To use an SSL certificate that it is not accepted by the default Java certificate authorities and specify a custom `cacerts` store as accepted by the `tw` CLI, you can specify the `-Djavax.net.ssl.trustStore=/absolute/path/to/cacerts` option enclosed in double quotes to `twkit` as you would to `tw`, preceded by `--cli` flag to indicate use of `tw` specific CLI options. For example:

```
twkit hello-world-config.yml --cli "-Djavax.net.ssl.trustStore=/absolute/path/to/cacerts"

```

3. Login to your Tower instance and check the Runs page in the appropriate Workspace for the pipeline you just launched!

### Launch via a Python script
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/test_tower.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,31 @@ def test_cli_args_inclusion(self, mock_subprocess):
for arg in self.cli_args:
self.assertIn(arg, called_command)

@patch("subprocess.Popen")
def test_cli_args_inclusion_ssl_certs(self, mock_subprocess):
# Add path to custom certs store to cli_args
self.cli_args.append("-Djavax.net.ssl.trustStore=/absolute/path/to/cacerts")

# Initialize Tower with cli_args
tower.Tower(cli_args=self.cli_args)

# Mock the stdout of the Popen process
mock_subprocess.return_value.communicate.return_value = (
json.dumps({"key": "value"}).encode(),
b"",
)

# Call a method
self.tw.pipelines("view", "--name", "pipeline_name", to_json=True)

# Extract the command used to call Popen
called_command = mock_subprocess.call_args[0][0]

# Check if the cli_args are present in the called command
self.assertIn(
"-Djavax.net.ssl.trustStore=/absolute/path/to/cacerts", called_command
)

def test_cli_args_exclusion_of_verbose(self): # TODO: remove this test once fixed
# Add --verbose to cli_args
verbose_args = ["--verbose"]
Expand All @@ -127,5 +152,18 @@ def test_cli_args_exclusion_of_verbose(self): # TODO: remove this test once fix
)


class TestKitOptions(unittest.TestCase):
def setUp(self):
self.dryrun_tw = tower.Tower(dryrun=True)

@patch("subprocess.Popen")
def test_dryrun_call(self, mock_subprocess):
# Run a method with dryrun=True
self.dryrun_tw.pipelines("view", "--name", "pipeline_name", to_json=True)

# Assert that subprocess.Popen is not called
mock_subprocess.assert_not_called()


if __name__ == "__main__":
unittest.main()
8 changes: 5 additions & 3 deletions twkit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ def parse_args():
"--cli",
dest="cli_args",
type=str,
nargs="+",
help="Additional arguments to pass to the Tower"
"CLI enclosed in double quotes (e.g. '--cli \"--insecure\"')",
"CLI enclosed in double quotes (e.g. '--cli=\"--insecure\"')",
)
return parser.parse_args()

Expand Down Expand Up @@ -112,9 +111,12 @@ def handle_block(self, block, args, destroy=False):
def main():
options = parse_args()
logging.basicConfig(level=options.log_level)
cli_args_list = options.cli_args[0].split()

# Parse CLI arguments into a list
cli_args_list = options.cli_args.split() if options.cli_args else []

tw = tower.Tower(cli_args=cli_args_list, dryrun=options.dryrun)

block_manager = BlockParser(
tw,
[
Expand Down
5 changes: 3 additions & 2 deletions twkit/tower.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def __call__(self, *args, **kwargs):
command = ["tw"]

# Prepend the CLI args if present
# command.extend(self.tw_instance.cli_args)
command = self.cmd.split()
command.extend(args)
return self.tw_instance._tw_run(command, **kwargs)
Expand Down Expand Up @@ -65,11 +64,13 @@ def _tw_run(self, cmd, *args, **kwargs):
command.append(f"--params-file={params_path}")

full_cmd = " ".join(arg if "$" in arg else shlex.quote(arg) for arg in command)
logging.debug(f" Running command: {full_cmd}\n")

# Skip if --dryrun
if self.dryrun:
logging.debug(f" DRYRUN: Running command: {full_cmd}\n")
return
else:
logging.debug(f" Running command: {full_cmd}\n")

# Run the command and return the stdout
process = subprocess.Popen(
Expand Down
3 changes: 3 additions & 0 deletions twkit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ def check_if_exists(json_data, namekey, namevalue):
Wrapper around find_key_value_in_dict() to validate that a resource was
created successfully in Tower by looking for the name and value.
"""
if not json_data:
return False

data = json.loads(json_data)
if find_key_value_in_dict(data, namekey, namevalue, return_key=None):
return True
Expand Down