Skip to content

Commit

Permalink
Update clean command (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmwant authored Jul 29, 2024
1 parent 19a74db commit f6cdc69
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 192 deletions.
23 changes: 21 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,24 @@ Available sections are:

## [Unreleased]


## [v0.4.0]

### Added

- Allow to use current active shell to run commands instead of the default one.
- New command `hap cleanall` to delete all finished haps (both successful and failed)


### Changed

- Command `hap clean` now accepts `--all` parameter to cleanup both successful and failed haps from the list


## [v0.3.0]

### Fixed

- Fix [issue](https://github.com/bmwant/hapless/issues/30) when displaying runtime for the corrupted hap


## [v0.2.2]
Expand Down Expand Up @@ -121,7 +136,11 @@ Available sections are:

- Initial release.

[Unreleased]: https://github.com/bmwant/hapless/compare/v0.2.0...HEAD
[Unreleased]: https://github.com/bmwant/hapless/compare/v0.4.0...HEAD

[v0.4.0]: https://github.com/bmwant/hapless/compare/v0.3.0...v0.4.0

[v0.3.0]: https://github.com/bmwant/hapless/compare/v0.2.0...v0.3.0

[v0.2.0]: https://github.com/bmwant/hapless/compare/v0.1.10...v0.2.0

Expand Down
63 changes: 42 additions & 21 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,88 +9,109 @@
➡️ Run a simple script

```bash
$ hap run ./examples/script.sh
$ hap run python ./examples/fast.py
hap run ./examples/script.sh
hap run python ./examples/fast.py
```

➡️ Run script accepting arguments

```bash
$ hap run -- python ./examples/show_details.py -v --name details --count=5
hap run -- python ./examples/show_details.py -v --name details --count=5
```

Use `--` delimiter in order to separate target script and its arguments. Otherwise arguments will be interpreted as a keys for `hap` executable.

> NOTE: as of version `0.4.0` specifying double dash is optional and required only if parameters for your target command match parameters of `hap run` command itself
```bash
# wouldn't work, as run has its own `--help` option
hap run python --help

# will work as expected without `--` delimiter
hap run ./examples/show_details.py --flag --param=value
```

➡️ Check script for early failures right after launch

```bash
$ hap run --check python ./examples/fail_fast.py
hap run --check python ./examples/fail_fast.py
```

### ✏️ Checking status

➡️ Show summary for all haps

```bash
$ hap
hap
# or
$ hap status # equivalent
$ hap show # same as above
hap status # equivalent
hap show # same as above
```

➡️ Check status of the specific hap

```bash
$ hap show [hap-alias]
hap show [hap-alias]
# or
$ hap status [hap-alias]
hap status [hap-alias]
```

➡️ Show detailed status for the hap (including environment variables)

```bash
$ hap show -v [hap-alias]
$ hap show --verbose [hap-alias] # same as above
hap show -v [hap-alias]
hap show --verbose [hap-alias] # same as above
# or
$ hap status -v [hap-alias]
$ hap status --verbose [hap-alias] # same as above
hap status -v [hap-alias]
hap status --verbose [hap-alias] # same as above
```

### ✏️ Checking logs

➡️ Print process logs to the console

```bash
$ hap logs [hap-alias]
hap logs [hap-alias]
```

➡️ Stream logs continuously to the console

```bash
$ hap logs -f [hap-alias]
hap logs -f [hap-alias]
# or
$ hap logs --follow [hap-alias]
hap logs --follow [hap-alias]
```

### ✏️ Other commands

➡️ Suspend (pause) a hap. Sends `SIGSTOP` signal to the process

```bash
$ hap pause [hap-alias]
hap pause [hap-alias]
# or
$ hap suspend [hap-alias]
hap suspend [hap-alias]
```

➡️ Resume paused hap. Sends `SIGCONT` signal to the suspended process

```bash
$ hap resume [hap-alias]
hap resume [hap-alias]
```

➡️ Send specific signal to the process by its code

```bash
$ hap signal [hap-alias] 9 # sends SIGKILL
$ hap signal [hap-alias] 15 # sends SIGTERM
hap signal [hap-alias] 9 # sends SIGKILL
hap signal [hap-alias] 15 # sends SIGTERM
```

- Remove haps from the list. Without any parameters removes only successfully finished haps (with `0` return code). Provide `--all` flag to remove failed haps as well. Used to make list more concise in case you have a lot of things running at once and you are not interested in results/error logs of completed ones.

```bash
hap clean

# Remove all finished haps (both successful and failed)
hap clean --all
# Same as above
hap cleanall
```
1 change: 1 addition & 0 deletions examples/show_details.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import os
import sys

Expand Down
52 changes: 38 additions & 14 deletions hapless/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ def cli(ctx, verbose):
_status(None, verbose=verbose)


@cli.command(short_help="Display information about haps")
@cli.command(short_help="Display information about haps.")
@click.argument("hap_alias", metavar="hap", required=False)
@click.option("-v", "--verbose", is_flag=True, default=False)
def status(hap_alias, verbose):
_status(hap_alias, verbose)


@cli.command(short_help="Same as a status")
@cli.command(short_help="Same as a status.")
@click.argument("hap_alias", metavar="hap", required=False)
@click.option("-v", "--verbose", is_flag=True, default=False)
def show(hap_alias, verbose):
Expand All @@ -61,7 +61,7 @@ def _status(hap_alias: Optional[str] = None, verbose: bool = False):
hapless.stats(haps, verbose=verbose)


@cli.command(short_help="Output logs for a hap")
@cli.command(short_help="Output logs for a hap.")
@click.argument("hap_alias", metavar="hap")
@click.option("-f", "--follow", is_flag=True, default=False)
@click.option("-e", "--stderr", is_flag=True, default=False)
Expand All @@ -70,16 +70,40 @@ def logs(hap_alias, follow, stderr):
hapless.logs(hap, stderr=stderr, follow=follow)


@cli.command(short_help="Remove finished haps")
@click.option("--skip-failed", is_flag=True, default=False)
def clean(skip_failed):
hapless.clean(skip_failed)
@cli.command(short_help="Remove successfully completed haps.")
@click.option(
"-a",
"--all",
"clean_all",
is_flag=True,
default=False,
help="Include failed haps for the removal.",
)
def clean(clean_all):
hapless.clean(clean_all=clean_all)


@cli.command(short_help="Execute background command as a hap")
@cli.command(short_help="Remove all finished haps, including failed ones.")
def cleanall():
hapless.clean(clean_all=True)


@cli.command(
short_help="Execute background command as a hap.",
context_settings=dict(
ignore_unknown_options=True,
),
)
@click.argument("cmd", nargs=-1)
@click.option("-n", "--name")
@click.option("--check", is_flag=True, default=False)
@click.option(
"-n", "--name", help="Provide your own alias for the hap instead of a default one."
)
@click.option(
"--check",
is_flag=True,
default=False,
help="Verify command launched does not fail immediately.",
)
def run(cmd, name, check):
hap = hapless.get_hap(name)
if hap is not None:
Expand All @@ -101,21 +125,21 @@ def run(cmd, name, check):
hapless.run(cmd_escaped, name=name, check=check)


@cli.command(short_help="Pause a specific hap")
@cli.command(short_help="Pause a specific hap.")
@click.argument("hap_alias", metavar="hap")
def pause(hap_alias):
hap = get_or_exit(hap_alias)
hapless.pause_hap(hap)


@cli.command(short_help="Resume execution of a paused hap")
@cli.command(short_help="Resume execution of a paused hap.")
@click.argument("hap_alias", metavar="hap")
def resume(hap_alias):
hap = get_or_exit(hap_alias)
hapless.resume_hap(hap)


@cli.command(short_help="Terminate a specific hap / all haps")
@cli.command(short_help="Terminate a specific hap / all haps.")
@click.argument("hap_alias", metavar="hap", required=False)
@click.option("-a", "--all", "killall", is_flag=True, default=False)
def kill(hap_alias, killall):
Expand All @@ -135,7 +159,7 @@ def kill(hap_alias, killall):
hapless.kill([hap])


@cli.command(short_help="Send an arbitrary signal to a hap")
@cli.command(short_help="Send an arbitrary signal to a hap.")
@click.argument("hap_alias", metavar="hap")
@click.argument("signal", callback=validate_signal, metavar="signal-code")
def signal(hap_alias, signal):
Expand Down
33 changes: 17 additions & 16 deletions hapless/hap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import string
import time
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Dict, Optional

Expand All @@ -19,14 +20,15 @@
from hapless import config
from hapless.utils import allow_missing, get_mtime, logger

"""
active
* paused
* running
finished
* finished(failed) non-zero rc
* finished(success) zero rc
"""

# TODO: add unbound status for the hap without associated pid
class Status(str, Enum):
# Active statuses
PAUSED = "paused"
RUNNING = "running"
# Finished statuses
FAILED = "failed"
SUCCESS = "success"


class Hap(object):
Expand Down Expand Up @@ -95,15 +97,15 @@ def _set_env(self):
with open(self._env_file, "w") as env_file:
env_file.write(json.dumps(environ))

def attach(self, pid: int):
def bind(self, pid: int):
"""
Associate hap object with existing process by pid
"""
try:
self._set_pid(pid)
self._set_env()
except (RuntimeError, psutil.AccessDenied) as e:
logger.error(f"Cannot attached due to {e}")
logger.error(f"Cannot bind due to {e}")

@staticmethod
def get_random_name(length: int = 6):
Expand All @@ -115,19 +117,18 @@ def get_random_name(length: int = 6):
)

# TODO: add extended status to show panel proc.status()
# TODO: return enum entries instead
@property
def status(self) -> str:
def status(self) -> Status:
proc = self.proc
if proc is not None:
if proc.status() == psutil.STATUS_STOPPED:
return "paused"
return "running"
return Status.PAUSED
return Status.RUNNING

if self.rc != 0:
return "failed"
return Status.FAILED

return "success"
return Status.SUCCESS

@cached_property
def proc(self):
Expand Down
14 changes: 7 additions & 7 deletions hapless/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from rich.text import Text

from hapless import config
from hapless.hap import Hap
from hapless.hap import Hap, Status
from hapless.utils import kill_proc_tree, logger, wait_created

console = Console(highlight=False)
Expand Down Expand Up @@ -225,7 +225,7 @@ def run_hap(self, hap: Hap):

pid = proc.pid
logger.debug(f"Attaching hap {hap} to pid {pid}")
hap.attach(pid)
hap.bind(pid)

retcode = proc.wait()

Expand Down Expand Up @@ -287,11 +287,11 @@ def logs(self, hap: Hap, stderr: bool = False, follow: bool = False):
else:
return subprocess.run(["cat", filepath])

def clean(self, skip_failed: bool = False):
def to_clean(hap):
if hap.rc is not None:
return hap.rc == 0 or not skip_failed
return False
def clean(self, clean_all: bool = False):
def to_clean(hap: Hap) -> bool:
return hap.status == Status.SUCCESS or (
hap.status == Status.FAILED and clean_all
)

haps = list(filter(to_clean, self.get_haps()))
for hap in haps:
Expand Down
Loading

0 comments on commit f6cdc69

Please sign in to comment.