[pkg-nagios-changes] [Git][nagios-team/pkg-icinga2][master] 4 commits: New upstream version 2.9.2

Bas Couwenberg gitlab at salsa.debian.org
Fri Sep 28 07:02:55 BST 2018


Bas Couwenberg pushed to branch master at Debian Nagios Maintainer Group / pkg-icinga2


Commits:
e36992a0 by Bas Couwenberg at 2018-09-28T05:30:36Z
New upstream version 2.9.2
- - - - -
505e0188 by Bas Couwenberg at 2018-09-28T05:30:40Z
Merge tag 'upstream/2.9.2'

Upstream version 2.9.2

- - - - -
4c44bb6f by Bas Couwenberg at 2018-09-28T05:31:02Z
New upstream release.

- - - - -
4e5ea9e2 by Bas Couwenberg at 2018-09-28T05:32:52Z
Set distribution to unstable.

- - - - -


15 changed files:

- AUTHORS
- CHANGELOG.md
- RELEASE.md
- VERSION
- debian/changelog
- doc/16-upgrading-icinga-2.md
- lib/base/scriptutils.cpp
- lib/config/configitem.cpp
- lib/methods/clusterzonechecktask.cpp
- lib/perfdata/elasticsearchwriter.cpp
- lib/perfdata/influxdbwriter.cpp
- lib/remote/apilistener.cpp
- lib/remote/filterutility.cpp
- lib/remote/pkiutility.cpp
- lib/remote/pkiutility.hpp


Changes:

=====================================
AUTHORS
=====================================
@@ -31,6 +31,7 @@ Christian Jonak <christian at jonak.org>
 Christian Lehmann <christian_lehmann at gmx.de>
 Christian Loos <cloos at netsandbox.de>
 Christian Schmidt <github at chsc.dk>
+Christopher Schirner <schinken at bamberg.ccc.de>
 Claudio Bilotta <bilottalove at gmail.com>
 Claudio Kuenzler <ck at claudiokuenzler.com>
 Conrad Clement <cclement at printeron.com>


=====================================
CHANGELOG.md
=====================================
@@ -1,5 +1,34 @@
 # Icinga 2.x CHANGELOG
 
