From 29bfd81c60f6ccc1772eb8c3ab24f5e04e14042e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robbert=20M=C3=BCller?= Date: Sun, 26 Jul 2020 19:20:29 +0200 Subject: [PATCH] :sparkels: Initial add-on code --- prometheus/DOCS.md | 120 ++++++++++++++++++ prometheus/Dockerfile | 61 +++++++++ prometheus/build.json | 8 ++ prometheus/config.json | 29 +++++ prometheus/icon.png | Bin 0 -> 15451 bytes prometheus/logo.png | Bin 0 -> 15451 bytes .../rootfs/etc/cont-init.d/prometheus.sh | 9 ++ .../rootfs/etc/prometheus/prometheus.yml | 40 ++++++ .../services.d/prometheus-configgen/finish | 9 ++ .../etc/services.d/prometheus-configgen/run | 14 ++ .../rootfs/etc/services.d/prometheus/finish | 9 ++ .../rootfs/etc/services.d/prometheus/run | 45 +++++++ .../rootfs/opt/prometheus-configgen/combiner | 103 +++++++++++++++ .../prometheus-configgen/prometheus.template | 38 ++++++ .../opt/prometheus-configgen/requirements.txt | 5 + 15 files changed, 490 insertions(+) create mode 100644 prometheus/DOCS.md create mode 100755 prometheus/Dockerfile create mode 100644 prometheus/build.json create mode 100755 prometheus/config.json create mode 100644 prometheus/icon.png create mode 100644 prometheus/logo.png create mode 100644 prometheus/rootfs/etc/cont-init.d/prometheus.sh create mode 100644 prometheus/rootfs/etc/prometheus/prometheus.yml create mode 100644 prometheus/rootfs/etc/services.d/prometheus-configgen/finish create mode 100755 prometheus/rootfs/etc/services.d/prometheus-configgen/run create mode 100644 prometheus/rootfs/etc/services.d/prometheus/finish create mode 100755 prometheus/rootfs/etc/services.d/prometheus/run create mode 100644 prometheus/rootfs/opt/prometheus-configgen/combiner create mode 100644 prometheus/rootfs/opt/prometheus-configgen/prometheus.template create mode 100644 prometheus/rootfs/opt/prometheus-configgen/requirements.txt diff --git a/prometheus/DOCS.md b/prometheus/DOCS.md new file mode 100644 index 0000000..a240f9e --- /dev/null +++ b/prometheus/DOCS.md @@ -0,0 +1,120 @@ +# Home Assistant Community Add-on: Prometheus + +.... + +## Installation + +The installation of this add-on is pretty straightforward and not different in +comparison to installing any other Home Assistant add-on. + +1. Search for the "Prometheus" add-on in the Supervisor add-on store. +1. Install the "Prometheus" add-on. +1. Start the "Prometheus" add-on. +1. Check the logs of the "Prometheus" to see if everything went well. +1. Open the Web UI. + +**Note**: The addon supports both Ingress and direct access, this is the default + +## Configuration + +There are no configuration options for the addon. + +To add additional scrape targets you need to create a file per target in /share/prometheus/targets. + +Example: + +```yaml +--- +job_name: 'octoprint' +scrape_interval: 5s +metrics_path: '/plugin/prometheus_exporter/metrics' +params: + apikey: ['VERYSECRETAPIKEY'] +static_configs: + - targets: ['octoprint.example.org:5000'] +``` + +**Note**: _This is just an example, don't copy and paste it! Create your own!_ + + +The job names `home-assistant` and `prometheus` are already defined by default. + +Rules can be created under /share/prometheus/rules/ + +The addon will reload the configuration if a valid configuration is available. If not it will log errors in the addon log + +## Known issues and limitations + +* Job name must be unique, but this has to be enforced by the user. +* no alert manager yet + +## Changelog & Releases + +This repository keeps a change log using [GitHub's releases][releases] +functionality. The format of the log is based on +[Keep a Changelog][keepchangelog]. + +Releases are based on [Semantic Versioning][semver], and use the format +of ``MAJOR.MINOR.PATCH``. In a nutshell, the version will be incremented +based on the following: + +- ``MAJOR``: Incompatible or major changes. +- ``MINOR``: Backwards-compatible new features and enhancements. +- ``PATCH``: Backwards-compatible bugfixes and package updates. + +## Support + +Got questions? + +You have several options to get them answered: + +- The [Home Assistant Community Add-ons Discord chat server][discord] for add-on + support and feature requests. +- The [Home Assistant Discord chat server][discord-ha] for general Home + Assistant discussions and questions. +- The Home Assistant [Community Forum][forum]. +- Join the [Reddit subreddit][reddit] in [/r/homeassistant][reddit] + +You could also [open an issue here][issue] GitHub. + +## Authors & contributors + +The original setup of this repository is by [Robbert Müller][mjrider]. + +For a full list of all authors and contributors, +check [the contributor's page][contributors]. + +## License + +MIT License + +Copyright (c) 2018-2020 Franck Nijhof + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +[contributors]: https://github.com/hassio-addons/addon-Prometheus/graphs/contributors +[discord-ha]: https://discord.gg/c5DvZ4e +[discord]: https://discord.me/hassioaddons +[forum]: https://example.net +[mjrider]: https://github.com/mjrider +[issue]: https://github.com/hassio-addons/addon-prometheus/issues +[keepchangelog]: http://keepachangelog.com/en/1.0.0/ +[reddit]: https://reddit.com/r/homeassistant +[releases]: https://github.com/hassio-addons/addon-prometheus/releases +[semver]: http://semver.org/spec/v2.0.0.htm diff --git a/prometheus/Dockerfile b/prometheus/Dockerfile new file mode 100755 index 0000000..0b44073 --- /dev/null +++ b/prometheus/Dockerfile @@ -0,0 +1,61 @@ +ARG BUILD_FROM=hassioaddons/base:8.0.1 +# hadolint ignore=DL3006 +FROM ${BUILD_FROM} + +# Set shell +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Setup base system +ARG BUILD_ARCH=amd64 +ENV PROMETHEUS_VERSION=2.19.2 + +# Copy root filesystem +COPY rootfs / + +RUN \ + ARCH="${BUILD_ARCH}" \ + && if [ "${BUILD_ARCH}" = "aarch64" ]; then ARCH="arm64"; fi \ + \ + && apk update \ + && apk --no-cache add python3 py3-idna py3-certifi py3-chardet py3-yaml py3-urllib3 py3-requests \ + && apk --no-cache add --virtual builddeps py-pip \ + \ + && curl -J -L -o /tmp/prometheus.tar.gz \ + https://github.com/prometheus/prometheus/releases/download/v${PROMETHEUS_VERSION}/prometheus-${PROMETHEUS_VERSION}.linux-${ARCH}.tar.gz \ + && adduser -s /bin/false -D -H prometheus \ + && cd /tmp \ + && tar -xvf /tmp/prometheus.tar.gz \ + && mkdir -p /etc/prometheus \ + && cp prometheus-${PROMETHEUS_VERSION}.linux-${ARCH}/promtool /usr/local/bin/ \ + && cp prometheus-${PROMETHEUS_VERSION}.linux-${ARCH}/prometheus /usr/local/bin/ \ + && cp -R prometheus-${PROMETHEUS_VERSION}.linux-${ARCH}/console_libraries/ /etc/prometheus/ \ + && cp -R prometheus-${PROMETHEUS_VERSION}.linux-${ARCH}/consoles/ /etc/prometheus/ \ + && rm -r prometheus-${PROMETHEUS_VERSION}.linux-${ARCH} \ + && chown -R prometheus:prometheus /etc/prometheus \ + && pip3 install -r /opt/prometheus-configgen/requirements.txt \ + && apk del builddeps + +# Build arguments +ARG BUILD_DATE +ARG BUILD_REF +ARG BUILD_VERSION + +# Labels +LABEL \ + io.hass.name="Prometheus" \ + io.hass.description="Cloud native metrics" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.type="addon" \ + io.hass.version=${BUILD_VERSION} \ + maintainer="Robbert Müller " \ + org.opencontainers.image.title="Prometheus" \ + org.opencontainers.image.description="Cloud native metrics" \ + org.opencontainers.image.vendor="Home Assistant Community Add-ons" \ + org.opencontainers.image.authors="Robbert Müller " \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.url="https://addons.community" \ + org.opencontainers.image.source="https://github.com/hassio-addons/addon-prometheus" \ + org.opencontainers.image.documentation="https://github.com/hassio-addons/addon-prometheus/blob/master/README.md" \ + org.opencontainers.image.created=${BUILD_DATE} \ + org.opencontainers.image.revision=${BUILD_REF} \ + org.opencontainers.image.version=${BUILD_VERSION} diff --git a/prometheus/build.json b/prometheus/build.json new file mode 100644 index 0000000..bfb53e4 --- /dev/null +++ b/prometheus/build.json @@ -0,0 +1,8 @@ +{ + "build_from": { + "aarch64": "hassioaddons/base-aarch64:8.0.1", + "amd64": "hassioaddons/base-amd64:8.0.1", + "armv7": "hassioaddons/base-armv7:8.0.1" + }, + "args": {} +} diff --git a/prometheus/config.json b/prometheus/config.json new file mode 100755 index 0000000..7d36c3a --- /dev/null +++ b/prometheus/config.json @@ -0,0 +1,29 @@ +{ + "name": "Prometheus", + "version": "dev", + "slug": "prometheus", + "description": "Cloud native metrics", + "url": "https://github.com/hassio-addons/addon-prometheus", + "startup": "services", + "ingress": true, + "ingress_port": 9090, + "ingress_entry": "graph", + "panel_icon": "mdi:chart-timeline", + "panel_title": "Prometheus", + "arch": ["aarch64", "amd64", "armv7"], + "boot": "auto", + "hassio_api": true, + "homeassistant_api": true, + "hassio_role": "default", + "map": ["share:rw"], + "options": { + }, + "ports": { + "9090/tcp": null + }, + "ports_description": { + "9090/tcp": "Not required for Ingress" + }, + "schema": { + } +} diff --git a/prometheus/icon.png b/prometheus/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c03fe508819d89be006d8662d6071b3f1e4c4619 GIT binary patch literal 15451 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ⅈK21-}b)Nkw`wM+?T{?O8Np+{4r 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ⅈK21-}b)Nkw`wM+?T{?O8Np+{4r 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 '/run/home-assistant.token' + diff --git a/prometheus/rootfs/etc/prometheus/prometheus.yml b/prometheus/rootfs/etc/prometheus/prometheus.yml new file mode 100644 index 0000000..8b6d1d1 --- /dev/null +++ b/prometheus/rootfs/etc/prometheus/prometheus.yml @@ -0,0 +1,40 @@ +--- +# my global config +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +# Alertmanager configuration +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: + - "/share/prometheus/rules/*.yml" + - "/share/prometheus/rules/*.yaml" + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ['localhost:9090'] + - job_name: 'home-assistant' + scrape_interval: 60s + metrics_path: /core/api/prometheus + + # Long-Lived Access Token + bearer_token_file: '/run/home-assistant.token' + + scheme: http + static_configs: + - targets: ['supervisor:80'] diff --git a/prometheus/rootfs/etc/services.d/prometheus-configgen/finish b/prometheus/rootfs/etc/services.d/prometheus-configgen/finish new file mode 100644 index 0000000..c2a8d04 --- /dev/null +++ b/prometheus/rootfs/etc/services.d/prometheus-configgen/finish @@ -0,0 +1,9 @@ +#!/usr/bin/execlineb -S0 +# ============================================================================== +# Home Assistant Community Add-on: Prometheus +# Take down the S6 supervision tree when Prometheus fails +# ============================================================================== +if { s6-test ${1} -ne 0 } +if { s6-test ${1} -ne 256 } + +s6-svscanctl -t /var/run/s6/services diff --git a/prometheus/rootfs/etc/services.d/prometheus-configgen/run b/prometheus/rootfs/etc/services.d/prometheus-configgen/run new file mode 100755 index 0000000..2de1b0b --- /dev/null +++ b/prometheus/rootfs/etc/services.d/prometheus-configgen/run @@ -0,0 +1,14 @@ +#!/usr/bin/with-contenv bashio +# shellcheck disable=SC2191 + +bashio::log.info 'Starting prometheus config generator...' + +if [[ ! -d /share/promethus/targets ]] ; then + mkdir -p /share/prometheus/targets + chown -R prometheus:prometheus /share/prometheus/targets +fi + +cd /opt/prometheus-configgen + +# Run Prometheus +exec s6-setuidgid prometheus python3 combiner diff --git a/prometheus/rootfs/etc/services.d/prometheus/finish b/prometheus/rootfs/etc/services.d/prometheus/finish new file mode 100644 index 0000000..c2a8d04 --- /dev/null +++ b/prometheus/rootfs/etc/services.d/prometheus/finish @@ -0,0 +1,9 @@ +#!/usr/bin/execlineb -S0 +# ============================================================================== +# Home Assistant Community Add-on: Prometheus +# Take down the S6 supervision tree when Prometheus fails +# ============================================================================== +if { s6-test ${1} -ne 0 } +if { s6-test ${1} -ne 256 } + +s6-svscanctl -t /var/run/s6/services diff --git a/prometheus/rootfs/etc/services.d/prometheus/run b/prometheus/rootfs/etc/services.d/prometheus/run new file mode 100755 index 0000000..99dbd50 --- /dev/null +++ b/prometheus/rootfs/etc/services.d/prometheus/run @@ -0,0 +1,45 @@ +#!/usr/bin/with-contenv bashio +# shellcheck disable=SC2191 +# ============================================================================== +# Home Assistant Community Add-on: Prometheus +# Runs the Prometheus Server +# ============================================================================== +declare -a options +declare name +declare value + +bashio::log.info 'Starting prometheus...' + +options+=(--config.file="/etc/prometheus/prometheus.yml" ) +options+=(--storage.tsdb.path="/data/prometheus" ) +options+=(--web.console.libraries="/usr/share/prometheus/console_libraries" ) +options+=(--web.console.templates="/usr/share/prometheus/consoles" ) +options+=(--web.route-prefix="/" ) +options+=(--web.external-url="http://localhost:9090$(bashio::addon.ingress_entry)/" ) +options+=(--web.enable-lifecycle ) + +# Load custom environment variables +for var in $(bashio::config 'env_vars|keys'); do + name=$(bashio::config "env_vars[${var}].name") + value=$(bashio::config "env_vars[${var}].value") + bashio::log.info "Setting ${name} to ${value}" + export "${name}=${value}" +done + +if [[ ! -d /data/prometheus ]] ; then + mkdir -p /data/prometheus + chown prometheus:prometheus /data/prometheus +fi + +if [[ ! -d /share/promethus/rules ]] ; then + mkdir -p /share/prometheus/rules + chown -R prometheus:prometheus /share/prometheus/rules +fi + +if [[ ! -d /share/promethus/targets ]] ; then + mkdir -p /share/prometheus/targets + chown -R prometheus:prometheus /share/prometheus/targets +fi + +# Run Prometheus +exec s6-setuidgid prometheus /usr/local/bin/prometheus "${options[@]}" diff --git a/prometheus/rootfs/opt/prometheus-configgen/combiner b/prometheus/rootfs/opt/prometheus-configgen/combiner new file mode 100644 index 0000000..94c69f4 --- /dev/null +++ b/prometheus/rootfs/opt/prometheus-configgen/combiner @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +import sys +import asyncio +import aionotify +import yaml +import os +import tempfile +import requests + +from yamlinclude import YamlIncludeConstructor + + +def generateConfig(): + YamlIncludeConstructor.add_to_loader_class( + loader_class=yaml.FullLoader, base_dir="/share/prometheus/" + ) + + with open("prometheus.template") as f: + data = yaml.load(f, Loader=yaml.FullLoader) + + data["scrape_configs"] = ( + data[".scrape_configs_static"] + data[".scrape_configs_included"] + ) + del data[".scrape_configs_static"] + del data[".scrape_configs_included"] + return yaml.dump(data, default_flow_style=False, default_style="") + + +def testConfig(config): + tmp = None + result = False + try: + tmp = tempfile.NamedTemporaryFile() + with open(tmp.name, "w") as f: + f.write(config) + r = os.system("promtool check config " + tmp.name + "> /dev/null") + result = r == 0 + except: + print("Failed to validate") + raise + if result == False: + raise Exception("validation error") + return result + + +def writeConfig(config, file): + try: + with open(file, "w") as f: + f.write(config) + r = requests.post(url="http://localhost:9090/-/reload", data={}) + except: + print("Exception") + pass + + +loop = asyncio.get_event_loop() +paths_to_watch = ["/share/prometheus/targets/"] + +lock = asyncio.Lock() + + +async def compile(): + if lock.locked() == False: + await lock.acquire() + try: + config = generateConfig() + testConfig(config) + writeConfig(config, "/etc/prometheus/prometheus.yml") + print("Compiled") + except: + pass + finally: + lock.release() + + +async def watcher(): + asyncio.create_task(compile()) + filewatch = aionotify.Watcher() + for path in paths_to_watch: + filewatch.watch( + path, + aionotify.Flags.MODIFY | aionotify.Flags.CREATE | aionotify.Flags.DELETE, + ) + print(path) + await filewatch.setup(loop) + while True: + event = await filewatch.get_event() + sys.stdout.write("Got event: %s\n" % repr(event)) + asyncio.create_task(compile()) + filewatch.close() + + +def main(): + try: + loop.run_until_complete(watcher()) + finally: + # loop.close() + pass + + +if __name__ == "__main__": + main() diff --git a/prometheus/rootfs/opt/prometheus-configgen/prometheus.template b/prometheus/rootfs/opt/prometheus-configgen/prometheus.template new file mode 100644 index 0000000..c9aa2b2 --- /dev/null +++ b/prometheus/rootfs/opt/prometheus-configgen/prometheus.template @@ -0,0 +1,38 @@ +--- +# my global config +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +# Alertmanager configuration +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: + - "/share/prometheus/rules/*.yaml" + + +.scrape_configs_included: !include targets/*.yaml +.scrape_configs_static: + - job_name: 'home-assistant' + scrape_interval: 60s + metrics_path: /core/api/prometheus + + # Long-Lived Access Token + bearer_token_file: '/run/home-assistant.token' + + scheme: http + static_configs: + - targets: ['supervisor:80'] + - job_name: 'prometheus' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ['localhost:9090'] diff --git a/prometheus/rootfs/opt/prometheus-configgen/requirements.txt b/prometheus/rootfs/opt/prometheus-configgen/requirements.txt new file mode 100644 index 0000000..15b5dd2 --- /dev/null +++ b/prometheus/rootfs/opt/prometheus-configgen/requirements.txt @@ -0,0 +1,5 @@ +PyYAML>=5.3.1 +pyyaml-include>=1.2 +requests>=2.23.0 +aionotify +