Collection of CheckMK checks (see https://checkmk.com/). All checks and plugins are provided as is. Absolutely no warranty. Send any comments to thl-cmk[at]outlook[dot]com

Skip to content
Snippets Groups Projects
Commit 553ec959 authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

update project

parent 5637149a
No related branches found
No related tags found
No related merge requests found
...@@ -48,10 +48,15 @@ ...@@ -48,10 +48,15 @@
# added options --path-as-is, --ssl-allow-beast, --no-buffer, --no-keepalive, --no-sessionid # added options --path-as-is, --ssl-allow-beast, --no-buffer, --no-keepalive, --no-sessionid
# 2022-03-28: added option --crlf # 2022-03-28: added option --crlf
# added SMTP settings: --mail-auth, --mail-from, --mail-rcpt, --mail-rcpt-allowfails, --upload-file (SMTP) # added SMTP settings: --mail-auth, --mail-from, --mail-rcpt, --mail-rcpt-allowfails, --upload-file (SMTP)
# 2022-04-10: added deployment of cURL executables
# no separate WATO rules per OS necessary anymore
# reworked to make scalable for multiple OSs (THX to andreas.doehler[at]gmail[dot]com)
# removed data adjustments between default and per url settings after WATO rework
# #
from pathlib import Path from pathlib import Path
from typing import List, Tuple, Dict from typing import List, Dict, Any
from dataclasses import dataclass
from cmk.utils import ( from cmk.utils import (
password_store, password_store,
...@@ -65,53 +70,69 @@ from cmk.base.cee.plugins.bakery.bakery_api.v1 import ( ...@@ -65,53 +70,69 @@ from cmk.base.cee.plugins.bakery.bakery_api.v1 import (
) )
bakery_version = '20220325.v0.0.6' @dataclass
class CurlConfig:
base_os: OS
def get_curl_files(conf: Tuple[str, Dict[str, List[any]]]) -> FileGenerator: curl_output: str
if conf[0] == 'linux': temp_path: str
_os = OS.LINUX conf_path: str
_script = 'curl.sh' traget_path_bin: str
_curl_output = '--output /dev/null' plugin_name: str
_temp_path = '/var/tmp/' curl_executable_src: Dict[str, str]
_conf_path = '/etc/check_mk/' curl_executable_dest: str
elif conf[0] == 'windows':
_os = OS.WINDOWS
_script = 'curl.ps1' CURL_CONFIGS: List[CurlConfig] = [
_curl_output = '--output NUL' CurlConfig(
_temp_path = 'c:/windows/temp/' base_os=OS.LINUX,
_conf_path = 'C:/ProgramData/checkmk/agent/config/' curl_output="--output /dev/null",
else: temp_path="/var/tmp/",
_os = None conf_path="/etc/check_mk/",
_script = None traget_path_bin='../bin/curl',
_curl_output = None plugin_name='curl.sh',
_temp_path = None curl_executable_src={'64bit': 'curl-amd64', '32bit': 'curl-i386'},
_conf_path = None curl_executable_dest='../bin/curl',
),
CurlConfig(
base_os=OS.WINDOWS,
curl_output="--output NUL",
temp_path="c:/windows/temp/",
conf_path="C:/ProgramData/checkmk/agent/config/",
traget_path_bin='..\\bin\\curl.exe',
plugin_name='curl.ps1',
curl_executable_src={'64bit': 'curl.exe.64', '32bit': 'curl.exe.32'},
curl_executable_dest='..\\bin\\curl.exe',
),
]
bakery_version = '20220410.v0.0.7'
def get_curl_files(conf) -> FileGenerator:
field_separator: str = '|' # needs matching separator in the shell scripts field_separator: str = '|' # needs matching separator in the shell scripts
options_separator: str = ' '
options = conf[1].copy()
url_cfg_lines = []
# catch pre 20220410 WATO format
options: Dict[str, Any] = conf[1].copy() if type(conf) == tuple else conf
url_cfg_lines = []
url_list = options['url_list'] url_list = options['url_list']
default_settings = options.get('default_settings', {}) default_settings = options.get('default_settings', {})
interval = None interval = options['interval'] * 60 if options.get('interval') else None
timeout = None timeout = options['timeout'] * 60 if options.get('timeout') else None
if options.get('interval'):
interval = options['interval'] * 60 # minutes to seconds
options.pop('interval')
if options.get('timeout'): if options.get('curl_executable'):
timeout = options['timeout'] * 60 # minutes to seconds for curl_config in CURL_CONFIGS:
options.pop('timeout') yield Plugin(
base_os=curl_config.base_os,
source=Path(curl_config.curl_executable_src[str(options['curl_executable'])]),
target=Path(curl_config.curl_executable_dest),
)
yield Plugin(base_os=curl_config.base_os, source=Path('curl-ca-bundle.crt'))
curl_item = 0 curl_item = 0
for entry in url_list: for entry in url_list:
curl_item += 1 curl_item += 1
options_array = []
header_array = []
regex_option = 'no_regex' regex_option = 'no_regex'
save_output = False save_output = False
# get service name and url, first try new format, then old format # get service name and url, first try new format, then old format
...@@ -121,8 +142,6 @@ def get_curl_files(conf: Tuple[str, Dict[str, List[any]]]) -> FileGenerator: ...@@ -121,8 +142,6 @@ def get_curl_files(conf: Tuple[str, Dict[str, List[any]]]) -> FileGenerator:
service_name = entry['service_name'] service_name = entry['service_name']
url = entry['url'] url = entry['url']
options_array.append(f'--url "{url}"')
url_settings = default_settings.copy() url_settings = default_settings.copy()
entry = entry.get('url_settings', {}).copy() entry = entry.get('url_settings', {}).copy()
...@@ -150,394 +169,284 @@ def get_curl_files(conf: Tuple[str, Dict[str, List[any]]]) -> FileGenerator: ...@@ -150,394 +169,284 @@ def get_curl_files(conf: Tuple[str, Dict[str, List[any]]]) -> FileGenerator:
# merge per url settings with default settings # merge per url settings with default settings
url_settings.update(entry) url_settings.update(entry)
# adjust per url settings with default settings (dictionary)
for key in [
'expected_strings',
'header_strings',
'referer',
'request_headers',
]:
if type(url_settings.get(key)) == tuple:
url_settings.update({key: url_settings[key][1]})
for key in [
'limit_rate',
'max_file_size',
'max_time',
'speed_limit',
'speed_time',
'connect_timeout',
'api_key_header',
# 'user_auth',
]:
if type(url_settings.get(key)) == tuple:
if url_settings[key][0] == key:
url_settings.update({key: url_settings[key][1]})
# adjust per url settings with default settings (tuple) for curl_config in CURL_CONFIGS:
for key in [ _os = curl_config.base_os
'http_proxy', _curl_output = curl_config.curl_output
]: _temp_path = curl_config.temp_path
if type(url_settings.get(key)) == tuple: _conf_path = curl_config.conf_path
if type(url_settings[key][1]) == dict:
url_settings.update({key: url_settings[key][1]}) _options = [f'--url "{url}"']
_headers = []
# filter options
if url_settings.get('http_proxy'): # filter options
if url_settings['http_proxy'] == '--noproxy': _options.append(url_settings['get_header_only']) if url_settings.get('get_header_only') else None
options_array.append("--noproxy '*'") _options.append(url_settings['compressed']) if url_settings.get('compressed') else None
else: _options.append(f'--max-time {url_settings["max_time"]}') if url_settings.get('max_time') else None
proxy_protocol, proxy_server, proxy_port = url_settings['http_proxy']['proxy_server'] _options.append(f'--speed-time {url_settings["speed_time"]}') if url_settings.get('speed_time') else None
options_array.append(f'{proxy_protocol} {proxy_server}:{proxy_port}') _options.append(f'--connect-timeout {url_settings["connect_timeout"]}') if url_settings.get('connect_timeout') else None
if url_settings['http_proxy'].get('proxy_auth'): _options.append(f'--user-agent "{url_settings["user_agent"]}"') if url_settings.get('user_agent') else None
proxy_user, proxy_password, proxy_auth = url_settings['http_proxy']['proxy_auth'] _options.append(f'--referer {url_settings["referer"]}') if url_settings.get('referer') else None
if proxy_password[0] == 'store': _options.append(f'{url_settings["tls_ssl_version"]}') if url_settings.get('tls_ssl_version') else None
pw = password_store.extract(proxy_password[1]) _options.append(f'{url_settings["http_version"]}') if url_settings.get('http_version') else None
else:
pw = proxy_password[1] if url_settings.get('max_file_size'):
options_array.append(f'--proxy-user {proxy_user}:{pw}') max_size, unit = url_settings['max_file_size']
options_array.append(proxy_auth) _options.append(f'--max-filesize {max_size}{unit}')
url_settings.pop('http_proxy')
if url_settings.get('speed_limit'):
if url_settings.get('user_auth'): speed, unit = url_settings['speed_limit']
if url_settings['user_auth'][0] == 'user_auth': _options.append(f'--speed-limit {speed * unit}')
user, user_password, user_auth = url_settings['user_auth'][1]
if user_password[0] == 'store': if url_settings.get('limit_rate'):
pw = password_store.extract(user_password[1]) speed, unit = url_settings['limit_rate']
_options.append(f'--limit-rate {speed}{unit}')
if url_settings.get('http_proxy'):
if url_settings['http_proxy'] == '--noproxy':
_options.append("--noproxy '*'")
else: else:
pw = user_password[1] proxy_protocol, proxy_server, proxy_port = url_settings['http_proxy']['proxy_server']
options_array.append(f'--user {user}:{pw}') _options.append(f'{proxy_protocol} {proxy_server}:{proxy_port}')
options_array.append(user_auth) if url_settings['http_proxy'].get('proxy_auth'):
elif url_settings['user_auth'][0] == 'priv_key_auth': proxy_user, proxy_password, proxy_auth = url_settings['http_proxy']['proxy_auth']
user, pass_phrase, priv_key = url_settings['user_auth'][1] if proxy_password[0] == 'store':
options_array.append(f'--user {user}:') pw = password_store.extract(proxy_password[1])
options_array.append(f'--key {_conf_path}curl/curl_item_{curl_item}.priv_key') else:
if pass_phrase[0] == 'store': pw = proxy_password[1]
pw = password_store.extract(pass_phrase[1]) _options.append(f'--proxy-user {proxy_user}:{pw}')
_options.append(proxy_auth)
if url_settings.get('request_headers'):
for header in url_settings['request_headers']:
key, value = header
_headers.append(f'{key}:{value}')
if url_settings.get('api_key_header'):
api_header, api_key = url_settings['api_key_header']
api_header = api_header.rstrip(':')
if api_key[0] == 'store':
api_key = password_store.extract(api_key[1])
else: else:
pw = pass_phrase[1] api_key = api_key[1]
options_array.append(f'--pass {pw}') _headers.append(f'{api_header}:{api_key}')
if url_settings.get('redirects'):
location, location_trusted, max_redirects = url_settings['redirects']
_options.append(f'--location') if location else None
_options.append(f'--location-trusted') if location_trusted else None
_options.append(f'--max-redirs {max_redirects}') if max_redirects else None
if url_settings.get('advanced_settings'):
allow_beast, cr2lf, no_apln, no_buffering, no_npn, no_sessionid, no_keepalive, \
path_as_is, tcp_fastopen, tcp_nodelay = url_settings['advanced_settings']
_options.append(f'--ssl-allow-beast') if allow_beast else None
_options.append(f'--crlf') if cr2lf else None
_options.append(f'--no-alpn') if no_apln else None
_options.append(f'--no-buffer') if no_buffering else None
_options.append(f'--no-npn') if no_npn else None
_options.append(f'--no-sessionid') if no_sessionid else None
_options.append(f'--no-keepalive') if no_keepalive else None
_options.append(f'--path-as-is') if path_as_is else None
_options.append(f'--tcp-fastopen') if tcp_fastopen else None
_options.append(f'--tcp-nodelay') if tcp_nodelay else None
if url_settings.get('ip_address_resolution'):
dns_options = url_settings['ip_address_resolution']
_options.append(dns_options['dns_resolve_names']) if dns_options.get('dns_resolve_names') else None
_options.append(f'--dns-interface {dns_options["dns_source_interface"]}') if dns_options.get('dns_source_interface') else None
_options.append(f'--dns-ipv4-addr {dns_options["dns_source_ipv4"]}') if dns_options.get('dns_source_ipv4') else None
_options.append(f'--dns-ipv6-addr {dns_options["dns_source_ipv6"]}') if dns_options.get('dns_source_ipv6') else None
_options.append(f'--dns-servers {",".join(dns_options["dns_servers"])}') if dns_options.get('dns_servers') else None
if url_settings.get('ftp_settings'):
ftp_options = url_settings['ftp_settings']
_options.append(f'--compressed-ssh') if ftp_options.get('compressed_ssh') else None
_options.append(f'--list-only') if ftp_options.get('list_only') else None
_options.append(f'--use-ascii') if ftp_options.get('use_ascii') else None
_options.append(f'--ftp-create-dirs') if ftp_options.get('ftp_create_dirs') else None
_options.append(f'--ftp-account {ftp_options["ftp_account"]}') if ftp_options.get('ftp_account') else None
_options.append(f'--ftp-alternative-to-user {ftp_options["ftp_alternate_to_user"]}') if ftp_options.get('ftp_alternate_to_user') else None
_options.append(f'--ftp-method {ftp_options["ftp_change_cwd_method"]}') if ftp_options.get('ftp_change_cwd_method') else None
if ftp_options.get('ftp_mode'):
ftp_mode = ftp_options['ftp_mode']
if ftp_mode[0] == 'ftp_pass':
no_send_epsv, send_pret, skip_ip = ftp_mode[1]
_options.append(f'--ftp-pasv')
_options.append(f'--disable-epsv') if no_send_epsv else None
_options.append(f'--ftp-pret') if send_pret else None
_options.append(f'--ftp-skip-pasv-ip') if skip_ip else None
else:
no_send_eprt, active_address = ftp_options[1]
_options.append(f'--disable-eprt') if no_send_eprt else None
_options.append(f'--ftp-port {active_address}') if active_address else None
if ftp_options.get('ftp_ssl_control'):
_options.append(f'--ftp-ssl-control')
ftp_ssl_ccc, ftp_ssl_ccc_mode = ftp_options['ftp_ssl_control']
if ftp_ssl_ccc:
_options.append(f'--ftp-ssl-ccc')
_options.append(f'--ftp-ssl-ccc-mode {ftp_ssl_ccc_mode}')
if url_settings.get('user_auth'):
if url_settings['user_auth'][0] == 'user_auth':
user, user_password, user_auth = url_settings['user_auth'][1]
pw = password_store.extract(user_password[1]) if user_password[0] == 'store' else user_password[1]
_options.append(f'--user {user}:{pw}')
_options.append(user_auth)
elif url_settings['user_auth'][0] == 'priv_key_auth':
user, pass_phrase, priv_key = url_settings['user_auth'][1]
pw = password_store.extract(pass_phrase[1]) if pass_phrase[0] == 'store' else pass_phrase[1]
_options.append(f'--user {user}:')
_options.append(f'--key {_conf_path}curl/curl_item_{curl_item}.priv_key')
_options.append(f'--pass {pw}')
yield PluginConfig(
base_os=_os,
lines=[priv_key],
target=Path(f'curl/curl_item_{curl_item}.priv_key'),
include_header=False,
)
if url_settings.get('expected_strings'):
save_output = True
yield PluginConfig( yield PluginConfig(
base_os=_os, base_os=_os,
lines=[priv_key], lines=url_settings['expected_strings'],
target=Path(f'curl/curl_item_{curl_item}.priv_key'), target=Path(f'curl/curl_item_{curl_item}.search_response'),
include_header=False, include_header=False,
) )
url_settings.pop('user_auth')
if url_settings.get('expected_strings'):
save_output = True
yield PluginConfig(
base_os=_os,
lines=url_settings['expected_strings'],
target=Path(f'curl/curl_item_{curl_item}.search_response'),
include_header=False,
)
url_settings.pop('expected_strings')
if url_settings.get('header_strings'): if url_settings.get('header_strings'):
options_array.append(f'--dump-header {_temp_path}curl_header') _options.append(f'--dump-header {_temp_path}curl_header')
yield PluginConfig(
base_os=_os,
lines=url_settings['header_strings'],
target=Path(f'curl/curl_item_{curl_item}.search_header'),
include_header=False,
)
url_settings.pop('header_strings')
if url_settings.get('limit_rate'):
speed, unit = url_settings['limit_rate']
options_array.append(f'--limit-rate {speed}{unit}')
url_settings.pop('limit_rate')
if url_settings.get('max_file_size'):
max_size, unit = url_settings['max_file_size']
options_array.append(f'--max-filesize {max_size}{unit}')
url_settings.pop('max_file_size')
if url_settings.get('max_time'):
options_array.append(f'--max-time {url_settings["max_time"]}')
url_settings.pop('max_time')
if url_settings.get('speed_limit'):
speed, unit = url_settings['speed_limit']
options_array.append(f'--speed-limit {speed * unit}')
url_settings.pop('speed_limit')
if url_settings.get('speed_time'):
options_array.append(f'--speed-time {url_settings["speed_time"]}')
url_settings.pop('speed_time')
if url_settings.get('connect_timeout'):
options_array.append(f'--connect-timeout {url_settings["connect_timeout"]}')
url_settings.pop('connect_timeout')
if url_settings.get('user_agent'):
user_agent = url_settings['user_agent']
options_array.append(f'--user-agent "{user_agent}"')
url_settings.pop('user_agent')
if url_settings.get('referer'):
referer = url_settings['referer']
options_array.append(f'--referer {referer}')
url_settings.pop('referer')
if url_settings.get('request_headers'):
for header in url_settings['request_headers']:
key, value = header
header_array.append(f'{key}:{value}')
# options_array.append(f'--header "{key}:{value}"')
url_settings.pop('request_headers')
if url_settings.get('api_key_header'):
api_header, api_key = url_settings['api_key_header']
api_header = api_header.rstrip(':')
if api_key[0] == 'store':
api_key = password_store.extract(api_key[1])
else:
api_key = api_key[1]
header_array.append(f'{api_header}:{api_key}')
# options_array.append(f'--header "{api_header}:{api_key}"')
url_settings.pop('api_key_header')
if url_settings.get('redirects'):
location, location_trusted, max_redirects = url_settings['redirects']
if location:
options_array.append(f'--location')
if location_trusted:
options_array.append(f'--location-trusted')
if max_redirects:
options_array.append(f'--max-redirs {int(max_redirects)}')
url_settings.pop('redirects')
if url_settings.get('cert_verify'):
pub_md5 = None
pub_sha256 = None
pub_key = None
try: # 2022-03-23: added ssh settings
insecure, ocsp, no_revoke, cert_chain = url_settings['cert_verify']
except ValueError: # 2022-03-23: added ssh settings
insecure, ocsp, no_revoke, cert_chain, pub_md5, pub_sha256, pub_key = url_settings['cert_verify']
if insecure:
options_array.append(f'--insecure')
if ocsp:
options_array.append(f'--cert-status')
if no_revoke:
options_array.append(f'--ssl-no-revoke')
if cert_chain:
cert_chain = cert_chain[cert_chain.find('-----BEGIN CERTIFICATE-----'):]
yield PluginConfig(
base_os=_os,
lines=[cert_chain],
target=Path(f'curl/curl_item_{curl_item}.ca_cert'),
include_header=False,
)
options_array.append(f'--cacert {_conf_path}curl/curl_item_{curl_item}.ca_cert')
if pub_md5:
options_array.append(f'--hostpubmd5 {pub_md5}')
if pub_sha256:
options_array.append(f'--hostpubsha256 {pub_sha256}')
if pub_key:
yield PluginConfig( yield PluginConfig(
base_os=_os, base_os=_os,
lines=[pub_key], lines=url_settings['header_strings'],
target=Path(f'curl/curl_item_{curl_item}.pub_key'), target=Path(f'curl/curl_item_{curl_item}.search_header'),
include_header=False, include_header=False,
) )
options_array.append(f'--pubkey {_conf_path}curl/curl_item_{curl_item}.pub_key')
url_settings.pop('cert_verify')
if url_settings.get('advanced_settings'):
allow_beast, cr2lf, no_apln, no_buffering, no_npn, no_sessionid, no_keepalive, \
path_as_is, tcp_fastopen, tcp_nodelay = url_settings['advanced_settings']
if allow_beast:
options_array.append(f'--ssl-allow-beast')
if cr2lf:
options_array.append(f'--crlf')
if no_apln:
options_array.append(f'--no-alpn')
if no_buffering:
options_array.append(f'--no-buffer')
if no_npn:
options_array.append(f'--no-npn')
if no_sessionid:
options_array.append(f'--no-sessionid')
if no_keepalive:
options_array.append(f'--no-keepalive')
if path_as_is:
options_array.append(f'--path-as-is')
if tcp_fastopen:
options_array.append(f'--tcp-fastopen')
if tcp_nodelay:
options_array.append(f'--tcp-nodelay')
url_settings.pop('advanced_settings')
if url_settings.get('post_binary'):
header_array.append(f'content-type: {url_settings["post_binary"][0]}')
yield PluginConfig(
base_os=_os,
lines=[url_settings["post_binary"][1]],
target=Path(f'curl/curl_item_{curl_item}.post_binary'),
include_header=False,
)
options_array.append(f'--data-binary @{_conf_path}curl/curl_item_{curl_item}.post_binary')
url_settings.pop('post_binary')
if url_settings.get('regex_response'): if url_settings.get('cert_verify'):
regex_str, no_case, multi_line = url_settings['regex_response'] pub_md5 = None
if regex_str: pub_sha256 = None
save_output = True pub_key = None
try: # 2022-03-23: added ssh settings
insecure, ocsp, no_revoke, cert_chain = url_settings['cert_verify']
except ValueError: # 2022-03-23: added ssh settings
insecure, ocsp, no_revoke, cert_chain, pub_md5, pub_sha256, pub_key = url_settings['cert_verify']
_options.append(f'--insecure') if insecure else None
_options.append(f'--cert-status') if ocsp else None
_options.append(f'--ssl-no-revoke') if no_revoke else None
_options.append(f'--hostpubmd5 {pub_md5}') if pub_md5 else None
_options.append(f'--hostpubsha256 {pub_sha256}') if pub_sha256 else None
if cert_chain:
cert_chain = cert_chain[cert_chain.find('-----BEGIN CERTIFICATE-----'):]
yield PluginConfig(
base_os=_os,
lines=[cert_chain],
target=Path(f'curl/curl_item_{curl_item}.ca_cert'),
include_header=False,
)
_options.append(f'--cacert {_conf_path}curl/curl_item_{curl_item}.ca_cert')
if pub_key:
yield PluginConfig(
base_os=_os,
lines=[pub_key],
target=Path(f'curl/curl_item_{curl_item}.pub_key'),
include_header=False,
)
_options.append(f'--pubkey {_conf_path}curl/curl_item_{curl_item}.pub_key')
if url_settings.get('post_binary'):
_headers.append(f'content-type: {url_settings["post_binary"][0]}')
yield PluginConfig( yield PluginConfig(
base_os=_os, base_os=_os,
lines=[regex_str], lines=[url_settings["post_binary"][1]],
target=Path(f'curl/curl_item_{curl_item}.regex'), target=Path(f'curl/curl_item_{curl_item}.post_binary'),
include_header=False, include_header=False,
) )
if no_case: _options.append(f'--data-binary @{_conf_path}curl/curl_item_{curl_item}.post_binary')
regex_option = 'nocase'
else: if url_settings.get('regex_response'):
regex_option = 'case' regex_str, no_case, multi_line = url_settings['regex_response']
if multi_line: if regex_str:
regex_option += '_multiline' save_output = True
else: yield PluginConfig(
regex_option += '_nomultiline' base_os=_os,
lines=[regex_str],
url_settings.pop('regex_response') target=Path(f'curl/curl_item_{curl_item}.regex'),
include_header=False,
if url_settings.get('ip_address_resolution'): )
dns_options = url_settings['ip_address_resolution'] regex_option = 'nocase' if no_case else 'case'
if dns_options.get('dns_resolve_names'): regex_option += '_multiline' if multi_line else '_nomultiline'
options_array.append(dns_options['dns_resolve_names'])
if dns_options.get('ens_source_interface'): if url_settings.get('mail_settings'):
options_array.append(f'--dns-interface {dns_options["ens_source_interface"]}') mail_options = url_settings['mail_settings']
if dns_options.get('dns_source_ipv4'):
options_array.append(f'--dns-ipv4-addr {dns_options["dns_source_ipv4"]}') _options.append(f'--mail-from {mail_options["mail_from"]}') if mail_options.get('mail_from') else None
if dns_options.get('dns_source_ipv6'): _options.append(f'--mail-auth {mail_options["mail_auth"]}') if mail_options.get('mail_auth') else None
options_array.append(f'--dns-ipv6-addr {dns_options["dns_source_ipv6"]}') _options.append(f'--request {mail_options["request"]}') if mail_options.get('request') else None
if dns_options.get('dns_servers'): _options.append(f'--mail-rcpt-allowfails') if mail_options.get('mail_rpct_allowfail') else None
options_array.append(f'--dns-servers {",".join(dns_options["dns_servers"])}')
url_settings.pop('ip_address_resolution') if mail_options.get('mail_rcpt'):
for address in mail_options['mail_rcpt']:
if url_settings.get('ftp_settings'): _options.append(f'--mail-rcpt {address}')
ftp_options = url_settings['ftp_settings']
if ftp_options.get('ftp_account'): message = []
options_array.append(f'--ftp-account {ftp_options["ftp_account"]}') message.append(mail_options['message']) if mail_options.get('message') else None
if ftp_options.get('ftp_alternate_to_user'): if mail_options.get('request_headers'):
options_array.append(f'--ftp-alternative-to-user {ftp_options["ftp_alternate_to_user"]}') for header, value in mail_options['request_headers']:
if ftp_options.get('ftp_create_dirs'): message.append(f'{header}: {value}')
options_array.append(f'--ftp-create-dirs') if message:
if ftp_options.get('ftp_change_cwd_method'): _options.append(f'--upload-file {_conf_path}curl/curl_item_{curl_item}.message')
options_array.append(f'--ftp-method {ftp_options["ftp_change_cwd_method"]}') yield PluginConfig(
if ftp_options.get('ftp_mode'): base_os=_os,
ftp_mode = ftp_options['ftp_mode'] lines=message,
if ftp_mode[0] == 'ftp_pass': target=Path(f'curl/curl_item_{curl_item}.message'),
options_array.append(f'--ftp-pasv') include_header=False,
no_send_epsv, send_pret, skip_ip = ftp_mode[1] )
if no_send_epsv:
options_array.append(f'--disable-epsv') _options.append(f'--output {_temp_path}curl_output') if save_output else _options.append(_curl_output)
if send_pret:
options_array.append(f'--ftp-pret') if _headers:
if skip_ip:
options_array.append(f'--ftp-skip-pasv-ip')
else:
no_send_eprt, active_address = ftp_options[1]
if no_send_eprt:
options_array.append(f'--disable-eprt')
if active_address:
options_array.append(f'--ftp-port {active_address}')
if ftp_options.get('ftp_ssl_control'):
options_array.append(f'--ftp-ssl-control')
ftp_ssl_ccc, ftp_ssl_ccc_mode = ftp_options['ftp_ssl_control']
if ftp_ssl_ccc:
options_array.append(f'--ftp-ssl-ccc')
options_array.append(f'--ftp-ssl-ccc-mode {ftp_ssl_ccc_mode}')
if ftp_options.get('compressed_ssh'):
options_array.append(f'--compressed-ssh')
if ftp_options.get('list_only'):
options_array.append(f'--list-only')
if ftp_options.get('use_ascii'):
options_array.append(f'--use-ascii')
url_settings.pop('ftp_settings')
if url_settings.get('mail_settings'):
mail_options = url_settings['mail_settings']
if mail_options.get('mail_from'):
options_array.append(f'--mail-from {mail_options["mail_from"]}')
if mail_options.get('mail_rcpt'):
for address in mail_options['mail_rcpt']:
options_array.append(f'--mail-rcpt {address}')
if mail_options.get('mail_auth'):
options_array.append(f'--mail-auth {mail_options["mail_auth"]}')
if mail_options.get('request'):
options_array.append(f'--request {mail_options["request"]}')
if mail_options.get('mail_rpct_allowfail'):
options_array.append(f'--mail-rcpt-allowfails')
message = []
if mail_options.get('request_headers'):
for header, value in mail_options['request_headers']:
message.append(f'{header}: {value}')
if mail_options.get('message'):
message.append(mail_options['message'])
if message:
options_array.append(f'--upload-file {_conf_path}curl/curl_item_{curl_item}.message')
yield PluginConfig( yield PluginConfig(
base_os=_os, base_os=_os,
lines=message, lines=_headers,
target=Path(f'curl/curl_item_{curl_item}.message'), target=Path(f'curl/curl_item_{curl_item}.header'),
include_header=False, include_header=False,
) )
url_settings.pop('mail_settings') _options.append(f'--header @{_conf_path}curl/curl_item_{curl_item}.header')
if save_output:
options_array.append(f'--output {_temp_path}curl_output')
else:
options_array.append(_curl_output)
if header_array:
yield PluginConfig( yield PluginConfig(
base_os=_os, base_os=_os,
lines=header_array, lines=_options,
target=Path(f'curl/curl_item_{curl_item}.header'), target=Path(f'curl/curl_item_{curl_item}.options'),
include_header=False, include_header=True,
) )
options_array.append(f'--header @{_conf_path}curl/curl_item_{curl_item}.header')
for value in url_settings.values():
options_array.append(value)
curl_options = options_separator.join(options_array)
while ' ' in curl_options:
curl_options = curl_options.replace(' ', ' ')
url_cfg_lines.append( url_cfg_lines.append(
f'{service_name}{field_separator}' f'{service_name}{field_separator}'
# f'"{url}"{field_separator}'
# f'{curl_options.strip()}{field_separator}'
f'curl_item_{curl_item}{field_separator}' f'curl_item_{curl_item}{field_separator}'
f'{regex_option}{field_separator}' f'{regex_option}{field_separator}'
) )
for curl_config in CURL_CONFIGS:
yield Plugin(
base_os=curl_config.base_os,
source=Path(curl_config.plugin_name),
interval=interval,
timeout=timeout
)
yield PluginConfig( yield PluginConfig(
base_os=_os, base_os=curl_config.base_os,
lines=options_array, lines=url_cfg_lines,
target=Path(f'curl/curl_item_{curl_item}.options'), target=Path('curl.cfg'),
include_header=True, include_header=False
) )
yield Plugin(
base_os=_os,
source=Path(_script),
target=Path(_script),
interval=interval,
timeout=timeout,
)
yield PluginConfig(
base_os=_os,
lines=url_cfg_lines,
target=Path('curl.cfg'),
include_header=False,
)
register.bakery_plugin( register.bakery_plugin(
name='curl', name='curl',
......
No preview for this file type
...@@ -2,18 +2,21 @@ ...@@ -2,18 +2,21 @@
'description': 'cURL agent plugin\n' 'description': 'cURL agent plugin\n'
'\n' '\n'
'Monitor URLs from the perspective of your monitored hosts, ' 'Monitor URLs from the perspective of your monitored hosts, '
'not the monitoring server ;-). Deployment and configuration ' 'not the monitoring server ;-). \n'
'of the plugin is integrated in the CMK bakery.\n' '\n'
'The plugin is based on a idea by Christian Wirtz '
'doc[at]snowheaven[dot]de and Ingo Hambrock.\n'
'\n' '\n'
'The plugin is based on a idea by based on the work by '
'Christian Wirtz doc[at]snowheaven[dot]de and Ingo Hambrock\n'
'This agent plugin for Linux and Windows is build around the ' 'This agent plugin for Linux and Windows is build around the '
'curl command line tool from https://curl.se.\n' 'curl command line tool from https://curl.se.\n'
'\n' '\n'
'The executable is not included in this package. The plugin ' 'The executable is not included in this package. The plugin '
'will use by default the system provided curl executable.\n' 'will use by default the system provided curl executable.\n'
'\n' '\n'
'Note: this plugin needs at leat curl version 7.70.0 from ' 'Deployment and configuration of the plugin is integrated in '
'the CMK bakery.\n'
'\n'
'Note: this plugin needs at least curl version 7.70.0 from '
'April 29 2020 to work. You can use my CMK package ' 'April 29 2020 to work. You can use my CMK package '
'"curl_executable.mkp" to deploy a compatible version.\n', '"curl_executable.mkp" to deploy a compatible version.\n',
'download_url': 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/curl', 'download_url': 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/curl',
...@@ -23,7 +26,7 @@ ...@@ -23,7 +26,7 @@
'name': 'curl', 'name': 'curl',
'num_files': 6, 'num_files': 6,
'title': 'cURL agent plugin', 'title': 'cURL agent plugin',
'version': '20220406.v0.1.6', 'version': '20220410.v0.1.7',
'version.min_required': '2.0.0', 'version.min_required': '2.0.0',
'version.packaged': '2021.09.20', 'version.packaged': '2021.09.20',
'version.usable_until': None} 'version.usable_until': None}
\ No newline at end of file
...@@ -65,6 +65,11 @@ ...@@ -65,6 +65,11 @@
# 2022-03-25: added options --compressed-ssh, --list-only, --use-ascii # 2022-03-25: added options --compressed-ssh, --list-only, --use-ascii
# added options --path-as-is, --ssl-allow-beast, --no-buffer, --no-keepalive, --no-sessionid # added options --path-as-is, --ssl-allow-beast, --no-buffer, --no-keepalive, --no-sessionid
# 2022-03-28: added options --mail-auth, --mail-from, --mail-rcpt, --mail-rcpt-allowfails, --crlf, --upload-file (SMTP) # 2022-03-28: added options --mail-auth, --mail-from, --mail-rcpt, --mail-rcpt-allowfails, --crlf, --upload-file (SMTP)
# 2022-04-10: added options to deploy cURL executable, no separate rules for curl and curl executable anymore
# windows/linux summarized under on option, no separate WATO rules per OS necessary anymore
# replaced most CascadingDropdown with Alternative to avoid the need for adusting default and url settings
# in the bakery
#
import ipaddress import ipaddress
from cmk.gui.i18n import _ from cmk.gui.i18n import _
...@@ -89,6 +94,7 @@ from cmk.gui.valuespec import ( ...@@ -89,6 +94,7 @@ from cmk.gui.valuespec import (
Age, Age,
# Url, # Url,
UploadOrPasteTextFile, UploadOrPasteTextFile,
Alternative,
) )
from cmk.gui.plugins.wato import ( from cmk.gui.plugins.wato import (
rulespec_registry, rulespec_registry,
...@@ -104,7 +110,7 @@ from cmk.gui.cee.plugins.wato.agent_bakery.rulespecs.utils import ( ...@@ -104,7 +110,7 @@ from cmk.gui.cee.plugins.wato.agent_bakery.rulespecs.utils import (
RulespecGroupMonitoringAgentsAgentPlugins, RulespecGroupMonitoringAgentsAgentPlugins,
) )
bakery_plugin_version = '20220325.v0.0.6' bakery_plugin_version = '20220410.v0.0.7'
# unsafe characters https://www.tutorialspoint.com/html/html_url_encoding.htm # unsafe characters https://www.tutorialspoint.com/html/html_url_encoding.htm
forbidden_chars = '"<>#%{}|\^~[]` \'' forbidden_chars = '"<>#%{}|\^~[]` \''
...@@ -147,7 +153,7 @@ _curl_check_elements = [ ...@@ -147,7 +153,7 @@ _curl_check_elements = [
)), )),
('http_error_code_to_ignore', ('http_error_code_to_ignore',
ListOfStrings( ListOfStrings(
title=_('HTTP:q error codes to ignore'), title=_('HTTP error codes to ignore'),
allow_empty=False, allow_empty=False,
orientation='horizontal', orientation='horizontal',
valuespec=Integer(size=3, minvalue=0, maxvalue=999), valuespec=Integer(size=3, minvalue=0, maxvalue=999),
...@@ -552,21 +558,19 @@ _option_proxy_auth = ('proxy_auth', ...@@ -552,21 +558,19 @@ _option_proxy_auth = ('proxy_auth',
)) ))
_options_proxy = ('http_proxy', _options_proxy = ('http_proxy',
CascadingDropdown( Alternative(
title=_('Configure proxy server'), title=_('Configure proxy server'),
sorted=False, elements=[
choices=[ Dictionary(
('http_proxy', _('Use proxy'), title='Use proxy',
Dictionary( elements=[
title='Use proxy', _option_proxy_server,
elements=[ _option_proxy_auth,
_option_proxy_server, ],
_option_proxy_auth, required_keys=['proxy_server'],
], ),
required_keys=['proxy_server'], FixedValue('--noproxy', title=_('Don\'t use any proxy'), totext=_('')),
)), FixedValue(None, title=_('Don\'t configure an proxy (use system settings)'), totext=_('')),
('--noproxy', _('Don\'t use any proxy')),
('', _('Don\'t configure an proxy (use system settings)')),
], ],
)) ))
...@@ -626,7 +630,7 @@ _option_address_resolution = ('ip_address_resolution', ...@@ -626,7 +630,7 @@ _option_address_resolution = ('ip_address_resolution',
('--ipv4', _('IPv4 only')), ('--ipv4', _('IPv4 only')),
('--ipv6', _('IPv6 only')), ('--ipv6', _('IPv6 only')),
])), ])),
('ens_source_interface', TextUnicode( ('dns_source_interface', TextUnicode(
title=_('Source interface'), title=_('Source interface'),
regex='[0-9a-zA-Z]', regex='[0-9a-zA-Z]',
size=15, size=15,
...@@ -750,12 +754,11 @@ _option_expected_strings = ('expected_strings', ...@@ -750,12 +754,11 @@ _option_expected_strings = ('expected_strings',
valuespec=TextInput(allow_empty=False, regex='[a-zA-Z0-9\.]'), valuespec=TextInput(allow_empty=False, regex='[a-zA-Z0-9\.]'),
)) ))
_url_expected_strings = ('expected_strings', _url_expected_strings = ('expected_strings',
CascadingDropdown( Alternative(
title=_('Strings to expect in response'), title=_('Override strings to expect in response'),
sorted=False, elements=[
choices=[ _option_expected_strings[1],
('expected_strings', _('Expected strings'), _option_expected_strings[1]), FixedValue(None, title=_('Don\'t expect any strings in the response'), totext=_('')),
('', _('Don\'t expect any strings in the response')),
], ],
)) ))
...@@ -767,12 +770,11 @@ _option_header_strings = ('header_strings', ...@@ -767,12 +770,11 @@ _option_header_strings = ('header_strings',
valuespec=TextInput(allow_empty=False, regex='[a-zA-Z0-9\\.]'), valuespec=TextInput(allow_empty=False, regex='[a-zA-Z0-9\\.]'),
)) ))
_url_header_strings = ('header_strings', _url_header_strings = ('header_strings',
CascadingDropdown( Alternative(
title=_('Strings to expect in header'), title=_('Override strings to expect in header'),
sorted=False, elements=[
choices=[ _option_expected_strings[1],
('header_strings', _('Header strings'), _option_expected_strings[1]), FixedValue(None, title=_('Don\'t expect any strings in the header'), totext=_('')),
('', _('Don\'t expect any strings in the header')),
], ],
)) ))
...@@ -804,15 +806,13 @@ _option_request_headers = ('request_headers', ...@@ -804,15 +806,13 @@ _option_request_headers = ('request_headers',
add_label=_('Add header'), add_label=_('Add header'),
movable=True, movable=True,
)) ))
_url_request_headers = ('request_headers', _url_request_headers = ('request_headers',
CascadingDropdown( Alternative(
title=_('Set headers'), title=_('Override default headers'),
sorted=False, elements=[
choices=[ _option_request_headers[1],
('request_headers', _('Override default request headers'), _option_request_headers[1]), FixedValue(None, title=_('Don\'t configure request headers'), totext=_(''))
('', _('Don\'t configure request headers')), ]
],
)) ))
_option_mail_settings = ('mail_settings', _option_mail_settings = ('mail_settings',
...@@ -875,12 +875,11 @@ _option_api_key_header = ('api_key_header', ...@@ -875,12 +875,11 @@ _option_api_key_header = ('api_key_header',
], ],
)) ))
_url_api_key_header = ('api_key_header', _url_api_key_header = ('api_key_header',
CascadingDropdown( Alternative(
title=_('Set API key header'), title=_('Set API key header'),
sorted=False, elements=[
choices=[ _option_api_key_header[1],
('api_key_header', _('API key header'), _option_api_key_header[1],), FixedValue(None, title=_('Don\'t configure an API key header'), totext=_('')),
('', _('Don\'t configure an API key header')),
], ],
)) ))
...@@ -907,12 +906,11 @@ _option_limit_rate = ('limit_rate', ...@@ -907,12 +906,11 @@ _option_limit_rate = ('limit_rate',
orientation='horizontal', orientation='horizontal',
)) ))
_url_limit_rate = ('limit_rate', _url_limit_rate = ('limit_rate',
CascadingDropdown( Alternative(
title=_('Maximum UP-/Download rate'), title=_('Maximum UP-/Download rate'),
sorted=False, elements=[
choices=[ _option_limit_rate[1],
('limit_rate', _('Override default rate limit'), _option_limit_rate[1],), FixedValue(None, title=_('Don\'t configure a rate limit'), totext=_('')),
('', _('No rate limit')),
], ],
)) ))
...@@ -939,12 +937,11 @@ _option_max_file_size = ('max_file_size', ...@@ -939,12 +937,11 @@ _option_max_file_size = ('max_file_size',
orientation='horizontal', orientation='horizontal',
)) ))
_url_max_file_size = ('max_file_size', _url_max_file_size = ('max_file_size',
CascadingDropdown( Alternative(
title=_('Maximum file size'), title=_('Maximum file size'),
sorted=False, elements=[
choices=[ _option_max_file_size[1],
('max_file_size', _('Override default maximum file size'), _option_max_file_size[1],), FixedValue(None, title=_('Don\'t configure a file size limit'), totext=_('')),
('', _('No file size limit')),
], ],
)) ))
...@@ -956,13 +953,11 @@ _option_max_time = ('max_time', ...@@ -956,13 +953,11 @@ _option_max_time = ('max_time',
unit='s', unit='s',
)) ))
_url_max_time = ('max_time', _url_max_time = ('max_time',
CascadingDropdown( Alternative(
title=_('Maximum transfer time'), title=_('Maximum transfer time'),
sorted=False, elements=[
choices=[ _option_max_time[1],
('max_time', _('Override default maximum transfer time'), FixedValue(None, title=_('Don\'t configure a transfer time limit'), totext=_('')),
_option_max_time[1],),
('', _('No transfer time limit')),
], ],
)) ))
...@@ -986,12 +981,11 @@ _option_speed_limit = ('speed_limit', ...@@ -986,12 +981,11 @@ _option_speed_limit = ('speed_limit',
], ],
orientation='horizontal', )) orientation='horizontal', ))
_url_speed_limit = ('speed_limit', _url_speed_limit = ('speed_limit',
CascadingDropdown( Alternative(
title=_('Minimum speed'), title=_('Minimum speed'),
sorted=False, elements=[
choices=[ _option_speed_limit[1],
('speed_limit', _('Override default minimum speed'), _option_speed_limit[1],), FixedValue(None, title=_('Don\'t configure a lower speed limit'), totext=_('')),
('', _('No lower speed limit')),
], ],
)) ))
...@@ -1002,13 +996,11 @@ _option_speed_time = ('speed_time', ...@@ -1002,13 +996,11 @@ _option_speed_time = ('speed_time',
minvalue=1, minvalue=1,
unit='s', )) unit='s', ))
_url_speed_time = ('speed_time', _url_speed_time = ('speed_time',
CascadingDropdown( Alternative(
title=_('Minimum speed time'), title=_('Minimum speed time'),
sorted=False, elements=[
choices=[ _option_speed_time[1],
('speed_time', _('Override default minimum speed time'), FixedValue(None, title=_('Don\'t configure a minimum speed time limit'), totext=_('')),
_option_speed_time[1],),
('', _('No minimum speed time limit')),
], ],
)) ))
...@@ -1019,13 +1011,11 @@ _option_connect_timeout = ('connect_timeout', ...@@ -1019,13 +1011,11 @@ _option_connect_timeout = ('connect_timeout',
minvalue=1, minvalue=1,
unit='s', )) unit='s', ))
_url_connect_timeout = ('connect_timeout', _url_connect_timeout = ('connect_timeout',
CascadingDropdown( Alternative(
title=_('Maximum time to connect'), title=_('Maximum time to connect'),
sorted=False, elements=[
choices=[ _option_connect_timeout[1],
('connect_timeout', _('Override default maximum time to connect'), FixedValue(None, title=_('Don\'t configure a maximum time to connect'), totext=_('')),
_option_connect_timeout[1],),
('', _('Don\'t configure a maximum time to connect')),
], ],
)) ))
...@@ -1044,6 +1034,21 @@ _option_limits = ('limits', ...@@ -1044,6 +1034,21 @@ _option_limits = ('limits',
), ),
title=_('Set connection limits') title=_('Set connection limits')
)) ))
_url_limits = ('limits',
Foldable(
Dictionary(
title=_('Limits'),
elements=[
_url_limit_rate,
_url_max_file_size,
_url_connect_timeout,
_url_max_time,
_url_speed_limit,
_url_speed_time,
]
),
title=_('Override connection limits')
))
_option_user_agent = ('user_agent', _option_user_agent = ('user_agent',
TextUnicode( TextUnicode(
...@@ -1073,14 +1078,12 @@ _option_referer = ('referer', ...@@ -1073,14 +1078,12 @@ _option_referer = ('referer',
forbidden_chars='|" ', forbidden_chars='|" ',
)) ))
_url_referer = ('referer', _url_referer = ('referer',
CascadingDropdown( Alternative(
title=_('Set referer'), title=_('Override default referer'),
sorted=False, elements=[
choices=[ _option_referer[1],
('referer', _('Override default referer'), FixedValue(None, title=_('Don\'t configure a referer'), totext=_(''))
_option_referer[1],), ]
('', _('Don\'t configure a referer')),
],
)) ))
_options_compressed = ('compressed', _options_compressed = ('compressed',
...@@ -1098,21 +1101,6 @@ _url_compressed = ('compressed', ...@@ -1098,21 +1101,6 @@ _url_compressed = ('compressed',
], ],
)) ))
_options_no_npn = ('no_npn',
FixedValue(
'--no-npn',
title=_('Next Protocol Negotiation (NPN)'),
totext=_('Next Protocol Negotiation disabled'),
))
_url_no_npn = ('no_npn',
DropdownChoice(
title=_('Next Protocol Negotiation (NPN)'),
choices=[
('--no-npn', _('Disable Next Protocol Negotiation')),
('', _('Don\'t disable Next Protocol Negotiation')),
],
))
_option_post = ('post_binary', _option_post = ('post_binary',
Tuple( Tuple(
title=_('Send HTTP POST data'), title=_('Send HTTP POST data'),
...@@ -1153,7 +1141,7 @@ _option_url_settings = ('url_settings', ...@@ -1153,7 +1141,7 @@ _option_url_settings = ('url_settings',
_url_user_agent, _url_user_agent,
_url_header_strings, _url_header_strings,
_url_expected_strings, _url_expected_strings,
_option_limits, _url_limits,
_option_address_resolution, _option_address_resolution,
_option_ftp_settings, _option_ftp_settings,
_option_mail_settings, _option_mail_settings,
...@@ -1272,33 +1260,56 @@ _option_plugin_timeout = ('timeout', ...@@ -1272,33 +1260,56 @@ _option_plugin_timeout = ('timeout',
), ),
),) ),)
elements = [ _option_curl_executable = ('curl_executable',
_option_url, DropdownChoice(
_option_plugin_interval, title=_('cURL executable to use'),
_option_plugin_timeout, help=_(
_option_default_settings, 'By default this plugin will use the system provided cURL executable. You can '
] 'decide to deploy a separate cURL executable. If you do so you need to install the '
'optional CMK package curl_executable<version>.mkp.'
),
choices=[
(None, _('Use system provided cURL executable')),
('64bit', _('Deploy 64bit cURL version')),
('32bit', _('Deploy 32bit cURL version')),
]
))
def _transform_forth_agent_config_curl(params):
# transform added 20220410 on removing os specific rules
if type(params) == tuple:
if params[0] in ['linux', 'windows', 'deploy']:
params = params[1]
else:
params = None
return params
def _valuespec_agent_config_curl(): def _valuespec_agent_config_curl():
return CascadingDropdown( return Transform(
title=_('cURL'), Alternative(
sorted=False, title=_('cURL'),
choices=[ elements=[
('linux', Dictionary(
_('Deploy Linux cURL agent plugin'), title=_('Deploy cURL agent plugin'),
Dictionary( elements=[
elements=elements, _option_url,
required_keys=['url_list'], _option_plugin_interval,
)), _option_plugin_timeout,
('windows', _option_curl_executable,
_('Deploy Windows cURL agent plugin'), _option_default_settings,
Dictionary( ],
elements=elements, required_keys=['url_list'],
required_keys=['url_list'], ),
)), FixedValue(
(None, _('Do not deploy the cURL agent plugin')), None,
], title=_('Do not deploy the cURL agent plugin'),
totext=_('The cURL agent plugin will not be deployed')
),
],
),
forth=_transform_forth_agent_config_curl,
) )
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment