Compare commits

..

No commits in common. "main" and "v0.0.2" have entirely different histories.
main ... v0.0.2

5 changed files with 103 additions and 124 deletions

View File

@ -55,82 +55,82 @@ jobs:
name: python-package-distributions name: python-package-distributions
path: dist/ path: dist/
- name: Publish distribution 📦 to PyPI - name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
password: ${{ secrets.PYPI_API_TOKEN }} password: ${{ secrets.PYPI_API_TOKEN }}
# github-release: github-release:
# name: >- name: >-
# Sign the Python 🐍 distribution 📦 with Sigstore Sign the Python 🐍 distribution 📦 with Sigstore
# and upload them to GitHub Release and upload them to GitHub Release
# needs: needs:
# - publish-to-pypi - publish-to-pypi
# runs-on: ubuntu-latest runs-on: ubuntu-latest
# permissions: permissions:
# contents: write # IMPORTANT: mandatory for making GitHub Releases contents: write # IMPORTANT: mandatory for making GitHub Releases
# id-token: write # IMPORTANT: mandatory for sigstore id-token: write # IMPORTANT: mandatory for sigstore
# env: env:
# RUNNER_TOOL_CACHE: /toolcache # https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd RUNNER_TOOL_CACHE: /toolcache # https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd
# AGENT_TOOLSDIRECTORY: /toolcache # https://github.com/actions/setup-python/issues/824 AGENT_TOOLSDIRECTORY: /toolcache # https://github.com/actions/setup-python/issues/824
# steps: steps:
# - name: Download all the dists - name: Download all the dists
# uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
# with: with:
# name: python-package-distributions name: python-package-distributions
# path: dist/ path: dist/
# - name: Sign the dists with Sigstore - name: Sign the dists with Sigstore
# uses: sigstore/gh-action-sigstore-python@v2.1.1 uses: sigstore/gh-action-sigstore-python@v2.1.1
# with: with:
# inputs: >- inputs: >-
# ./dist/*.tar.gz ./dist/*.tar.gz
# ./dist/*.whl ./dist/*.whl
# - name: Create GitHub Release - name: Create GitHub Release
# env: env:
# GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
# run: >- run: >-
# gh release create gh release create
# '${{ github.ref_name }}' '${{ github.ref_name }}'
# --repo '${{ github.repository }}' --repo '${{ github.repository }}'
# --notes "" --notes ""
# - name: Upload artifact signatures to GitHub Release - name: Upload artifact signatures to GitHub Release
# env: env:
# GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
# # Upload to GitHub Release using the `gh` CLI. # Upload to GitHub Release using the `gh` CLI.
# # `dist/` contains the built packages, and the # `dist/` contains the built packages, and the
# # sigstore-produced signatures and certificates. # sigstore-produced signatures and certificates.
# run: >- run: >-
# gh release upload gh release upload
# '${{ github.ref_name }}' dist/** '${{ github.ref_name }}' dist/**
# --repo '${{ github.repository }}' --repo '${{ github.repository }}'
# publish-to-testpypi: publish-to-testpypi:
# name: Publish Python 🐍 distribution 📦 to TestPyPI name: Publish Python 🐍 distribution 📦 to TestPyPI
# needs: needs:
# - build - build
# runs-on: ubuntu-latest runs-on: ubuntu-latest
# environment: environment:
# name: testpypi name: testpypi
# url: https://test.pypi.org/p/goat_monitor url: https://test.pypi.org/p/goat_monitor
# permissions: permissions:
# id-token: write # IMPORTANT: mandatory for trusted publishing id-token: write # IMPORTANT: mandatory for trusted publishing
# env: env:
# RUNNER_TOOL_CACHE: /toolcache # https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd RUNNER_TOOL_CACHE: /toolcache # https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd
# AGENT_TOOLSDIRECTORY: /toolcache # https://github.com/actions/setup-python/issues/824 AGENT_TOOLSDIRECTORY: /toolcache # https://github.com/actions/setup-python/issues/824
# steps: steps:
# - name: Download all the dists - name: Download all the dists
# uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
# with: with:
# name: python-package-distributions name: python-package-distributions
# path: dist/ path: dist/
# - name: Publish distribution 📦 to TestPyPI - name: Publish distribution 📦 to TestPyPI
# uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1
# with: with:
# password: ${{ secrets.TEST_PYPI_API_TOKEN }} password: ${{ secrets.TEST_PYPI_API_TOKEN }}
# repository-url: https://test.pypi.org/legacy/ repository-url: https://test.pypi.org/legacy/

10
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "4c61c72e95ca800fd43f3c1ba0fdbb9d0b96e8684edff51ae5c2f74a693ac20e" "sha256": "13a000de8cd5f6c3d270d059f8df15653008a32b9daa31494cc26bd4c80dd8d0"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -37,18 +37,16 @@
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
], ],
"index": "pypi",
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==8.1.7" "version": "==8.1.7"
}, },
"goat-monitor": {
"editable": true,
"file": "."
},
"gotify": { "gotify": {
"hashes": [ "hashes": [
"sha256:184faec76de78279c5acd6e21dfefd11223d74a816b607f1063ac22df40641b2", "sha256:184faec76de78279c5acd6e21dfefd11223d74a816b607f1063ac22df40641b2",
"sha256:47bdc0332143cd5c251e284ffa487467429c624a1d40aefb013774f6f4dd4b7d" "sha256:47bdc0332143cd5c251e284ffa487467429c624a1d40aefb013774f6f4dd4b7d"
], ],
"index": "pypi",
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==0.6.0" "version": "==0.6.0"
}, },
@ -140,6 +138,7 @@
"sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521", "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521",
"sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b" "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"
], ],
"index": "pypi",
"markers": "python_version >= '3.10'", "markers": "python_version >= '3.10'",
"version": "==2.1.1" "version": "==2.1.1"
}, },
@ -156,6 +155,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
], ],
"index": "pypi",
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2" "version": "==0.10.2"
} }

