nagios-charmers team mailing list archive
-
nagios-charmers team
-
Mailing list archive
-
Message #01099
Re: [Merge] ~peppepetra86/charm-nagios:extra_contacts into charm-nagios:master
Review: Needs Fixing
Suggest a slight rework of the extra_contacts validation function to allow for broken names and to find a way to block on truly bad configs. See inline comments.
Diff comments:
> diff --git a/config.yaml b/config.yaml
> index 5221c7a..2e85997 100644
> --- a/config.yaml
> +++ b/config.yaml
> @@ -290,3 +289,21 @@ options:
> description: |
> Defines the IP or Host Name to send snmp traps to. Leave blank (empty) to disable
> the traps functionality.
> + extra_contacts:
> + default: ''
> + type: string
> + description: |
> + Define extra contacts for Nagios. They will be appended in the
> + default administrators contact group. This is useful for adding
> + notification integrations with external services.
> + Contacts are passed as a YAML array, and must have a contact name,
> + a command to be executed for host notifications and a command to
> + be executed for service notifications. These commands may use all
> + relevant Nagios macros ($HOSTNAME$, $SERVICENAME$, etc). Refer to
> + the Nagios documentation for more information.
> + Only [A-Za-z0-9_-] contact names are valid. Be careful with
> + commands, since they are not sanitized.
> + Example
> + - name: contact_name_1
> + host: /custom/command/for/host $HOSTNAME$
would be nice to provide an additional, more useful, working examples like the following
- name: opsteam_email
host: /usr/bin/sendmail -s "alert for $HOSTNAME$" opsteam@xxxxxxxxxxxxx
service: /usr/bin/sendmail -s "alert for $SERVICENAME$" opsteam@xxxxxxxxxxxxx
- name: snmp_collector
host: /path/to/snmp/helper ....
service: /path/to/snmp/helper ....
Otherwise "Name" is misleading to me, the layperson, thinking it's "Drew Freiberger" and not "contact_mechanism_identifier"
> + service: /custom/command/for/service $SERVICENAME$
> diff --git a/hooks/templates/contacts-cfg.tmpl b/hooks/templates/contacts-cfg.tmpl
> index f182fcf..1b34c3b 100644
> --- a/hooks/templates/contacts-cfg.tmpl
> +++ b/hooks/templates/contacts-cfg.tmpl
> @@ -31,6 +31,39 @@ define contact{
> }
> {% endfor %}
>
> +###############################################################################
> +###############################################################################
> +#
> +# EXTRA CONTACTS
> +#
> +###############################################################################
> +###############################################################################
> +
> +{% for contact in extra_contacts %}
> +
> +define contact{
> + contact_name {{ contact.name }}
> + alias {{ contact.alias }}
> + service_notification_period 24x7
> + host_notification_period 24x7
> + service_notification_options w,u,c,r
might be nice to optionally parameterize these for contacts, with wucr being the fallback default if not specified, but that may be for a future request, since this code relies upon only the three fields now to provide proper validation.
> + host_notification_options d,r
> + service_notification_commands notify-service-by-{{ contact.name }}
> + host_notification_commands notify-host-by-{{ contact.name }}
> + }
> +
> +
> +define command {
> + command_name notify-service-by-{{ contact.name }}
> + command_line {{ contact.service }}
> +}
> +
> +define command {
> + command_name notify-host-by-{{ contact.name }}
> + command_line {{ contact.host }}
> +}
> +
> +{% endfor %}
>
> ###############################################################################
> ###############################################################################
> diff --git a/hooks/upgrade_charm.py b/hooks/upgrade_charm.py
> index bbbb8ac..c97315a 100755
> --- a/hooks/upgrade_charm.py
> +++ b/hooks/upgrade_charm.py
> @@ -46,10 +49,19 @@ contactgroup_members = hookenv.config("contactgroup-members")
> # it will be changed by functions
> forced_contactgroup_members = []
>
> +HTTP_ENABLED = ssl_config not in ["only"]
> +SSL_CONFIGURED = ssl_config in ["on", "only"]
for historical config support, given that we used to allow True False for this variable, we should also allow for in 'true'
SSL_CONFIGURED = ssl_config in ["on", "only", "true"]
> +
> +
> # Checks the charm relations for legacy relations
> # Inserts warnings into the log about legacy relations, as they will be removed
> # in the future
this comment block should be removed in favor of the docstring added.
> def warn_legacy_relations():
> + """Checks the charm relations for legacy relations.
> +
> + Inserts warnings into the log about legacy relations, as they will be removed
> + in the future
> + """
> if legacy_relations is not None:
> hookenv.log("Relations have been radically changed."
> " The monitoring interface is not supported anymore.",
> @@ -58,6 +70,45 @@ def warn_legacy_relations():
> "WARNING")
>
>
> +# Parses a list of extra Nagios contacts from a YAML string
> +# Does basic sanitization only
this should be converted to a proper docstring for the function.
> +def get_extra_contacts(yaml_string):
This should be called {parse|validate|filter}_extra_contacts, as it's mainly doing validation and sanitization, not fetching the value.
> + # Final result
> + extra_contacts = []
> +
> + # Valid characters for contact names
> + valid_name_chars = string.ascii_letters + string.digits + '_-'
> +
> + try:
> + extra_contacts_raw = yaml.load(yaml_string, Loader=yaml.SafeLoader) or []
> + if not isinstance(extra_contacts_raw, list):
> + raise ValueError('not a list')
> +
> + for contact in extra_contacts_raw:
> + if {'name', 'host', 'service'} > set(contact.keys()):
> + hookenv.log('Contact {} is missing fields.'.format(contact),
see comment in next block about name maybe not needing to be a required param
> + hookenv.WARNING)
> + continue
Need to set a counter or append the contact to a "failed contact validation" list for reporting below (see comment in the except block below)
> +
> + if set(contact['name']) >= set(valid_name_chars):
> + hookenv.log('Contact name {} is illegal'.format(contact['name']),
I feel like name is just an identifier and if the one passed is invalid or missing, I feel like substituting a generated UUID would work just as well if it is missing or invalid.
> + hookenv.WARNING)
> + continue
> +
> + if '\n' in (contact['host'] + contact['service']):
> + hookenv.log('Line breaks not allowed in commands', hookenv.WARNING)
> + continue
Need to set a counter or append the contact to a "failed contact validation" list for reporting below (see comment in the except block below)
> + contact['name'] = contact['name'].lower()
a space before this block would be super helpful for reading
> + contact['alias'] = contact['name'].capitalize()
> + extra_contacts.append(contact)
> +
> + except (ValueError, yaml.error.YAMLError) as e:
> + hookenv.log('Invalid "extra_contacts" configuration: {}'.format(e),
> + hookenv.WARNING)
Any contacts being "continue"d past should be flagged and noted with the charm status state noting "invalid extra_contacts config, found len(extra_contacts_raw) contacts defined, only implemented len(extra_contacts), check unit logs for detailed errors"
Perhaps this is as simple as
if len(extra_contacts_raw) != len(extra_contacts):
"$block_handler_here"
Unfortunately, it's quite unlikely this triggers in upgrade-charm as the definition of the config option likely doesn't yet exist.
> +
> + return extra_contacts
> +
> +
> # If the charm has extra configuration provided, write that to the
> # proper nagios3 configuration file, otherwise remove the config
> def write_extra_config():
> diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py
> index fafe7f0..c14d60b 100644
> --- a/tests/functional/conftest.py
> +++ b/tests/functional/conftest.py
> @@ -62,29 +61,34 @@ async def current_model():
> @pytest.fixture
> async def get_app(model):
> """Return the application requested."""
> +
these blank lines should go away, unless that's our new black settings.
> async def _get_app(name):
> try:
> return model.applications[name]
> except KeyError:
> raise JujuError("Cannot find application {}".format(name))
> +
ditto
> return _get_app
>
>
> @pytest.fixture
> async def get_unit(model):
> """Return the requested <app_name>/<unit_number> unit."""
> +
> async def _get_unit(name):
> try:
> (app_name, unit_number) = name.split('/')
> return model.applications[app_name].units[unit_number]
> except (KeyError, ValueError):
> raise JujuError("Cannot find unit {}".format(name))
> +
> return _get_unit
>
>
> @pytest.fixture
> async def get_entity(model, get_unit, get_app):
> """Return a unit or an application."""
> +
> async def _get_entity(name):
> try:
> return await get_unit(name)
> @@ -104,6 +109,7 @@ async def run_command(get_unit):
> :param cmd: Command to be run
> :param target: Unit object or unit name string
> """
This doc string should move into the function.
> +
> async def _run_command(cmd, target):
> unit = (
> target
> @@ -123,10 +130,12 @@ async def file_stat(run_command):
> :param path: File path
> :param target: Unit object or unit name string
> """
this docstring should move into the function
> +
> async def _file_stat(path, target):
> cmd = STAT_FILE % path
> results = await run_command(cmd, target)
> return json.loads(results['Stdout'])
> +
> return _file_stat
>
>
> @@ -138,16 +147,19 @@ async def file_contents(run_command):
> :param path: File path
> :param target: Unit object or unit name string
> """
ditto
> +
> async def _file_contents(path, target):
> cmd = 'cat {}'.format(path)
> results = await run_command(cmd, target)
> return results['Stdout']
> +
> return _file_contents
>
>
> @pytest.fixture
> async def reconfigure_app(get_app, model):
> """Apply a different config to the requested app."""
> +
> async def _reconfigure_app(cfg, target):
> application = (
> target
> @@ -157,15 +169,18 @@ async def reconfigure_app(get_app, model):
> await application.set_config(cfg)
> await application.get_config()
> await model.block_until(lambda: application.status == 'active')
> +
> return _reconfigure_app
>
>
> @pytest.fixture
> async def create_group(run_command):
> """Create the UNIX group specified."""
> +
again, blank lines should be removed unless this is black
> async def _create_group(group_name, target):
> cmd = "sudo groupadd %s" % group_name
> await run_command(cmd, target)
> +
> return _create_group
>
>
--
https://code.launchpad.net/~peppepetra86/charm-nagios/+git/charm-nagios/+merge/388119
Your team Nagios Charm developers is subscribed to branch charm-nagios:master.
References