Compare commits

..

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

7 changed files with 108 additions and 132 deletions

View File

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

2
.gitignore vendored
View File

@ -160,5 +160,3 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
**/_version.py

10
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "4c61c72e95ca800fd43f3c1ba0fdbb9d0b96e8684edff51ae5c2f74a693ac20e"
"sha256": "13a000de8cd5f6c3d270d059f8df15653008a32b9daa31494cc26bd4c80dd8d0"
},
"pipfile-spec": 6,
"requires": {
@ -37,18 +37,16 @@
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==8.1.7"
},
"goat-monitor": {
"editable": true,
"file": "."
},
"gotify": {
"hashes": [
"sha256:184faec76de78279c5acd6e21dfefd11223d74a816b607f1063ac22df40641b2",
"sha256:47bdc0332143cd5c251e284ffa487467429c624a1d40aefb013774f6f4dd4b7d"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==0.6.0"
},
@ -140,6 +138,7 @@
"sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521",
"sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
"version": "==2.1.1"
},
@ -156,6 +155,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"index": "pypi",
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"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 lives in a TOML file which should have (at a minimum) the following:
```toml
server = "https://gotify.example.com"
app_token = "app_token_from_gotify"
```
## 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.

1
goat_monitor/_version.py Normal file
View File

@ -0,0 +1 @@
__version__ = "editable"

View File

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

View File

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