View File

@ -6,17 +6,16 @@ Yes, I know Gotify doesn't have "goat" in its name but it sounds like it.
## Configuration ## Configuration
Configuration lives in a TOML file which should have (at a minimum) the following:
```toml ```toml
server = "https://gotify.example.com" server = "https://gotify.example.com"
app_token = "app_token_from_gotify" app_token = "app_token_from_gotify"
``` ```
## Usage ## Usage
``` ```
goat_monitor --config ./config.toml --retries 3 -- <COMMAND TO MONITOR> goat_monitor --config ./config.toml --retries -1 -- <COMMAND TO MONITOR>
``` ```
The command can be any shell command including arbitrarily many options / arguments. The command can be any shell command including arbitrarily many options / arguments.

View File

@ -9,7 +9,7 @@ import gotify
import numpy as np import numpy as np
import toml import toml
from goat_monitor._version import __version__ # type: ignore from goat_monitor._version import __version__
# %% commands # %% commands
@ -17,8 +17,7 @@ from goat_monitor._version import __version__ # type: ignore
@click.argument("command", nargs=-1, required=False, type=str) @click.argument("command", nargs=-1, required=False, type=str)
@click.option( @click.option(
"--config", "--config",
# default="~/.config/goat_monitor.toml", default="~/.config/goat_monitor.toml",
required=True,
type=click.Path(path_type=Path), type=click.Path(path_type=Path),
help="Use a .toml configuration file", help="Use a .toml configuration file",
) )
@ -34,13 +33,7 @@ from goat_monitor._version import __version__ # type: ignore
default=False, default=False,
help="Print version and exit", help="Print version and exit",
) )
@click.option( def wrap(command: List[str], config: Path, retries: int, version):
"--dry-run",
is_flag=True,
default=False,
help="Run command without sending any notifications",
)
def wrap(command: List[str], config: Path, retries: int, version: bool, dry_run: bool):
"""Wrap an arbitrary command with gotify notifications""" """Wrap an arbitrary command with gotify notifications"""
if version: if version:
@ -52,42 +45,35 @@ def wrap(command: List[str], config: Path, retries: int, version: bool, dry_run:
settings = toml.load(f) settings = toml.load(f)
# gotify configuration # gotify configuration
if not dry_run: url = settings["server"]
url = settings["server"] app_token = settings["app_token"]
app_token = settings["app_token"] with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection:
with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection: # this is just to test configuration.
# this is just to test configuration. # I don't know how long a gotify connection will stay up so rather than thinking about it
# I don't know how long a gotify connection will stay up so rather than thinking about it # I'm just creating a new one whenever I need to send a message
# I'm just creating a new one whenever I need to send a message pass
pass
if retries == -1: if retries == -1:
retries = np.inf retries = np.inf
if retries < 0: if retries < 0:
raise ValueError("Invalid number of retries specified") raise ValueError("Invalid number of retries specified")
attempt = 0 for attempt in range(retries + 1):
while attempt <= retries:
# run the command # run the command
with subprocess.Popen( result = subprocess.run(
" ".join(command), " ".join(command),
shell=True, shell=True,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
) as proc: encoding="utf-8",
stdout = "" )
while True:
c = proc.stdout.read(1)
if c == b"":
break
s = c.decode("utf-8")
stdout += s
print(s, end="", flush=True)
return_code = proc.wait()
if return_code: # TODO: print lines real time as the subprocess runs
print(result.stdout, end="")
if result.returncode:
# failed # failed
title = f"Command failed with exit code {return_code}" title = f"Command failed with exit code {result.returncode}"
if attempt < retries: if attempt < retries:
title += f" - Retrying (attempt {attempt+1}/{retries})" title += f" - Retrying (attempt {attempt+1}/{retries})"
else: else:
@ -97,26 +83,20 @@ def wrap(command: List[str], config: Path, retries: int, version: bool, dry_run:
title = "Command succeeded" title = "Command succeeded"
MAX_LINES = 20 MAX_LINES = 20
lines = stdout.splitlines() lines = result.stdout.splitlines()
truncated = False
if len(lines) > MAX_LINES: if len(lines) > MAX_LINES:
truncated = True
lines = lines[-MAX_LINES:] lines = lines[-MAX_LINES:]
message = ( message = (
"Command:\n" + " ".join(command) + f'\n\nResult{ " (truncated)" if truncated else ""}:\n' + "\n".join(lines) "Command:\n"
+ " ".join(command)
+ f'\n\nResult{ " (truncated)" if len(lines) > MAX_LINES else ""}:\n'
+ "\n".join(lines)
) )
if not dry_run: with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection:
with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection: gotify_connection.create_message(message=message, title=title)
gotify_connection.create_message(message=message, title=title)
if not return_code: sys.exit(result.returncode)
# only retry on failure
break
attempt += 1
sys.exit(return_code)
# %% main # %% main

View File

@ -7,7 +7,7 @@ name = "goat_monitor"
authors = [{ name = "Brendan Haines", email = "brendan.haines@gmail.com" }] authors = [{ name = "Brendan Haines", email = "brendan.haines@gmail.com" }]
description = "Remote monitoring of anything" description = "Remote monitoring of anything"
readme = "README.md" readme = "README.md"
requires-python = ">=3" requires-python = ">=3.11" # might work with earlier versions, I haven't tried
# keywords = ["one", "two"] # keywords = ["one", "two"]
license = { text = "MIT License" } license = { text = "MIT License" }
classifiers = ["Programming Language :: Python :: 3"] classifiers = ["Programming Language :: Python :: 3"]