diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f58ff84..205fc34 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.155.1/containers/ubuntu/.devcontainer/base.Dockerfile -# [Choice] CheckMK version: 2.2.0-latest -ARG VARIANT="2.2.0-latest" +# [Choice] CheckMK version: 2.3.0-latest +ARG VARIANT="2.3.0-latest" FROM checkmk/check-mk-raw:${VARIANT} RUN /docker-entrypoint.sh /bin/true diff --git a/.devcontainer/build.sh b/.devcontainer/build.sh index 62901f1..9c8c35b 100755 --- a/.devcontainer/build.sh +++ b/.devcontainer/build.sh @@ -2,19 +2,9 @@ NAME=$(python3 -c 'print(eval(open("package").read())["name"])') VERSION=$(python3 -c 'print(eval(open("package").read())["version"])') -rm -f $NAME-$VERSION.mkp \ - /omd/sites/cmk/var/cat check_mk/packages/${NAME}-*.mkp \ +rm /omd/sites/cmk/var/check_mk/packages/${NAME} \ /omd/sites/cmk/var/check_mk/packages_local/${NAME}-*.mkp ||: mkp -v package package 2>&1 | sed '/Installing$/Q' ||: -cp /omd/sites/cmk/var/check_mk/packages_local/$NAME-$VERSION.mkp . - -mkp inspect $NAME-$VERSION.mkp - -# Set Outputs for GitHub Workflow steps -if [ -n "$GITHUB_WORKSPACE" ]; then - echo "pkgfile=${NAME}-${VERSION}.mkp" >> $GITHUB_OUTPUT - echo "pkgname=${NAME}" >> $GITHUB_OUTPUT - echo "pkgversion=$VERSION" >> $GITHUB_OUTPUT -fi \ No newline at end of file +mkp inspect /omd/sites/cmk/var/check_mk/packages_local/$NAME-$VERSION.mkp diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b68ca48..d39c5da 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "build": { "dockerfile": "Dockerfile", // Update 'VARIANT' to pick an Ubuntu version: focal, bionic - "args": { "VARIANT": "2.2.0-latest" } + "args": { "VARIANT": "2.3.0-latest" } }, "customizations": { @@ -29,7 +29,9 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": ".devcontainer/symlink.sh", + //"postCreateCommand": ".devcontainer/symlink.sh", + "workspaceMount": "source=${localWorkspaceFolder},target=/omd/sites/cmk/local/lib/python3/cmk_addons/plugins/jb_fls,type=bind,consistency=cached", + "workspaceFolder": "/omd/sites/cmk/local/lib/python3/cmk_addons/plugins/jb_fls", // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "cmk", diff --git a/.devcontainer/symlink.sh b/.devcontainer/symlink.sh deleted file mode 100755 index 57727b0..0000000 --- a/.devcontainer/symlink.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -for DIR in 'agents' 'checkman' 'checks' 'doc' 'inventory' 'notifications' 'pnp-templates' 'web'; do - rm -rfv $OMD_ROOT/local/share/check_mk/$DIR - ln -sv $WORKSPACE/$DIR $OMD_ROOT/local/share/check_mk/$DIR -done; - -rm -rfv $OMD_ROOT/local/lib/check_mk/base/plugins/agent_based -ln -sv $WORKSPACE/agent_based $OMD_ROOT/local/lib/check_mk/base/plugins/agent_based - -rm -rfv $OMD_ROOT/local/lib/nagios/plugins -ln -sv $WORKSPACE/nagios_plugins $OMD_ROOT/local/lib/nagios/plugins \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a9cce21 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +buy_me_a_coffee: jiuka \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 1c3aa3e..fe659e8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -11,30 +11,33 @@ jobs: name: Build Release Package runs-on: ubuntu-latest container: - image: checkmk/check-mk-raw:2.2.0-latest + image: checkmk/check-mk-raw:2.3.0-latest permissions: contents: write - env: - OMD_ROOT: /omd/sites/cmk - OMD_SITE: cmk - CMK_SITE_ID: cmk - WORKSPACE: ${{ github.workspace }} - steps: - name: Initialize Checkmk Site run: /docker-entrypoint.sh /bin/true - - uses: actions/checkout@v3 - - name: Setup links - run: .devcontainer/symlink.sh - - name: Update GITHUB_PATH - run: echo "/omd/sites/cmk/bin" >> $GITHUB_PATH + - uses: actions/checkout@v4 + - name: Parse Package File + run: | + NAME=$(python3 -c 'print(eval(open("package").read())["name"])') + VERSION=$(python3 -c 'print(eval(open("package").read())["version"])') + echo "CMKPKG_NAME=$NAME" >> "$GITHUB_ENV" + echo "CMKPKG_VERSION=$VERSION" >> "$GITHUB_ENV" + - name: Install Plugin + run: | + rsync -aC --chown=cmk:cmk $GITHUB_WORKSPACE/ /omd/sites/cmk/local/lib/python3/cmk_addons/plugins/$CMKPKG_NAME/ - name: Build Extension - run: .devcontainer/build.sh + run: | + su -l -c "mkp -v package $GITHUB_WORKSPACE/package" cmk + cp /omd/sites/cmk/var/check_mk/packages_local/${CMKPKG_NAME}-${CMKPKG_VERSION}.mkp . + echo "pkgfile=${CMKPKG_NAME}-${CMKPKG_VERSION}.mkp" >> $GITHUB_OUTPUT + echo "pkgname=${CMKPKG_NAME}" >> $GITHUB_OUTPUT + echo "pkgversion=${CMKPKG_VERSION}" >> $GITHUB_OUTPUT id: cmkpkg - - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: release_name: Release ${{ github.ref }} draft: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 18bcb70..bd57704 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,27 +13,31 @@ jobs: name: Build Checkmk package runs-on: ubuntu-latest container: - image: checkmk/check-mk-raw:2.2.0-latest - - env: - OMD_ROOT: /omd/sites/cmk - OMD_SITE: cmk - CMK_SITE_ID: cmk - WORKSPACE: ${{ github.workspace }} + image: checkmk/check-mk-raw:2.3.0-latest steps: - name: Initialize Checkmk Site run: /docker-entrypoint.sh /bin/true - - uses: actions/checkout@v3 - - name: Setup links - run: .devcontainer/symlink.sh - - name: Update GITHUB_PATH - run: echo "/omd/sites/cmk/bin" >> $GITHUB_PATH + - uses: actions/checkout@v4 + - name: Parse Package File + run: | + NAME=$(python3 -c 'print(eval(open("package").read())["name"])') + VERSION=$(python3 -c 'print(eval(open("package").read())["version"])') + echo "CMKPKG_NAME=$NAME" >> "$GITHUB_ENV" + echo "CMKPKG_VERSION=$VERSION" >> "$GITHUB_ENV" + - name: Install Plugin + run: | + rsync -aC --chown=cmk:cmk $GITHUB_WORKSPACE/ /omd/sites/cmk/local/lib/python3/cmk_addons/plugins/$CMKPKG_NAME/ - name: Build Extension - run: .devcontainer/build.sh + run: | + su -l -c "mkp -v package $GITHUB_WORKSPACE/package" cmk + cp /omd/sites/cmk/var/check_mk/packages_local/${CMKPKG_NAME}-${CMKPKG_VERSION}.mkp . + echo "pkgfile=${CMKPKG_NAME}-${CMKPKG_VERSION}.mkp" >> $GITHUB_OUTPUT + echo "pkgname=${CMKPKG_NAME}" >> $GITHUB_OUTPUT + echo "pkgversion=${CMKPKG_VERSION}" >> $GITHUB_OUTPUT id: cmkpkg - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.cmkpkg.outputs.pkgfile }} path: ${{ steps.cmkpkg.outputs.pkgfile }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 91c978a..f2e29cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,6 +4,8 @@ on: push: paths: - '**.py' + - .github/workflows/lint.yml + jobs: flake8_py3: @@ -11,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.9 - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: '3.12' - name: Install flake8 run: pip install flake8 - name: Run flake8 diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 695fba6..3d428a6 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,30 +1,32 @@ name: pytest on: - push: [] + push: + paths: + - '**.py' + - .github/workflows/pytest.yml jobs: pytest: runs-on: ubuntu-latest container: - image: checkmk/check-mk-raw:2.2.0-latest - - env: - OMD_ROOT: /omd/sites/cmk - OMD_SITE: cmk - CMK_SITE_ID: cmk - WORKSPACE: ${{ github.workspace }} + image: checkmk/check-mk-raw:2.3.0-latest steps: - - name: Initialize Checkmk Site - run: /docker-entrypoint.sh /bin/true - - uses: actions/checkout@v3 - - name: Setup links - run: ./.devcontainer/symlink.sh - - name: Install pytest - run: su -l -c "REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt pip3 install -r $GITHUB_WORKSPACE/.devcontainer/requirements.txt" cmk - - name: Update GITHUB_PATH - run: echo "/omd/sites/cmk/bin" >> $GITHUB_PATH - - name: Run pytest - run: python3 -m pytest \ No newline at end of file + - name: Initialize Checkmk Site + run: /docker-entrypoint.sh /bin/true + - uses: actions/checkout@v4 + - name: Parse Package File + run: | + NAME=$(python3 -c 'print(eval(open("package").read())["name"])') + VERSION=$(python3 -c 'print(eval(open("package").read())["version"])') + echo "CMKPKG_NAME=$NAME" >> "$GITHUB_ENV" + echo "CMKPKG_VERSION=$VERSION" >> "$GITHUB_ENV" + - name: Install Plugin + run: | + rsync -aC --chown=cmk:cmk $GITHUB_WORKSPACE/ /omd/sites/cmk/local/lib/python3/cmk_addons/plugins/$CMKPKG_NAME/ + - name: Install pytest + run: su -l -c "REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt pip3 install -r $GITHUB_WORKSPACE/.devcontainer/requirements.txt" cmk + - name: Run pytest + run: su -l -c "python3 -m pytest /omd/sites/cmk/local/lib/python3/cmk_addons/plugins/$CMKPKG_NAME/" cmk \ No newline at end of file diff --git a/README.md b/README.md index cbc7480..bcbe2b4 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,7 @@ For the best development experience use [VSCode](https://code.visualstudio.com/) ## Directories -The following directories in this repo are getting mapped into the Checkmk site. - -* `agents`, `checkman`, `checks`, `doc`, `inventory`, `notifications`, `pnp-templates`, `web` are mapped into `local/share/check_mk/` -* `agent_based` is mapped to `local/lib/check_mk/base/plugins/agent_based` -* `nagios_plugins` is mapped to `local/lib/nagios/plugins` +The repository is mounted into `/omd/sites/cmk/local/lib/python3/cmk_addons/plugins/jb_fls` ## Continuous integration ### Local diff --git a/agent_based/jb_fls.py b/agent_based/jb_fls.py index 53ba244..0f6f056 100644 --- a/agent_based/jb_fls.py +++ b/agent_based/jb_fls.py @@ -3,7 +3,7 @@ # # jb_fls - JetBrains Floatingcheck # -# Copyright (C) 2020 Marius Rieder +# Copyright (C) 2020-2024 Marius Rieder # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,17 +21,26 @@ import datetime -from .agent_based_api.v1 import ( - check_levels, - register, +from collections.abc import Mapping + +from cmk.agent_based.v1 import check_levels +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, render, Result, Service, State, + StringTable, ) -def parse_jb_fls(string_table): +_Section = Mapping[str, str | tuple[str, str]] + + +def parse_jb_fls(string_table: StringTable) -> _Section: parsed = {'connection': []} for line in string_table: if line[0] == 'connection': @@ -41,18 +50,22 @@ def parse_jb_fls(string_table): return parsed -register.agent_section( - name='jb_fls', +agent_section_jb_fls = AgentSection( + name="jb_fls", parse_function=parse_jb_fls, ) -def discovery_jb_fls(section): +def discovery_jb_fls(section: _Section) -> DiscoveryResult: if 'serverUID' in section: yield Service() -def check_jb_fls(params, section): +def check_jb_fls(params, section: _Section) -> CheckResult: + if 'serverUID' not in section: + yield Result(state=State.UNKNOWN, summary='Server: %s not found' % (section.get('url'))) + return + yield Result(state=State.OK, summary='Server: %s %s ' % (section.get('serverUID', 'Unknown'), section.get('url'))) if section.get('health', 500) != '200': @@ -80,7 +93,7 @@ def check_jb_fls(params, section): ) -register.check_plugin( +check_plugin_jb_fls = CheckPlugin( name='jb_fls', service_name='JB FLS', discovery_function=discovery_jb_fls, diff --git a/agent_based/jb_fls_licenses.py b/agent_based/jb_fls_licenses.py index c954d97..d96fbc1 100644 --- a/agent_based/jb_fls_licenses.py +++ b/agent_based/jb_fls_licenses.py @@ -3,7 +3,7 @@ # # jb_fls_licenses - JetBrains Floating Licenses check # -# Copyright (C) 2020 Marius Rieder +# Copyright (C) 2020-2024 Marius Rieder # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,9 +19,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from .agent_based_api.v1 import ( - Metric, - register, + +from cmk.agent_based.v2 import ( + AgentSection, + check_levels, + CheckPlugin, Result, Service, State, @@ -37,8 +39,8 @@ def parse_jb_fls_licenses(string_table): return parsed -register.agent_section( - name='jb_fls_licenses', +agent_section_jb_fls_licenses = AgentSection( + name="jb_fls_licenses", parse_function=parse_jb_fls_licenses, ) @@ -54,49 +56,30 @@ def check_jb_fls_licenses(item, params, section): license_total, license_used = section.get(item) - license_params = params.get('licenses', None) - - if license_params is False: - license_warn = None - license_crit = None - elif not license_params: - license_warn = int(license_total) - license_crit = int(license_total) - elif isinstance(license_params[0], int): - license_warn = max(0, int(license_total) - license_params[0]) - license_crit = max(0, int(license_total) - license_params[1]) - else: - license_warn = int(license_total) * (1 - license_params[0] / 100.0) - license_crit = int(license_total) * (1 - license_params[1] / 100.0) - - yield Metric('licenses', - int(license_used), - levels=(license_warn, license_crit), - boundaries=(0, int(license_total))) - - if int(license_used) <= int(license_total): - infotext = 'used %d out of %d licenses' % (int(license_used), int(license_total)) - else: - infotext = 'used %d licenses, but you have only %d' % (int(license_used), int(license_total)) - - if license_crit is not None and int(license_used) >= license_crit: - status = State.CRIT - elif license_warn is not None and int(license_used) >= license_warn: - status = State.WARN - else: - status = State.OK - - if license_crit is not None: - infotext += ' (warn/crit at %d/%d)' % (license_warn, license_crit) - - yield Result(state=status, summary=infotext) - - -register.check_plugin( + match params.get('licenses'): + case ('crit_on_all', _): + levels = ('fixed', (license_total, license_total)) + case ('absolute', {'warn': warn, 'crit': crit}): + levels = ('fixed', (license_total - warn, license_total - crit)) + case ('percentage', {'warn': warn, 'crit': crit}): + levels = ('fixed', (int(license_total * (100 - warn) / 100), int(license_total * (100 - crit) / 100))) + case _: + levels = ('no_levels', None) + + yield Result(state=State.OK, summary=f"Licenses available: {license_total}") + yield from check_levels(license_used, + label="used", + render_func=lambda v: f"{v:d}", + levels_upper=levels, + metric_name='licenses', + boundaries=(0, int(license_total))) + + +check_plugin_jb_fls = CheckPlugin( name='jb_fls_licenses', service_name='JB Licenses %s', discovery_function=discovery_jb_fls_licenses, check_function=check_jb_fls_licenses, check_ruleset_name='jb_fls_licenses', - check_default_parameters={}, + check_default_parameters={'licenses': ('crit_on_all', True)}, ) diff --git a/agents/special/agent_jb_fls b/lib/agent.py old mode 100755 new mode 100644 similarity index 70% rename from agents/special/agent_jb_fls rename to lib/agent.py index 8fdde0c..3ea67ca --- a/agents/special/agent_jb_fls +++ b/lib/agent.py @@ -3,7 +3,7 @@ # # jb_fls - JetBrains Floatingcheck # -# Copyright (C) 2020 Marius Rieder +# Copyright (C) 2020-2024 Marius Rieder # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,17 +20,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from typing import Optional, Sequence -import logging import requests +import logging from functools import cached_property -from cmk.special_agents.utils.agent_common import ( +from cmk.special_agents.v0_unstable.agent_common import ( + SectionWriter, special_agent_main, ) -from cmk.special_agents.utils.argument_parsing import ( - Args, - create_default_argument_parser, -) +from cmk.special_agents.v0_unstable.argument_parsing import Args, create_default_argument_parser import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) @@ -42,7 +40,7 @@ class AgentJbFls: '''Checkmk special Agent for JetBrains Floating License Server''' def run(self): - special_agent_main(self.parse_arguments, self.main) + return special_agent_main(self.parse_arguments, self.main) def parse_arguments(self, argv: Optional[Sequence[str]]) -> Args: parser = create_default_argument_parser(description=self.__doc__) @@ -71,34 +69,33 @@ def main(self, args: Args): self.report_licenses() def report_fls(self): - print('<<>>') - - # URL - print('url\t%s' % self.args.url) - - # Check Health - print('health\t%s' % self._health['status_code']) - for key, value in self._health.items(): - if key == 'status_code': - continue - print('%s\t%s' % (key, value)) - - # Check Connection - for line in self._check_connection.splitlines(): - if not line: - continue - print('connection\t%s' % line) - - # Check Version - for key, value in self._check_version.items(): - if key == 'status_code': - continue - print('%s\t%s' % (key, value)) + with SectionWriter('jb_fls', separator='\t') as writer: + # URL + writer.append(f"url\t{self.args.url}") + + # Check Health + writer.append(f"health\t{self._health['status_code']}") + for key, value in self._health.items(): + if key == 'status_code': + continue + writer.append(f"{key}\t{value}") + + # Check Connection + for line in self._check_connection.splitlines(): + if not line: + continue + writer.append(f"connection\t{line}") + + # Check Version + for key, value in self._check_version.items(): + if key == 'status_code': + continue + writer.append(f"{key}\t{value}") def report_licenses(self): - print('<<>>') - for license in self._licenses_report['licenses']: - print('%s\t%s\t%s' % (license['name'], license['available'], license['allocated'])) + with SectionWriter('jb_fls_licenses', separator='\t') as writer: + for license in self._licenses_report['licenses']: + writer.append(f"{license['name']}\t{license['available']}\t{license['allocated']}") @cached_property def _check_version(self): @@ -132,7 +129,3 @@ def _get(self, url, payload=None, **kwargs) -> requests.Response: return json except Exception: return resp.text - - -if __name__ == '__main__': - AgentJbFls().run() diff --git a/checks/agent_jb_fls b/libexec/agent_jb_fls old mode 100644 new mode 100755 similarity index 74% rename from checks/agent_jb_fls rename to libexec/agent_jb_fls index c423ff0..30ee3b0 --- a/checks/agent_jb_fls +++ b/libexec/agent_jb_fls @@ -1,6 +1,8 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # -*- encoding: utf-8; py-indent-offset: 4 -*- # +# jb_fls_licenses - JetBrains Floating Licenses check +# # Copyright (C) 2020 Marius Rieder # # This program is free software; you can redistribute it and/or @@ -17,12 +19,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import sys -def agent_jb_fls_arguments(params, hostname, ipaddress): - args = ['--url', params['url']] - if 'token' in params: - args += ['--token', passwordstore_get_cmdline('%s', params['token'])] - return args - +from cmk_addons.plugins.jb_fls.lib.agent import AgentJbFls -special_agent_info['jb_fls'] = agent_jb_fls_arguments +if __name__ == "__main__": + sys.exit(AgentJbFls().run()) diff --git a/package b/package index 1fed409..f855bde 100644 --- a/package +++ b/package @@ -3,24 +3,20 @@ 'description': u'Special Agent to check the JetBrains Floating License Server.', 'download_url': 'https://github.com/jiuka/checkmk_jb_fls/releases', 'files': { - 'agent_based': ['jb_fls.py', 'jb_fls_licenses.py'], - 'agents': ['special/agent_jb_fls'], - 'checkman': [], - 'checks': ['agent_jb_fls'], - 'doc': [], - 'inventory': [], - 'notifications': [], - 'pnp-templates': [], - 'web': [ - 'plugins/metrics/jb_fls.py', - 'plugins/wato/check_parameters_jb_fls.py', - 'plugins/wato/check_parameters_jb_fls_licenses.py', - 'plugins/wato/datasource_jb_fls.py', - ]}, + 'cmk_addons_plugins': [ + 'jb_fls/agent_based/jb_fls.py', + 'jb_fls/agent_based/jb_fls_licenses.py', + 'jb_fls/lib/agent.py', + 'jb_fls/libexec/agent_jb_fls', + 'jb_fls/rulesets/check_parameters.py', + 'jb_fls/rulesets/datasource.py', + 'jb_fls/server_side_calls/special_agent.py', + ], + }, 'name': 'jb_fls', 'title': u'Special Agent for the JetBrains Floating License Server', - 'version': '1.0.3', - 'version.min_required': '2.0.0b6', - 'version.packaged': '2.2.0', - 'version.usable_until': '2.3.0' + 'version': '2.3.0', + 'version.min_required': '2.3.0p3', + 'version.packaged': '2.3.0', + 'version.usable_until': '2.4.0' } diff --git a/rulesets/check_parameters.py b/rulesets/check_parameters.py new file mode 100644 index 0000000..12fa444 --- /dev/null +++ b/rulesets/check_parameters.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- +# +# Copyright (C) 2020 Marius Rieder +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from cmk.rulesets.v1 import Help, Title, Label, Message +from cmk.rulesets.v1.form_specs import ( + CascadingSingleChoice, CascadingSingleChoiceElement, + DefaultValue, + DictElement, + Dictionary, + FixedValue, + InputHint, + Integer, + LevelDirection, + migrate_to_float_simple_levels, + Percentage, + ServiceState, + SimpleLevels, + TimeSpan, + TimeMagnitude, + validators, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, Topic, HostAndItemCondition + + +def _parameter_form_jb_fls(): + return Dictionary( + elements={ + 'updateAvailable': DictElement( + parameter_form=ServiceState( + title=Title('Update Available'), + help_text=Help('State if updates are available'), + prefill=DefaultValue(ServiceState.WARN), + ), + ), + "lastCallHome": DictElement( + parameter_form=SimpleLevels( + title=Title('Maximal time since last call home'), + level_direction=LevelDirection.UPPER, + form_spec_template=TimeSpan( + displayed_magnitudes=[TimeMagnitude.DAY, TimeMagnitude.HOUR, TimeMagnitude.MINUTE] + ), + migrate=migrate_to_float_simple_levels, + prefill_fixed_levels=InputHint(value=(86400 * 1, 86400 * 2)), + ), + required=False, + ), + } + ) + + +rule_spec_jb_fls = CheckParameters( + name='jb_fls', + topic=Topic.APPLICATIONS, + parameter_form=_parameter_form_jb_fls, + title=Title('JetBrains FLS check parameter'), + condition=HostAndItemCondition( + item_title=Title('UID of the JetBrains Floating License Server'), + ), +) + + +def crit_lower_then_warn(value): + if value['crit'] >= value['warn']: + raise validators.ValidationError(Message("The critical level needs to be less or equal then the warning.")) + + +def migrate_to_level_dict(value): + if isinstance(value, tuple): + return dict(warn=value[0], crit=value[1]) + return value + + +def _parameter_form_jb_fls_licenses(): + return Dictionary( + title=Title('JetBrains Floating License Server'), + elements={ + 'licenses': DictElement( + parameter_form=CascadingSingleChoice( + title=Title("Levels for Number of Licenses"), + elements=[ + CascadingSingleChoiceElement( + name="absolute", + title=Title('Absolute levels for unused licenses'), + parameter_form=Dictionary( + elements={ + "warn": DictElement( + parameter_form=Integer( + label=Label('Warning below'), + unit_symbol='unused license', + prefill=InputHint(5), + ), + required=True, + ), + "crit": DictElement( + parameter_form=Integer( + label=Label('Critical below'), + unit_symbol='unused license', + prefill=InputHint(0), + ), + required=True, + ) + }, + custom_validate=[ + crit_lower_then_warn + ], + migrate=migrate_to_level_dict, + ) + ), + CascadingSingleChoiceElement( + name="percentage", + title=Title('Percentual levels for unused licenses'), + parameter_form=Dictionary( + elements={ + "warn": DictElement( + parameter_form=Percentage( + label=Label('Warning below'), + prefill=InputHint(10.0), + ), + required=True, + ), + "crit": DictElement( + parameter_form=Percentage( + label=Label('Critical below'), + prefill=InputHint(0.0), + ), + required=True, + ) + }, + custom_validate=[ + crit_lower_then_warn + ], + migrate=migrate_to_level_dict, + ) + ), + CascadingSingleChoiceElement( + name='always_ok', + title=Title('Always be OK'), + parameter_form=FixedValue(value=False), + ), + CascadingSingleChoiceElement( + name='crit_on_all', + title=Title('Go critical if all licenses are used'), + parameter_form=FixedValue(value=None), + ), + ], + prefill='crit_on_all', + ), + required=True, + ), + } + ) + + +rule_spec_jb_fls_licenses = CheckParameters( + name='jb_fls_licenses', + topic=Topic.APPLICATIONS, + parameter_form=_parameter_form_jb_fls_licenses, + title=Title('Number of used JetBrains Floating licenses'), + condition=HostAndItemCondition(item_title=Title('Name of the JetBrains license, e.g. IntelliJ IDEA Ultimate 12.0')), +) diff --git a/rulesets/datasource.py b/rulesets/datasource.py new file mode 100644 index 0000000..dc52a6b --- /dev/null +++ b/rulesets/datasource.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- +# +# Copyright (C) 2020-2024 Marius Rieder +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from cmk.rulesets.v1 import Title +from cmk.rulesets.v1.form_specs import ( + DictElement, + Dictionary, + migrate_to_password, + Password, + String, + validators, +) +from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic + + +def _form_special_agents_jb_fls() -> Dictionary: + return Dictionary( + title=Title("JetBrains Floating License Server"), + elements = { + "url": DictElement( + parameter_form=String( + title=Title("URL of the JetBrains Floating License Server, e.g. https://host:1212/"), + custom_validate=( + validators.Url( + [validators.UrlProtocol.HTTP, validators.UrlProtocol.HTTPS], + ), + ), + ), + required=True, + ), + "token": DictElement( + parameter_form=Password( + title=Title("JetBrains Floating License Report Token"), + migrate=migrate_to_password + ), + required=False, + ), + }, + ) + + +rule_spec_jb_lfs_datasource = SpecialAgent( + name="jb_fls", + title=Title("JetBrains Floating License Server"), + topic=Topic.APPLICATIONS, + parameter_form=_form_special_agents_jb_fls, +) diff --git a/server_side_calls/special_agent.py b/server_side_calls/special_agent.py new file mode 100644 index 0000000..e34aa51 --- /dev/null +++ b/server_side_calls/special_agent.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- +# +# Copyright (C) 2020-2024 Marius Rieder +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +from collections.abc import Iterator + +from pydantic import BaseModel + +from cmk.server_side_calls.v1 import HostConfig, Secret, SpecialAgentCommand, SpecialAgentConfig + + +class Params(BaseModel): + url: str + token: Secret | None = None + + +def commands_function( + params: Params, + host_config: HostConfig, +) -> Iterator[SpecialAgentCommand]: + command_arguments: list[str | Secret] = ['--url', params.url] + if params.token is not None: + command_arguments += ['--token', params.token.unsafe()] + yield SpecialAgentCommand(command_arguments=command_arguments) + + +special_agent_jb_fls = SpecialAgentConfig( + name='jb_fls', + parameter_parser=Params.model_validate, + commands_function=commands_function, +) diff --git a/tests/integration/agents/special/test_int_agent_jb_fls.py b/tests/integration/test_agent.py similarity index 86% rename from tests/integration/agents/special/test_int_agent_jb_fls.py rename to tests/integration/test_agent.py index 02b3659..b749da7 100644 --- a/tests/integration/agents/special/test_int_agent_jb_fls.py +++ b/tests/integration/test_agent.py @@ -22,12 +22,7 @@ import pytest # type: ignore[import] import requests # noqa: F401 -from importlib.util import spec_from_loader, module_from_spec -from importlib.machinery import SourceFileLoader - -spec = spec_from_loader("agent_jb_fls", SourceFileLoader("agent_jb_fls", "agents/special/agent_jb_fls")) -agent_jb_fls = module_from_spec(spec) -spec.loader.exec_module(agent_jb_fls) +from cmk_addons.plugins.jb_fls.lib.agent import AgentJbFls @pytest.fixture @@ -66,16 +61,13 @@ class Args: def test_AgentJbFls_main(capsys, fls_mock): - agent = agent_jb_fls.AgentJbFls() + agent = AgentJbFls() agent.main(Args()) captured = capsys.readouterr() assert captured.err == "" assert captured.out.splitlines() == [ - '<<>>', - 'AgentOS: JetBrains FLS', - 'Version: 123', '<<>>', 'url\thttp://jbfls:1212/', 'health\t200', @@ -90,7 +82,7 @@ def test_AgentJbFls_main(capsys, fls_mock): def test_AgentJbFls_main_w_token(capsys, fls_mock): - agent = agent_jb_fls.AgentJbFls() + agent = AgentJbFls() args = Args() args.token = 'foo' agent.main(args) @@ -99,9 +91,6 @@ def test_AgentJbFls_main_w_token(capsys, fls_mock): assert captured.err == "" assert captured.out.splitlines() == [ - '<<>>', - 'AgentOS: JetBrains FLS', - 'Version: 123', '<<>>', 'url\thttp://jbfls:1212/', 'health\t200', diff --git a/tests/unit/cmk/base/plugins/agent_based/test_jb_fls.py b/tests/unit/agent_based/test_jb_fls.py similarity index 93% rename from tests/unit/cmk/base/plugins/agent_based/test_jb_fls.py rename to tests/unit/agent_based/test_jb_fls.py index bca54dc..a0524c4 100644 --- a/tests/unit/cmk/base/plugins/agent_based/test_jb_fls.py +++ b/tests/unit/agent_based/test_jb_fls.py @@ -26,7 +26,8 @@ Service, State, ) -from cmk.base.plugins.agent_based import jb_fls + +from cmk_addons.plugins.jb_fls.agent_based import jb_fls @pytest.mark.parametrize('string_table, result', [ @@ -41,6 +42,10 @@ [['connection', 'foo', 'bar'], ['connection', 'alice', 'bob']], {'connection': [('foo', 'bar'), ('alice', 'bob')]} ), + ( + [['url', 'http://host:1212']], + {'connection': [], 'url': 'http://host:1212'} + ), ]) def test_parse_jb_fls(string_table, result): assert jb_fls.parse_jb_fls(string_table) == result @@ -142,6 +147,16 @@ def test_discovery_jb_fls(section, result): Metric('age', 1260, levels=(1800.0, 900.0)), ] ), + ( + {}, + { + 'url': 'https://host:1212/', + 'connection': [], + }, + [ + Result(state=State.UNKNOWN, summary='Server: https://host:1212/ not found'), + ] + ), ]) def test_check_jb_fls(freezer, params, section, result): freezer.move_to('2021-02-11 14:55') diff --git a/tests/unit/cmk/base/plugins/agent_based/test_jb_fls_licenses.py b/tests/unit/agent_based/test_jb_fls_licenses.py similarity index 57% rename from tests/unit/cmk/base/plugins/agent_based/test_jb_fls_licenses.py rename to tests/unit/agent_based/test_jb_fls_licenses.py index f953142..c105153 100644 --- a/tests/unit/cmk/base/plugins/agent_based/test_jb_fls_licenses.py +++ b/tests/unit/agent_based/test_jb_fls_licenses.py @@ -26,7 +26,7 @@ Service, State, ) -from cmk.base.plugins.agent_based import jb_fls_licenses +from cmk_addons.plugins.jb_fls.agent_based import jb_fls_licenses @pytest.mark.parametrize('string_table, result', [ @@ -78,59 +78,84 @@ def test_discovery_jb_fls_licenses(section, result): [] ), ( - 'All Products Pack Toolbox', {}, + 'All Products Pack Toolbox', {'licenses': ('crit_on_all', True)}, JB_FLS_LICENSES_SECTION, [ + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.OK, summary='used: 21'), Metric('licenses', 21.0, levels=(42.0, 42.0), boundaries=(0.0, 42.0)), - Result(state=State.OK, summary='used 21 out of 42 licenses (warn/crit at 42/42)') ] ), ( - 'All Products Pack Toolbox', {'licenses': (5, 0)}, + 'All Products Pack Toolbox', {'licenses': ('absolute', {'warn': 5, 'crit': 0})}, JB_FLS_LICENSES_SECTION, [ + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.OK, summary='used: 21'), Metric('licenses', 21.0, levels=(37.0, 42.0), boundaries=(0.0, 42.0)), - Result(state=State.OK, summary='used 21 out of 42 licenses (warn/crit at 37/42)') ] ), ( - 'All Products Pack Toolbox', {'licenses': (30, 0)}, + 'All Products Pack Toolbox', {'licenses': ('absolute', {'warn': 30, 'crit': 0})}, JB_FLS_LICENSES_SECTION, [ + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.WARN, summary='used: 21 (warn/crit at 12/42)'), Metric('licenses', 21.0, levels=(12.0, 42.0), boundaries=(0.0, 42.0)), - Result(state=State.WARN, summary='used 21 out of 42 licenses (warn/crit at 12/42)') ] ), ( - 'All Products Pack Toolbox', {'licenses': (30, 25)}, + 'All Products Pack Toolbox', {'licenses': ('absolute', {'warn': 30, 'crit': 25})}, JB_FLS_LICENSES_SECTION, [ + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.CRIT, summary='used: 21 (warn/crit at 12/17)'), Metric('licenses', 21.0, levels=(12.0, 17.0), boundaries=(0.0, 42.0)), - Result(state=State.CRIT, summary='used 21 out of 42 licenses (warn/crit at 12/17)') ] ), ( - 'CLion Toolbox', {}, + 'All Products Pack Toolbox', {'licenses': ('percentage', {'warn': 10.0, 'crit': 0.0})}, JB_FLS_LICENSES_SECTION, [ - Metric('licenses', 23.0, levels=(23.0, 23.0), boundaries=(0.0, 23.0)), - Result(state=State.CRIT, summary='used 23 out of 23 licenses (warn/crit at 23/23)') + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.OK, summary='used: 21'), + Metric('licenses', 21.0, levels=(37.0, 42.0), boundaries=(0.0, 42.0)), + ] + ), + ( + 'All Products Pack Toolbox', {'licenses': ('percentage', {'warn': 50.0, 'crit': 0.0})}, + JB_FLS_LICENSES_SECTION, + [ + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.WARN, summary='used: 21 (warn/crit at 21/42)'), + Metric('licenses', 21.0, levels=(21.0, 42.0), boundaries=(0.0, 42.0)), + ] + ), + ( + 'All Products Pack Toolbox', {'licenses': ('percentage', {'warn': 75.0, 'crit': 50.0})}, + JB_FLS_LICENSES_SECTION, + [ + Result(state=State.OK, summary='Licenses available: 42'), + Result(state=State.CRIT, summary='used: 21 (warn/crit at 10/21)'), + Metric('licenses', 21.0, levels=(10.0, 21.0), boundaries=(0.0, 42.0)), ] ), ( - 'CLion Toolbox', {'auto-migration-wrapper-key': False}, + 'CLion Toolbox', {'licenses': ('crit_on_all', True)}, JB_FLS_LICENSES_SECTION, [ + Result(state=State.OK, summary='Licenses available: 23'), + Result(state=State.CRIT, summary='used: 23 (warn/crit at 23/23)'), Metric('licenses', 23.0, levels=(23.0, 23.0), boundaries=(0.0, 23.0)), - Result(state=State.CRIT, summary='used 23 out of 23 licenses (warn/crit at 23/23)') ] ), ( - 'CLion Toolbox', {'licenses': False}, + 'CLion Toolbox', {'licenses': ('always_ok', False)}, JB_FLS_LICENSES_SECTION, [ + Result(state=State.OK, summary='Licenses available: 23'), + Result(state=State.OK, summary='used: 23'), Metric('licenses', 23.0, boundaries=(0.0, 23.0)), - Result(state=State.OK, summary='used 23 out of 23 licenses') ] ), ]) diff --git a/web/plugins/wato/check_parameters_jb_fls.py b/web/plugins/wato/check_parameters_jb_fls.py deleted file mode 100644 index 072a870..0000000 --- a/web/plugins/wato/check_parameters_jb_fls.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- -# -# Copyright (C) 2020 Marius Rieder -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from cmk.gui.i18n import _ -from cmk.gui.valuespec import ( - Age, - Dictionary, - DropdownChoice, - TextAscii, - Tuple, -) -from cmk.gui.plugins.wato import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersApplications, -) - - -def _vs_jb_fls(): - return Dictionary( - title=_('JetBrains Floating License Server'), - elements=[ - ( - 'updateAvailable', - DropdownChoice( - title=_('Update Available'), - choices=[ - (2, _('CRIT')), - (1, _('WARN')), - (0, _('OK')), - (None, _('IGNORE')), - ], - default_value='1', - ) - ), - ( - 'lastCallHome', - Tuple( - title=_('Maximal time since last call home'), - elements=[ - Age(title=_('Warning if older than')), - Age(title=_('Critical if older than')), - ], - ) - ), - ] - ) - - -def _item_spec_jb_fls(): - return TextAscii( - title=_('UID of the JetBrains Floating License Server'), - allow_empty=False, - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - check_group_name='jb_fls', - group=RulespecGroupCheckParametersApplications, - item_spec=_item_spec_jb_fls, - parameter_valuespec=_vs_jb_fls, - title=lambda: _('JetBrains FLS check parameter'), - ) -) diff --git a/web/plugins/wato/check_parameters_jb_fls_licenses.py b/web/plugins/wato/check_parameters_jb_fls_licenses.py deleted file mode 100644 index c14d243..0000000 --- a/web/plugins/wato/check_parameters_jb_fls_licenses.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- -# -# jb_fls_licenses - JetBrains Floating Licenses check -# -# Copyright (C) 2020 Marius Rieder -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.check_parameters.licenses import _vs_license -from cmk.gui.valuespec import Dictionary, TextAscii -from cmk.gui.plugins.wato import ( - RulespecGroupCheckParametersApplications, - CheckParameterRulespecWithItem, - rulespec_registry, -) - - -def _vs_jb_fls_licenses(): - return Dictionary( - title=_('JetBrains Floating License Server'), - elements=[ - ( - 'licenses', - _vs_license() - ), - ], - required_keys = ['licenses'] - ) - - -def _item_spec_jb_fls_licenses(): - return TextAscii( - title=_("Name of the JetBrains license, e.g. IntelliJ IDEA Ultimate 12.0"), - allow_empty=False, - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - check_group_name="jb_fls_licenses", - group=RulespecGroupCheckParametersApplications, - item_spec=_item_spec_jb_fls_licenses, - parameter_valuespec=_vs_jb_fls_licenses, - title=lambda: _("Number of used JetBrains Floating licenses"), - )) diff --git a/web/plugins/wato/datasource_jb_fls.py b/web/plugins/wato/datasource_jb_fls.py deleted file mode 100644 index fe0bc64..0000000 --- a/web/plugins/wato/datasource_jb_fls.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- -# -# Copyright (C) 2020 Marius Rieder -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato import ( - HostRulespec, - IndividualOrStoredPassword, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, - HTTPUrl, -) -from cmk.gui.plugins.wato.datasource_programs import RulespecGroupDatasourceProgramsApps - - -def _valuespec_special_agents_jb_fls(): - return Dictionary( - title=_("JetBrains Floating License Server"), - help = _("This rule selects the JetBrains Floating License agent"), - elements = [ - ( - 'url', - HTTPUrl( - title = _("URL of the JetBrains Floating License Server, e.g. https://host:1212/"), - allow_empty = False, - ) - ), - ( - 'token', - IndividualOrStoredPassword( - title = _("JetBrains Floating License Report Token"), - allow_empty = True, - ) - ), - ], - optional_keys = ['token'], - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupDatasourceProgramsApps, - name='special_agents:jb_fls', - valuespec=_valuespec_special_agents_jb_fls, - ))