Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
933c488444 | |||
0242b4f541 | |||
0954c2322a | |||
8c1e9751af | |||
da2c453430 | |||
2668a880ee | |||
5891409fdb | |||
0e9eba4900 | |||
e02737abd1 | |||
4671cf5fac | |||
88a751c590 | |||
e434173dfb | |||
e677309631 | |||
69505e8371 | |||
a5b7c0220b | |||
1c1eaa71de | |||
30f0d0a576 | |||
e2e19876cc |
144
.github/workflows/python_publish.yml
vendored
144
.github/workflows/python_publish.yml
vendored
@ -55,80 +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@release/v1
|
uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241
|
||||||
|
|
||||||
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:
|
with:
|
||||||
name: python-package-distributions
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
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 }}'
|
|
||||||
|
|
||||||
publish-to-testpypi:
|
# github-release:
|
||||||
name: Publish Python 🐍 distribution 📦 to TestPyPI
|
# name: >-
|
||||||
needs:
|
# Sign the Python 🐍 distribution 📦 with Sigstore
|
||||||
- build
|
# and upload them to GitHub Release
|
||||||
runs-on: ubuntu-latest
|
# needs:
|
||||||
|
# - publish-to-pypi
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
|
||||||
environment:
|
# permissions:
|
||||||
name: testpypi
|
# contents: write # IMPORTANT: mandatory for making GitHub Releases
|
||||||
url: https://test.pypi.org/p/goat_monitor
|
# id-token: write # IMPORTANT: mandatory for sigstore
|
||||||
|
|
||||||
permissions:
|
# env:
|
||||||
id-token: write # IMPORTANT: mandatory for trusted publishing
|
# 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
|
||||||
|
|
||||||
env:
|
# steps:
|
||||||
RUNNER_TOOL_CACHE: /toolcache # https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd
|
# - name: Download all the dists
|
||||||
AGENT_TOOLSDIRECTORY: /toolcache # https://github.com/actions/setup-python/issues/824
|
# 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 }}'
|
||||||
|
|
||||||
steps:
|
# publish-to-testpypi:
|
||||||
- name: Download all the dists
|
# name: Publish Python 🐍 distribution 📦 to TestPyPI
|
||||||
uses: actions/download-artifact@v3
|
# needs:
|
||||||
with:
|
# - build
|
||||||
name: python-package-distributions
|
# runs-on: ubuntu-latest
|
||||||
path: dist/
|
|
||||||
- name: Publish distribution 📦 to TestPyPI
|
# environment:
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
# name: testpypi
|
||||||
with:
|
# url: https://test.pypi.org/p/goat_monitor
|
||||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
||||||
repository-url: https://test.pypi.org/legacy/
|
# 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/
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -159,4 +159,6 @@ cython_debug/
|
|||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
**/_version.py
|
10
Pipfile.lock
generated
10
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "13a000de8cd5f6c3d270d059f8df15653008a32b9daa31494cc26bd4c80dd8d0"
|
"sha256": "4c61c72e95ca800fd43f3c1ba0fdbb9d0b96e8684edff51ae5c2f74a693ac20e"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -37,16 +37,18 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@ -138,7 +140,6 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@ -155,7 +156,6 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
|
@ -6,16 +6,17 @@ 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 -1 -- <COMMAND TO MONITOR>
|
goat_monitor --config ./config.toml --retries 3 -- <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.
|
||||||
|
@ -1 +0,0 @@
|
|||||||
__version__ = "editable"
|
|
@ -9,7 +9,7 @@ import gotify
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import toml
|
import toml
|
||||||
|
|
||||||
from goat_monitor._version import __version__
|
from goat_monitor._version import __version__ # type: ignore
|
||||||
|
|
||||||
|
|
||||||
# %% commands
|
# %% commands
|
||||||
@ -17,7 +17,8 @@ from goat_monitor._version import __version__
|
|||||||
@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",
|
||||||
)
|
)
|
||||||
@ -33,7 +34,13 @@ from goat_monitor._version import __version__
|
|||||||
default=False,
|
default=False,
|
||||||
help="Print version and exit",
|
help="Print version and exit",
|
||||||
)
|
)
|
||||||
def wrap(command: List[str], config: Path, retries: int, version):
|
@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):
|
||||||
"""Wrap an arbitrary command with gotify notifications"""
|
"""Wrap an arbitrary command with gotify notifications"""
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
@ -45,35 +52,42 @@ def wrap(command: List[str], config: Path, retries: int, version):
|
|||||||
settings = toml.load(f)
|
settings = toml.load(f)
|
||||||
|
|
||||||
# gotify configuration
|
# gotify configuration
|
||||||
url = settings["server"]
|
if not dry_run:
|
||||||
app_token = settings["app_token"]
|
url = settings["server"]
|
||||||
with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection:
|
app_token = settings["app_token"]
|
||||||
# this is just to test configuration.
|
with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection:
|
||||||
# I don't know how long a gotify connection will stay up so rather than thinking about it
|
# this is just to test configuration.
|
||||||
# I'm just creating a new one whenever I need to send a message
|
# I don't know how long a gotify connection will stay up so rather than thinking about it
|
||||||
pass
|
# I'm just creating a new one whenever I need to send a message
|
||||||
|
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")
|
||||||
|
|
||||||
for attempt in range(retries + 1):
|
attempt = 0
|
||||||
|
while attempt <= retries:
|
||||||
# run the command
|
# run the command
|
||||||
result = subprocess.run(
|
with subprocess.Popen(
|
||||||
" ".join(command),
|
" ".join(command),
|
||||||
shell=True,
|
shell=True,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
encoding="utf-8",
|
) 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()
|
||||||
|
|
||||||
# TODO: print lines real time as the subprocess runs
|
if return_code:
|
||||||
print(result.stdout, end="")
|
|
||||||
|
|
||||||
if result.returncode:
|
|
||||||
# failed
|
# failed
|
||||||
title = f"Command failed with exit code {result.returncode}"
|
title = f"Command failed with exit code {return_code}"
|
||||||
if attempt < retries:
|
if attempt < retries:
|
||||||
title += f" - Retrying (attempt {attempt+1}/{retries})"
|
title += f" - Retrying (attempt {attempt+1}/{retries})"
|
||||||
else:
|
else:
|
||||||
@ -83,20 +97,26 @@ def wrap(command: List[str], config: Path, retries: int, version):
|
|||||||
title = "Command succeeded"
|
title = "Command succeeded"
|
||||||
|
|
||||||
MAX_LINES = 20
|
MAX_LINES = 20
|
||||||
lines = result.stdout.splitlines()
|
lines = 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"
|
"Command:\n" + " ".join(command) + f'\n\nResult{ " (truncated)" if truncated else ""}:\n' + "\n".join(lines)
|
||||||
+ " ".join(command)
|
|
||||||
+ f'\n\nResult{ " (truncated)" if len(lines) > MAX_LINES else ""}:\n'
|
|
||||||
+ "\n".join(lines)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with gotify.Gotify(base_url=url, app_token=app_token) as gotify_connection:
|
if not dry_run:
|
||||||
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)
|
||||||
|
|
||||||
sys.exit(result.returncode)
|
if not return_code:
|
||||||
|
# only retry on failure
|
||||||
|
break
|
||||||
|
|
||||||
|
attempt += 1
|
||||||
|
|
||||||
|
sys.exit(return_code)
|
||||||
|
|
||||||
|
|
||||||
# %% main
|
# %% main
|
||||||
|
@ -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.11" # might work with earlier versions, I haven't tried
|
requires-python = ">=3"
|
||||||
# keywords = ["one", "two"]
|
# keywords = ["one", "two"]
|
||||||
license = { text = "MIT License" }
|
license = { text = "MIT License" }
|
||||||
classifiers = ["Programming Language :: Python :: 3"]
|
classifiers = ["Programming Language :: Python :: 3"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user