From df78b30278f0dc520cbe1dbd099b30ee4a5839d5 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:50:08 +0200 Subject: [PATCH 1/9] Add tags fuctionality to ElasticSearchWriter --- lib/perfdata/elasticsearchwriter.cpp | 97 ++++++++++++++++++++++++++++ lib/perfdata/elasticsearchwriter.hpp | 4 ++ lib/perfdata/elasticsearchwriter.ti | 18 ++++++ 3 files changed, 119 insertions(+) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 9fb2aa90fe8..6eec764ae21 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -5,6 +5,7 @@ #include "remote/url.hpp" #include "icinga/compatutility.hpp" #include "icinga/service.hpp" +#include "icinga/macroprocessor.hpp" #include "icinga/checkcommand.hpp" #include "base/application.hpp" #include "base/defer.hpp" @@ -131,6 +132,42 @@ void ElasticsearchWriter::Pause() ObjectImpl::Pause(); } +void ElasticsearchWriter::AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr tmpl; + if (service && GetServiceTagsTemplate()) { + tmpl = static_pointer_cast(GetServiceTagsTemplate()->ShallowClone()); + } else if (GetHostTagsTemplate()) { + tmpl = static_pointer_cast(GetHostTagsTemplate()->ShallowClone()); + } + + if (tmpl) { + MacroProcessor::ResolverList resolvers; + resolvers.emplace_back("host", host); + if (service) { + resolvers.emplace_back("service", service); + } + + Dictionary::Ptr tags = new Dictionary(); + { + ObjectLock olock(tmpl); + for (const Dictionary::Pair& pair : tmpl) { + String missing_macro; + Value value = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missing_macro); + + if (missing_macro.IsEmpty()) { + tags->Set(pair.first, value); + } + } + } + fields->Set("tags", tags); + } +} + void ElasticsearchWriter::AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { String prefix = "check_result."; @@ -257,6 +294,8 @@ void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& check ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "checkresult", fields, ts); } @@ -307,6 +346,8 @@ void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& check ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "statechange", fields, ts); } @@ -377,6 +418,8 @@ void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Notifi ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "notification", fields, ts); } @@ -683,3 +726,57 @@ String ElasticsearchWriter::FormatTimestamp(double ts) return Utility::FormatDateTime("%Y-%m-%dT%H:%M:%S", ts) + "." + Convert::ToString(milliSeconds) + Utility::FormatDateTime("%z", ts); } + +void ElasticsearchWriter::ValidateHostTagsTemplate(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateHostTagsTemplate(lvalue, utils); + + Dictionary::Ptr tags = lvalue(); + if (tags) { + ObjectLock olock(tags); + for (const Dictionary::Pair& pair : tags) { + if (pair.second.IsObjectType()) { + Array::Ptr arr_object = pair.second; + Array::SizeType arr_index = 0; + ObjectLock arr_lock(arr_object); + for (const Value& arrval : arr_object) { + if (!MacroProcessor::ValidateMacroString(arrval)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first, Convert::ToString(arr_index) }, "Closing $ not found in macro format string '" + arrval + "'.")); + } + arr_index ++; + } + } else { + if (!MacroProcessor::ValidateMacroString(pair.second)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + } + } + } + } +} + +void ElasticsearchWriter::ValidateServiceTagsTemplate(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateServiceTagsTemplate(lvalue, utils); + + Dictionary::Ptr tags = lvalue(); + if (tags) { + ObjectLock olock(tags); + for (const Dictionary::Pair& pair : tags) { + if (pair.second.IsObjectType()) { + Array::Ptr arr_object = pair.second; + Array::SizeType arr_index = 0; + ObjectLock arr_lock(arr_object); + for (const Value& arrval : arr_object) { + if (!MacroProcessor::ValidateMacroString(arrval)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first, Convert::ToString(arr_index) }, "Closing $ not found in macro format string '" + arrval + "'.")); + } + arr_index ++; + } + } else { + if (!MacroProcessor::ValidateMacroString(pair.second)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + } + } + } + } +} diff --git a/lib/perfdata/elasticsearchwriter.hpp b/lib/perfdata/elasticsearchwriter.hpp index a988094d86f..9ecd0fd769f 100644 --- a/lib/perfdata/elasticsearchwriter.hpp +++ b/lib/perfdata/elasticsearchwriter.hpp @@ -23,6 +23,9 @@ class ElasticsearchWriter final : public ObjectImpl static String FormatTimestamp(double ts); + void ValidateHostTagsTemplate(const Lazy &lvalue, const ValidationUtils &utils) override; + void ValidateServiceTagsTemplate(const Lazy &lvalue, const ValidationUtils &utils) override; + protected: void OnConfigLoaded() override; void Resume() override; @@ -37,6 +40,7 @@ class ElasticsearchWriter final : public ObjectImpl std::mutex m_DataBufferMutex; void AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + void AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); diff --git a/lib/perfdata/elasticsearchwriter.ti b/lib/perfdata/elasticsearchwriter.ti index e3b8e27f57f..ed9b24fb5e1 100644 --- a/lib/perfdata/elasticsearchwriter.ti +++ b/lib/perfdata/elasticsearchwriter.ti @@ -29,6 +29,9 @@ class ElasticsearchWriter : ConfigObject [config] bool enable_tls { default {{{ return false; }}} }; + + [config] Dictionary::Ptr host_tags_template; + [config] Dictionary::Ptr service_tags_template; [config] bool insecure_noverify { default {{{ return false; }}} }; @@ -47,4 +50,19 @@ class ElasticsearchWriter : ConfigObject }; }; +validator ElasticsearchWriter { + Dictionary host_tags_template { + String "*"; + Array "*" { + String "*"; + }; + }; + Dictionary service_tags_template { + String "*"; + Array "*" { + String "*"; + }; + }; +}; + } From b844e81cb927fc980a7d510416a9f7e5854d34c9 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:54:47 +0200 Subject: [PATCH 2/9] Documentation for tags fuctionality in ElasticSearchWriter --- doc/09-object-types.md | 8 +++++++- doc/14-features.md | 26 +++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 6b5d355abed..c507620e35b 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1181,7 +1181,7 @@ Configuration Attributes: ### ElasticsearchWriter -Writes check result metrics and performance data to an Elasticsearch instance. +Writes check result metrics and performance data to an Elasticsearch or OpenSearch instance. This configuration object is available as [elasticsearch feature](14-features.md#elasticsearch-writer). Example: @@ -1194,6 +1194,10 @@ object ElasticsearchWriter "elasticsearch" { enable_send_perfdata = true + host_tags_template = { + os_name = "$host.vars.os$" + } + flush_threshold = 1024 flush_interval = 10 } @@ -1215,6 +1219,8 @@ Configuration Attributes: password | String | **Optional.** Basic auth password if Elasticsearch is hidden behind an HTTP proxy. enable\_tls | Boolean | **Optional.** Whether to use a TLS stream. Defaults to `false`. Requires an HTTP proxy. insecure\_noverify | Boolean | **Optional.** Disable TLS peer verification. + host\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch host entries. They will be added under `tags`. + service\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch service entries. They will be added under `tags`. ca\_path | String | **Optional.** Path to CA certificate to validate the remote host. Requires `enable_tls` set to `true`. cert\_path | String | **Optional.** Path to host certificate to present to the remote host for mutual verification. Requires `enable_tls` set to `true`. key\_path | String | **Optional.** Path to host key to accompany the cert\_path. Requires `enable_tls` set to `true`. diff --git a/doc/14-features.md b/doc/14-features.md index 4a6e1ce489a..d6b83ea3544 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -335,16 +335,14 @@ More integrations: #### Elasticsearch Writer This feature forwards check results, state changes and notification events -to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) installation over its HTTP API. +to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) or an [OpenSearch](https://opensearch.org/) installation over its HTTP API. The check results include parsed performance data metrics if enabled. > **Note** > -> Elasticsearch 5.x or 6.x are required. This feature has been successfully tested with -> Elasticsearch 5.6.7 and 6.3.1. - - +> Elasticsearch 5.x, 6.x or Opensearch 2.12.x are required. This feature has been successfully tested with +> Elasticsearch 5.6.7, 6.3.1 and OpenSearch 2.13.0. Enable the feature and restart Icinga 2. @@ -398,6 +396,24 @@ check_result.perfdata..warn check_result.perfdata..crit ``` +Additionaly it is possible to configure custom tags that are applied to the metrics via `host_tags_template` or `service_tags_template`. +Depending on whether the write event was triggered on a service or host object, additional tags are added to the ElasticSearch entries. + +A host metrics entry configured with the following `host_tags_template`: +``` +host_tags_template = { + os_name = "$host.vars.os$" + custom_label = "A Custom Label" + a_list_of_items = [ "foo", "bar" ] +} +``` +Will in addition to the above mentioned lines also contain: +``` +tags.os_name = "Linux" +tags.custom_label = "A Custom Label" +tags.a_list_of_items = [ "foo", "bar" ] +``` + #### Elasticsearch in Cluster HA Zones The Elasticsearch feature supports [high availability](06-distributed-monitoring.md#distributed-monitoring-high-availability-features) From 2f3c2fa06f62c7fedc9e4ec8424b776f58bd3b25 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:56:23 +0200 Subject: [PATCH 3/9] Update AUTHORS --- .mailmap | 1 + AUTHORS | 1 + 2 files changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index df55f3a0f54..cef6d6cf792 100644 --- a/.mailmap +++ b/.mailmap @@ -63,3 +63,4 @@ Robin O'Brien Roman Gerhardt Sebastian Chrostek Thomas Gelf +Sebastian Grund diff --git a/AUTHORS b/AUTHORS index 3253f3ec316..91aa3738240 100644 --- a/AUTHORS +++ b/AUTHORS @@ -255,6 +255,7 @@ Sascha Westermann Sebastian Brückner Sebastian Chrostek Sebastian Eikenberg +Sebastian Grund Sebastian Marsching Silas <67681686+Tqnsls@users.noreply.github.com> Simon Murray From 6cd12c09267ee6487db158c7f24bd5e408f017e5 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Tue, 24 Sep 2024 17:27:55 +0200 Subject: [PATCH 4/9] Change variable names to camel Case and rearange Block/Scopes --- lib/perfdata/elasticsearchwriter.cpp | 48 +++++++++++----------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 6eec764ae21..2ca05ce10f3 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -153,15 +153,13 @@ void ElasticsearchWriter::AddTemplateTags(const Dictionary::Ptr& fields, const C } Dictionary::Ptr tags = new Dictionary(); - { - ObjectLock olock(tmpl); - for (const Dictionary::Pair& pair : tmpl) { - String missing_macro; - Value value = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missing_macro); - - if (missing_macro.IsEmpty()) { - tags->Set(pair.first, value); - } + ObjectLock olock(tmpl); + for (const Dictionary::Pair& pair : tmpl) { + String missingMacro; + Value value = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missingMacro); + + if (missingMacro.IsEmpty()) { + tags->Set(pair.first, value); } } fields->Set("tags", tags); @@ -736,19 +734,15 @@ void ElasticsearchWriter::ValidateHostTagsTemplate(const Lazy& ObjectLock olock(tags); for (const Dictionary::Pair& pair : tags) { if (pair.second.IsObjectType()) { - Array::Ptr arr_object = pair.second; - Array::SizeType arr_index = 0; - ObjectLock arr_lock(arr_object); - for (const Value& arrval : arr_object) { - if (!MacroProcessor::ValidateMacroString(arrval)) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first, Convert::ToString(arr_index) }, "Closing $ not found in macro format string '" + arrval + "'.")); + Array::Ptr arrObject = pair.second; + ObjectLock arrLock(arrObject); + for (const Value& arrValue : arrObject) { + if (!MacroProcessor::ValidateMacroString(arrValue)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + arrValue + "'.")); } - arr_index ++; } - } else { - if (!MacroProcessor::ValidateMacroString(pair.second)) { + } else if (!MacroProcessor::ValidateMacroString(pair.second)) { BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); - } } } } @@ -763,19 +757,15 @@ void ElasticsearchWriter::ValidateServiceTagsTemplate(const Lazy()) { - Array::Ptr arr_object = pair.second; - Array::SizeType arr_index = 0; - ObjectLock arr_lock(arr_object); - for (const Value& arrval : arr_object) { - if (!MacroProcessor::ValidateMacroString(arrval)) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first, Convert::ToString(arr_index) }, "Closing $ not found in macro format string '" + arrval + "'.")); + Array::Ptr arrObject = pair.second; + ObjectLock arrLock(arrObject); + for (const Value& arrValue : arrObject) { + if (!MacroProcessor::ValidateMacroString(arrValue)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + arrValue + "'.")); } - arr_index ++; } - } else { - if (!MacroProcessor::ValidateMacroString(pair.second)) { + } else if (!MacroProcessor::ValidateMacroString(pair.second)) { BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); - } } } } From 4c959e2b96234e236ede730531d68dcd268c1ae2 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Tue, 24 Sep 2024 17:28:24 +0200 Subject: [PATCH 5/9] Remove Entry from .mailmap --- .mailmap | 1 - 1 file changed, 1 deletion(-) diff --git a/.mailmap b/.mailmap index cef6d6cf792..df55f3a0f54 100644 --- a/.mailmap +++ b/.mailmap @@ -63,4 +63,3 @@ Robin O'Brien Roman Gerhardt Sebastian Chrostek Thomas Gelf -Sebastian Grund From 99a4dc43bdedeb21880214d98e98642676285a8e Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Tue, 24 Sep 2024 17:32:44 +0200 Subject: [PATCH 6/9] Corrected indents --- lib/perfdata/elasticsearchwriter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 2ca05ce10f3..749e23bceb5 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -742,7 +742,7 @@ void ElasticsearchWriter::ValidateHostTagsTemplate(const Lazy& } } } else if (!MacroProcessor::ValidateMacroString(pair.second)) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); } } } @@ -765,7 +765,7 @@ void ElasticsearchWriter::ValidateServiceTagsTemplate(const Lazy Date: Wed, 25 Sep 2024 15:36:45 +0200 Subject: [PATCH 7/9] doc/14-features.md: correct Elasticsearch versions --- doc/14-features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/14-features.md b/doc/14-features.md index d6b83ea3544..77a807fbb61 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -341,8 +341,8 @@ The check results include parsed performance data metrics if enabled. > **Note** > -> Elasticsearch 5.x, 6.x or Opensearch 2.12.x are required. This feature has been successfully tested with -> Elasticsearch 5.6.7, 6.3.1 and OpenSearch 2.13.0. +> Elasticsearch 7.x, 8.x or Opensearch 2.12.x are required. This feature has been successfully tested with +> Elasticsearch 7.17.10, 8.8.1 and OpenSearch 2.13.0. Enable the feature and restart Icinga 2. From c661ba257eadccfab5c0bd0e2f778601354f1496 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Thu, 26 Sep 2024 11:09:40 +0200 Subject: [PATCH 8/9] Add ElasticSearch tags to root element instaed of a dictionary-field --- doc/09-object-types.md | 4 ++-- doc/14-features.md | 6 +++--- lib/perfdata/elasticsearchwriter.cpp | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index c507620e35b..d47daf37fb6 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1219,8 +1219,8 @@ Configuration Attributes: password | String | **Optional.** Basic auth password if Elasticsearch is hidden behind an HTTP proxy. enable\_tls | Boolean | **Optional.** Whether to use a TLS stream. Defaults to `false`. Requires an HTTP proxy. insecure\_noverify | Boolean | **Optional.** Disable TLS peer verification. - host\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch host entries. They will be added under `tags`. - service\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch service entries. They will be added under `tags`. + host\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch host entries. + service\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch service entries. ca\_path | String | **Optional.** Path to CA certificate to validate the remote host. Requires `enable_tls` set to `true`. cert\_path | String | **Optional.** Path to host certificate to present to the remote host for mutual verification. Requires `enable_tls` set to `true`. key\_path | String | **Optional.** Path to host key to accompany the cert\_path. Requires `enable_tls` set to `true`. diff --git a/doc/14-features.md b/doc/14-features.md index 77a807fbb61..43c4eee98d4 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -409,9 +409,9 @@ host_tags_template = { ``` Will in addition to the above mentioned lines also contain: ``` -tags.os_name = "Linux" -tags.custom_label = "A Custom Label" -tags.a_list_of_items = [ "foo", "bar" ] +os_name = "Linux" +custom_label = "A Custom Label" +a_list_of_items = [ "foo", "bar" ] ``` #### Elasticsearch in Cluster HA Zones diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 749e23bceb5..069715d27c2 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -152,17 +152,15 @@ void ElasticsearchWriter::AddTemplateTags(const Dictionary::Ptr& fields, const C resolvers.emplace_back("service", service); } - Dictionary::Ptr tags = new Dictionary(); ObjectLock olock(tmpl); for (const Dictionary::Pair& pair : tmpl) { String missingMacro; Value value = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missingMacro); if (missingMacro.IsEmpty()) { - tags->Set(pair.first, value); + fields->Set(pair.first, value); } } - fields->Set("tags", tags); } } From 468c7d04fadaa7b29074024e22278080f243253e Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Fri, 27 Sep 2024 13:00:33 +0200 Subject: [PATCH 9/9] Add host tags template only for host checks --- lib/perfdata/elasticsearchwriter.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 069715d27c2..94d60df3bd4 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -139,8 +139,10 @@ void ElasticsearchWriter::AddTemplateTags(const Dictionary::Ptr& fields, const C tie(host, service) = GetHostService(checkable); Dictionary::Ptr tmpl; - if (service && GetServiceTagsTemplate()) { - tmpl = static_pointer_cast(GetServiceTagsTemplate()->ShallowClone()); + if (service) { + if (GetServiceTagsTemplate()) { + tmpl = static_pointer_cast(GetServiceTagsTemplate()->ShallowClone()); + } } else if (GetHostTagsTemplate()) { tmpl = static_pointer_cast(GetHostTagsTemplate()->ShallowClone()); }