+## 2.9.2 (2018-09-26)
+
+### Enhancement
+
+* [#6602](https://github.com/icinga/icinga2/issues/6602) (API, Cluster, PR): Improve TLS handshake exception logging
+* [#6568](https://github.com/icinga/icinga2/issues/6568) (Configuration, PR): Ensure that config object types are committed in dependent load order
+* [#6497](https://github.com/icinga/icinga2/issues/6497) (Configuration, PR): Improve error logging for match/regex/cidr\_match functions and unsupported dictionary usage
+
+### Bug
+
+* [#6596](https://github.com/icinga/icinga2/issues/6596) (Crash, PR): Fix crash on API queries with Fedora 28 hardening and GCC 8
+* [#6581](https://github.com/icinga/icinga2/issues/6581) (Configuration, PR): Shuffle items before config validation
+* [#6569](https://github.com/icinga/icinga2/issues/6569) (DB IDO): Custom Vars not updated after upgrade
+* [#6533](https://github.com/icinga/icinga2/issues/6533) (Crash): Icinga2 crashes after using some api-commands on Fedora 28
+* [#6505](https://github.com/icinga/icinga2/issues/6505) (Cluster, PR): Fix clusterzonecheck if not connected
+* [#6498](https://github.com/icinga/icinga2/issues/6498) (Configuration, PR): Fix regression with MatchAny false conditions on match/regex/cidr\_match
+* [#6496](https://github.com/icinga/icinga2/issues/6496) (Configuration): error with match and type matchany
+
+### Documentation
+
+* [#6590](https://github.com/icinga/icinga2/issues/6590) (DB IDO, Documentation, PR): Update workaround for custom vars
+* [#6572](https://github.com/icinga/icinga2/issues/6572) (Documentation, PR): Add note about workaround for broken custom vars
+
+### Support
+
+* [#6540](https://github.com/icinga/icinga2/issues/6540) (Configuration): Evaluate a fixed config compiler commit order
+* [#6486](https://github.com/icinga/icinga2/issues/6486) (Configuration): Configuration validation w/ ScheduledDowntimes performance decreased in 2.9
+* [#6442](https://github.com/icinga/icinga2/issues/6442) (Configuration): Error while evaluating "assign where match" expression: std::bad\_cast
+
 ## 2.9.1 (2018-07-24)
 
 ### Bug


=====================================
RELEASE.md
=====================================
@@ -26,7 +26,7 @@
 Specify the release version.
 
 ```
-VERSION=2.9.0
+VERSION=2.9.2
 ```
 
 Add your signing key to your Git configuration file, if not already there.
@@ -60,10 +60,11 @@ git log --use-mailmap | grep '^Author:' | cut -f2- -d' ' | sort | uniq > AUTHORS
 
 ## Version <a id="version"></a>
 
-Update the version in the version file:
+Update the version:
 
 ```
 sed -i "s/Version: .*/Version: $VERSION/g" VERSION
+sed -i "s/VERSION=.*/VERSION=$VERSION/g" RELEASE.md
 ```
 
 ## Changelog <a id="changelog"></a>


=====================================
VERSION
=====================================
@@ -1,2 +1,2 @@
-Version: 2.9.1
+Version: 2.9.2
 Revision: 1


=====================================
debian/changelog
=====================================
@@ -1,8 +1,10 @@
-icinga2 (2.9.1-2) UNRELEASED; urgency=medium
+icinga2 (2.9.2-1) unstable; urgency=medium
 
+  * Team upload.
+  * New upstream release.
   * Bump Standards-Version to 4.2.1, no changes.
 
- -- Bas Couwenberg <sebastic at debian.org>  Sun, 05 Aug 2018 21:09:23 +0200
+ -- Bas Couwenberg <sebastic at debian.org>  Fri, 28 Sep 2018 07:32:37 +0200
 
 icinga2 (2.9.1-1) unstable; urgency=medium
 


=====================================
doc/16-upgrading-icinga-2.md
=====================================
@@ -97,6 +97,24 @@ With the removal of RHEL 5 as supported platform, we can finally use real unique
 This is reflected in generating names for e.g. API stage names. Previously it was a handcrafted
 mix of local FQDN, timestamps and random numbers.
 
+### Custom Vars not updating <a id="upgrading-to-2-9-custom-vars-not-updating"></a>
+
+A rare issue preventing the custom vars of objects created prior to 2.9.0 being updated when changed may occur. To
+remedy this, truncate the customvar tables and restart Icinga 2. The following is an example of how to do this with mysql:
+
+```
+$ mysql -uroot -picinga icinga
+MariaDB [icinga]> truncate icinga_customvariables;
+Query OK, 0 rows affected (0.05 sec)
+MariaDB [icinga]> truncate icinga_customvariablestatus;
+Query OK, 0 rows affected (0.03 sec)
+MariaDB [icinga]> exit
+Bye
+$ sudo systemctl restart icinga2
+```
+
+Custom vars should now stay up to date.
+
 
 ## Upgrading to v2.8.2 <a id="upgrading-to-2-8-2"></a>
 


=====================================
lib/base/scriptutils.cpp
=====================================
@@ -108,10 +108,14 @@ bool ScriptUtils::CastBool(const Value& value)
 bool ScriptUtils::Regex(const std::vector<Value>& args)
 {
 	if (args.size() < 2)
-		BOOST_THROW_EXCEPTION(std::invalid_argument("Regular expression and text must be specified."));
+		BOOST_THROW_EXCEPTION(std::invalid_argument("Regular expression and text must be specified for regex()."));
 
 	String pattern = args[0];
 	const Value& argTexts = args[1];
+
+	if (argTexts.IsObjectType<Dictionary>())
+		BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionaries are not supported by regex()."));
+
 	MatchType mode;
 
 	if (args.size() > 2)
@@ -147,7 +151,8 @@ bool ScriptUtils::Regex(const std::vector<Value>& args)
 				return false;
 		}
 
-		return true;
+		/* MatchAny: Nothing matched. MatchAll: Everything matched. */
+		return mode == MatchAll;
 	} else {
 		String text = argTexts;
 		boost::smatch what;
@@ -158,10 +163,14 @@ bool ScriptUtils::Regex(const std::vector<Value>& args)
 bool ScriptUtils::Match(const std::vector<Value>& args)
 {
 	if (args.size() < 2)
-		BOOST_THROW_EXCEPTION(std::invalid_argument("Pattern and text must be specified."));
+		BOOST_THROW_EXCEPTION(std::invalid_argument("Pattern and text must be specified for match()."));
 
 	String pattern = args[0];
 	const Value& argTexts = args[1];
+
+	if (argTexts.IsObjectType<Dictionary>())
+		BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionaries are not supported by match()."));
+
 	MatchType mode;
 
 	if (args.size() > 2)
@@ -189,7 +198,8 @@ bool ScriptUtils::Match(const std::vector<Value>& args)
 				return false;
 		}
 
-		return true;
+		/* MatchAny: Nothing matched. MatchAll: Everything matched. */
+		return mode == MatchAll;
 	} else {
 		String text = argTexts;
 		return Utility::Match(pattern, argTexts);
@@ -199,10 +209,14 @@ bool ScriptUtils::Match(const std::vector<Value>& args)
 bool ScriptUtils::CidrMatch(const std::vector<Value>& args)
 {
 	if (args.size() < 2)
-		BOOST_THROW_EXCEPTION(std::invalid_argument("CIDR and IP address must be specified."));
+		BOOST_THROW_EXCEPTION(std::invalid_argument("CIDR and IP address must be specified for cidr_match()."));
 
 	String pattern = args[0];
 	const Value& argIps = args[1];
+
+	if (argIps.IsObjectType<Dictionary>())
+		BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionaries are not supported by cidr_match()."));
+
 	MatchType mode;
 
 	if (args.size() > 2)
@@ -230,7 +244,8 @@ bool ScriptUtils::CidrMatch(const std::vector<Value>& args)
 				return false;
 		}
 
-		return true;
+		/* MatchAny: Nothing matched. MatchAll: Everything matched. */
+		return mode == MatchAll;
 	} else {
 		String ip = argIps;
 		return Utility::CidrMatch(pattern, ip);


=====================================
lib/config/configitem.cpp
=====================================
@@ -38,6 +38,8 @@
 #include "base/function.hpp"
 #include <sstream>
 #include <fstream>
+#include <algorithm>
+#include <random>
 
 using namespace icinga;
 
@@ -428,26 +430,77 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue
 	if (items.empty())
 		return true;
 
-	for (const auto& ip : items)
-		newItems.push_back(ip.first);
-
-	upq.ParallelFor(items, [](const ItemPair& ip) {
-		ip.first->Commit(ip.second);
-	});
+	// Shuffle all items to evenly distribute them over the threads of the workqueue. This increases perfomance
+	// noticably in environments with lots of objects and available threads.
+	std::shuffle(std::begin(items), std::end(items), std::default_random_engine {});
 
-	upq.Join();
+#ifdef I2_DEBUG
+	Log(LogDebug, "configitem")
+		<< "Committing " << items.size() << " new items.";
+#endif /* I2_DEBUG */
 
-	if (upq.HasExceptions())
-		return false;
+	for (const auto& ip : items)
+		newItems.push_back(ip.first);
 
 	std::set<Type::Ptr> types;
+	std::set<Type::Ptr> completed_types;
 
 	for (const Type::Ptr& type : Type::GetAllTypes()) {
 		if (ConfigObject::TypeInstance->IsAssignableFrom(type))
 			types.insert(type);
 	}
 
-	std::set<Type::Ptr> completed_types;
+	while (types.size() != completed_types.size()) {
+		for (const Type::Ptr& type : types) {
+			if (completed_types.find(type) != completed_types.end())
+				continue;
+
+			bool unresolved_dep = false;
+
+			/* skip this type (for now) if there are unresolved load dependencies */
+			for (const String& loadDep : type->GetLoadDependencies()) {
+				Type::Ptr pLoadDep = Type::GetByName(loadDep);
+				if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) {
+					unresolved_dep = true;
+					break;
+				}
+			}
+
+			if (unresolved_dep)
+				continue;
+
+			int committed_items = 0;
+			upq.ParallelFor(items, [&type, &committed_items](const ItemPair& ip) {
+				const ConfigItem::Ptr& item = ip.first;
+
+				if (item->m_Type != type)
+					return;
+
+				ip.first->Commit(ip.second);
+				committed_items++;
+			});
+
+			upq.Join();
+
+			completed_types.insert(type);
+
+#ifdef I2_DEBUG
+			if (committed_items > 0)
+				Log(LogDebug, "configitem")
+					<< "Committed " << committed_items << " items of type '" << type->GetName() << "'.";
+#endif /* I2_DEBUG */
+
+			if (upq.HasExceptions())
+				return false;
+		}
+	}
+
+#ifdef I2_DEBUG
+	Log(LogDebug, "configitem")
+		<< "Committed " << items.size() << " items.";
+#endif /* I2_DEBUG */
+
+	completed_types.clear();
 
 	while (types.size() != completed_types.size()) {
 		for (const Type::Ptr& type : types) {
@@ -468,7 +521,8 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue
 			if (unresolved_dep)
 				continue;
 
-			upq.ParallelFor(items, [&type](const ItemPair& ip) {
+			int notified_items = 0;
+			upq.ParallelFor(items, [&type, &notified_items](const ItemPair& ip) {
 				const ConfigItem::Ptr& item = ip.first;
 
 				if (!item->m_Object || item->m_Type != type)
@@ -476,6 +530,7 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue
 
 				try {
 					item->m_Object->OnAllConfigLoaded();
+					notified_items++;
 				} catch (const std::exception& ex) {
 					if (!item->m_IgnoreOnError)
 						throw;
@@ -496,11 +551,18 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue
 
 			upq.Join();
 
+#ifdef I2_DEBUG
+			if (notified_items > 0)
+				Log(LogDebug, "configitem")
+					<< "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'.";
+#endif /* I2_DEBUG */
+
 			if (upq.HasExceptions())
 				return false;
 
+			notified_items = 0;
 			for (const String& loadDep : type->GetLoadDependencies()) {
-				upq.ParallelFor(items, [loadDep, &type](const ItemPair& ip) {
+				upq.ParallelFor(items, [loadDep, &type, &notified_items](const ItemPair& ip) {
 					const ConfigItem::Ptr& item = ip.first;
 
 					if (!item->m_Object || item->m_Type->GetName() != loadDep)
@@ -508,14 +570,22 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue
 
 					ActivationScope ascope(item->m_ActivationContext);
 					item->m_Object->CreateChildObjects(type);
+					notified_items++;
 				});
 			}
 
 			upq.Join();
 
+#ifdef I2_DEBUG
+			if (notified_items > 0)
+				Log(LogDebug, "configitem")
+					<< "Sent CreateChildObjects to " << notified_items << " items of type '" << type->GetName() << "'.";
+#endif /* I2_DEBUG */
+
 			if (upq.HasExceptions())
 				return false;
 
+			// Make sure to activate any additionally generated items
 			if (!CommitNewItems(context, upq, newItems))
 				return false;
 		}


=====================================
lib/methods/clusterzonechecktask.cpp
=====================================
@@ -122,23 +122,23 @@ void ClusterZoneCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const Che
 		bytesReceivedPerSecond += endpoint->GetBytesReceivedPerSecond();
 	}
 
-	if (!connected) {
-		cr->SetState(ServiceCritical);
-		cr->SetOutput("Zone '" + zoneName + "' is not connected. Log lag: " + Utility::FormatDuration(zoneLag));
-	} else {
+	if (connected) {
 		cr->SetState(ServiceOK);
 		cr->SetOutput("Zone '" + zoneName + "' is connected. Log lag: " + Utility::FormatDuration(zoneLag));
-	}
 
-	/* Check whether the thresholds have been resolved and compare them */
-	if (missingLagCritical.IsEmpty() && zoneLag > lagCritical) {
+		/* Check whether the thresholds have been resolved and compare them */
+		if (missingLagCritical.IsEmpty() && zoneLag > lagCritical) {
+			cr->SetState(ServiceCritical);
+			cr->SetOutput("Zone '" + zoneName + "' is connected. Log lag: " + Utility::FormatDuration(zoneLag)
+				+ " greater than critical threshold: " + Utility::FormatDuration(lagCritical));
+		} else if (missingLagWarning.IsEmpty() && zoneLag > lagWarning) {
+			cr->SetState(ServiceWarning);
+			cr->SetOutput("Zone '" + zoneName + "' is connected. Log lag: " + Utility::FormatDuration(zoneLag)
+				+ " greater than warning threshold: " + Utility::FormatDuration(lagWarning));
+		}
+	} else {
 		cr->SetState(ServiceCritical);
-		cr->SetOutput("Zone '" + zoneName + "' is connected. Log lag: " + Utility::FormatDuration(zoneLag)
-			+ " greater than critical threshold: " + Utility::FormatDuration(lagCritical));
-	} else if (missingLagWarning.IsEmpty() && zoneLag > lagWarning) {
-		cr->SetState(ServiceWarning);
-		cr->SetOutput("Zone '" + zoneName + "' is connected. Log lag: " + Utility::FormatDuration(zoneLag)
-			+ " greater than warning threshold: " + Utility::FormatDuration(lagWarning));
+		cr->SetOutput("Zone '" + zoneName + "' is not connected. Log lag: " + Utility::FormatDuration(zoneLag));
 	}
 
 	cr->SetPerformanceData(new Array({


=====================================
lib/perfdata/elasticsearchwriter.cpp
=====================================
@@ -434,7 +434,7 @@ void ElasticsearchWriter::SendRequest(const String& body)
 		stream = Connect();
 	} catch (const std::exception& ex) {
 		Log(LogWarning, "ElasticsearchWriter")
-			<< "Flush failed, cannot connect to Elasticsearch.";
+			<< "Flush failed, cannot connect to Elasticsearch: " << DiagnosticInformation(ex, false);
 		return;
 	}
 


=====================================
lib/perfdata/influxdbwriter.cpp
=====================================
@@ -425,7 +425,7 @@ void InfluxdbWriter::Flush()
 		stream = Connect();
 	} catch (const std::exception& ex) {
 		Log(LogWarning, "InfluxDbWriter")
-			<< "Flush failed, cannot connect to InfluxDB.";
+			<< "Flush failed, cannot connect to InfluxDB: " << DiagnosticInformation(ex, false);
 		return;
 	}
 


=====================================
lib/remote/apilistener.cpp
=====================================
@@ -454,9 +454,9 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
 
 	try {
 		tlsStream->Handshake();
-	} catch (const std::exception&) {
+	} catch (const std::exception& ex) {
 		Log(LogCritical, "ApiListener")
-			<< "Client TLS handshake failed (" << conninfo << ")";
+			<< "Client TLS handshake failed (" << conninfo << "): " << DiagnosticInformation(ex, false);
 		tlsStream->Close();
 		return;
 	}


=====================================
lib/remote/filterutility.cpp
=====================================
@@ -127,7 +127,7 @@ static void FilteredAddTarget(ScriptFrame& permissionFrame, Expression *permissi
 	ScriptFrame& frame, Expression *ufilter, std::vector<Value>& result, const String& variableName, const Object::Ptr& target)
 {
 	if (FilterUtility::EvaluateFilter(permissionFrame, permissionFilter, target, variableName) && FilterUtility::EvaluateFilter(frame, ufilter, target, variableName))
-		result.emplace_back(target);
+		result.emplace_back(std::move(target));
 }
 
 void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter)
@@ -206,7 +206,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
 		if (attr == "type")
 			attr = "name";
 
-		if (query->Contains(attr)) {
+		if (query && query->Contains(attr)) {
 			String name = HttpUtility::GetLastParameter(query, attr);
 			Object::Ptr target = provider->GetTargetByName(type, name);
 
@@ -219,7 +219,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
 		attr = provider->GetPluralName(type);
 		boost::algorithm::to_lower(attr);
 
-		if (query->Contains(attr)) {
+		if (query && query->Contains(attr)) {
 			Array::Ptr names = query->Get(attr);
 			if (names) {
 				ObjectLock olock(names);
@@ -235,7 +235,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
 		}
 	}
 
-	if (query->Contains("filter") || result.empty()) {
+	if ((query && query->Contains("filter")) || result.empty()) {
 		if (!query->Contains("type"))
 			BOOST_THROW_EXCEPTION(std::invalid_argument("Type must be specified when using a filter."));
 
@@ -251,26 +251,31 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
 		frame.Sandboxed = true;
 		Dictionary::Ptr uvars = new Dictionary();
 
-		std::unique_ptr<Expression> ufilter;
-
 		if (query->Contains("filter")) {
 			String filter = HttpUtility::GetLastParameter(query, "filter");
-			ufilter = ConfigCompiler::CompileText("<API query>", filter);
-		}
+			std::unique_ptr<Expression> ufilter = ConfigCompiler::CompileText("<API query>", filter);
 
-		Dictionary::Ptr filter_vars = query->Get("filter_vars");
-		if (filter_vars) {
-			ObjectLock olock(filter_vars);
-			for (const Dictionary::Pair& kv : filter_vars) {
-				uvars->Set(kv.first, kv.second);
+			Dictionary::Ptr filter_vars = query->Get("filter_vars");
+			if (filter_vars) {
+				ObjectLock olock(filter_vars);
+				for (const Dictionary::Pair& kv : filter_vars) {
+					uvars->Set(kv.first, kv.second);
+				}
 			}
-		}
-
-		frame.Self = uvars;
 
-		provider->FindTargets(type, std::bind(&FilteredAddTarget,
-			std::ref(permissionFrame), permissionFilter,
-			std::ref(frame), &*ufilter, std::ref(result), variableName, _1));
+			frame.Self = uvars;
+
+			provider->FindTargets(type, std::bind(&FilteredAddTarget,
+				std::ref(permissionFrame), permissionFilter,
+				std::ref(frame), &*ufilter, std::ref(result), variableName, _1));
+		} else {
+			/* Ensure to pass a nullptr as filter expression.
+			 * GCC 8.1.1 on F28 causes problems, see GH #6533.
+			 */
+			provider->FindTargets(type, std::bind(&FilteredAddTarget,
+				std::ref(permissionFrame), permissionFilter,
+				std::ref(frame), nullptr, std::ref(result), variableName, _1));
+		}
 	}
 
 	return result;


=====================================
lib/remote/pkiutility.cpp
=====================================
@@ -187,8 +187,9 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const
 
 	try {
 		stream->Handshake();
-	} catch (const std::exception&) {
-		Log(LogCritical, "cli", "Client TLS handshake failed.");
+	} catch (const std::exception& ex) {
+		Log(LogCritical, "cli")
+			<< "Client TLS handshake failed: " << DiagnosticInformation(ex, false);
 		return 1;
 	}
 


=====================================
lib/remote/pkiutility.hpp
=====================================
@@ -21,6 +21,7 @@
 #define PKIUTILITY_H
 
 #include "remote/i2-remote.hpp"
+#include "base/exception.hpp"
 #include "base/dictionary.hpp"
 #include "base/string.hpp"
 #include <openssl/x509v3.h>



View it on GitLab: https://salsa.debian.org/nagios-team/pkg-icinga2/compare/1165cd0c0d2f9abf3d7aad4f40a2d3592982b735...4e5ea9e2b4d20b084dab3ac7d50e8576f0c701e0

-- 
View it on GitLab: https://salsa.debian.org/nagios-team/pkg-icinga2/compare/1165cd0c0d2f9abf3d7aad4f40a2d3592982b735...4e5ea9e2b4d20b084dab3ac7d50e8576f0c701e0
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-nagios-changes/attachments/20180928/124875fb/attachment-0001.html>


More information about the pkg-nagios-changes mailing list