/****************************************************************************** * Icinga 2 * * Copyright (C) 2012-2016 Icinga Development Team (https://www.icinga.org/) * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software Foundation * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ #include "cli/nodeupdateconfigcommand.hpp" #include "cli/nodeutility.hpp" #include "cli/repositoryutility.hpp" #include "cli/variableutility.hpp" #include "base/logger.hpp" #include "base/console.hpp" #include "base/application.hpp" #include "base/tlsutility.hpp" #include "base/json.hpp" #include "base/objectlock.hpp" #include #include #include #include #include #include using namespace icinga; namespace po = boost::program_options; REGISTER_CLICOMMAND("node/update-config", NodeUpdateConfigCommand); String NodeUpdateConfigCommand::GetDescription(void) const { return "Update Icinga 2 node config."; } String NodeUpdateConfigCommand::GetShortDescription(void) const { return "update node config"; } ImpersonationLevel NodeUpdateConfigCommand::GetImpersonationLevel(void) const { return ImpersonateRoot; } /** * The entry point for the "node update-config" CLI command. * * @returns An exit status. */ int NodeUpdateConfigCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const { //If there are changes pending, abort the current operation if (RepositoryUtility::ChangeLogHasPendingChanges()) { Log(LogWarning, "cli") << "There are pending changes for commit.\n" << "Please review and commit them using 'icinga2 repository commit [--simulate]'\n" << "or drop them using 'icinga2 repository clear-changes' before proceeding."; return 1; } String inventory_path = NodeUtility::GetRepositoryPath() + "/inventory.index"; Dictionary::Ptr old_inventory = new Dictionary(); if (Utility::PathExists(inventory_path)) { old_inventory = Utility::LoadJsonFile(inventory_path); } Dictionary::Ptr inventory = new Dictionary(); Log(LogInformation, "cli") << "Updating node configuration for "; NodeUtility::PrintNodes(std::cout); /* cache all existing object configs only once and pass it to AddObject() */ std::vector object_paths = RepositoryUtility::GetObjects(); /* cache all existing changes only once and pass it to AddObject() */ Array::Ptr changes = new Array(); RepositoryUtility::GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, changes)); std::vector nodes = NodeUtility::GetNodes(); /* first make sure that all nodes are valid and should not be removed */ BOOST_FOREACH(const Dictionary::Ptr& node, nodes) { Dictionary::Ptr repository = node->Get("repository"); String zone = node->Get("zone"); String endpoint = node->Get("endpoint"); String node_name = endpoint; /* store existing structure in index */ inventory->Set(endpoint, node); } if (old_inventory) { /* check if there are objects inside the old_inventory which do not exist anymore */ ObjectLock ulock(old_inventory); BOOST_FOREACH(const Dictionary::Pair& old_node_objs, old_inventory) { String old_node_name = old_node_objs.first; /* check if the node was dropped */ if (!inventory->Contains(old_node_name)) { Log(LogInformation, "cli") << "Node update found old node '" << old_node_name << "'. Removing it and all of its hosts/services."; //TODO Remove an node and all of his hosts Dictionary::Ptr old_node = old_node_objs.second; Dictionary::Ptr old_node_repository = old_node->Get("repository"); if (old_node_repository) { ObjectLock olock(old_node_repository); BOOST_FOREACH(const Dictionary::Pair& kv, old_node_repository) { String host = kv.first; Dictionary::Ptr host_attrs = new Dictionary(); host_attrs->Set("name", host); RepositoryUtility::RemoveObject(host, "Host", host_attrs, changes); //this removes all services for this host as well } } String zone = old_node->Get("zone"); String endpoint = old_node->Get("endpoint"); Dictionary::Ptr zone_attrs = new Dictionary(); zone_attrs->Set("name", zone); RepositoryUtility::RemoveObject(zone, "Zone", zone_attrs, changes); Dictionary::Ptr endpoint_attrs = new Dictionary(); endpoint_attrs->Set("name", endpoint); RepositoryUtility::RemoveObject(endpoint, "Endpoint", endpoint_attrs, changes); } else { /* get the current node */ Dictionary::Ptr new_node = inventory->Get(old_node_name); Dictionary::Ptr new_node_repository = new_node->Get("repository"); Dictionary::Ptr old_node = old_node_objs.second; Dictionary::Ptr old_node_repository = old_node->Get("repository"); if (old_node_repository) { ObjectLock xlock(old_node_repository); BOOST_FOREACH(const Dictionary::Pair& kv, old_node_repository) { String old_host = kv.first; if (old_host == "localhost") { Log(LogWarning, "cli") << "Ignoring host '" << old_host << "'. Please make sure to configure a unique name on your node '" << old_node_name << "'."; continue; } /* check against black/whitelist before trying to remove host */ if (NodeUtility::CheckAgainstBlackAndWhiteList("blacklist", old_node_name, old_host, Empty) && !NodeUtility::CheckAgainstBlackAndWhiteList("whitelist", old_node_name, old_host, Empty)) { Log(LogWarning, "cli") << "Host '" << old_node_name << "' on node '" << old_node_name << "' is blacklisted, but not whitelisted. Skipping."; continue; } if (!new_node_repository->Contains(old_host)) { Log(LogInformation, "cli") << "Node update found old host '" << old_host << "' on node '" << old_node_name << "'. Removing it."; Dictionary::Ptr host_attrs = new Dictionary(); host_attrs->Set("name", old_host); RepositoryUtility::RemoveObject(old_host, "Host", host_attrs, changes); //this will remove all services for this host too } else { /* host exists, now check all services for this host */ Array::Ptr old_services = kv.second; Array::Ptr new_services = new_node_repository->Get(old_host); ObjectLock ylock(old_services); BOOST_FOREACH(const String& old_service, old_services) { /* check against black/whitelist before trying to remove service */ if (NodeUtility::CheckAgainstBlackAndWhiteList("blacklist", old_node_name, old_host, old_service) && !NodeUtility::CheckAgainstBlackAndWhiteList("whitelist", old_node_name, old_host, old_service)) { Log(LogWarning, "cli") << "Service '" << old_service << "' on host '" << old_host << "' on node '" << old_node_name << "' is blacklisted, but not whitelisted. Skipping."; continue; } if (!new_services->Contains(old_service)) { Log(LogInformation, "cli") << "Node update found old service '" << old_service << "' on host '" << old_host << "' on node '" << old_node_name << "'. Removing it."; Dictionary::Ptr service_attrs = new Dictionary(); service_attrs->Set("name", old_service); service_attrs->Set("host_name", old_host); RepositoryUtility::RemoveObject(old_service, "Service", service_attrs, changes); } } } } } } } } /* next iterate over all nodes and add hosts/services */ BOOST_FOREACH(const Dictionary::Ptr& node, nodes) { Dictionary::Ptr repository = node->Get("repository"); String zone = node->Get("zone"); String endpoint = node->Get("endpoint"); String node_name = endpoint; Dictionary::Ptr host_services = new Dictionary(); if (NodeUtility::CheckAgainstBlackAndWhiteList("blacklist", node_name, "*", Empty) && !NodeUtility::CheckAgainstBlackAndWhiteList("whitelist", node_name, "*", Empty)) { Log(LogWarning, "cli") << "Skipping node '" << node_name << "' on blacklist."; continue; } Log(LogInformation, "cli") << "Adding host '" << zone << "' to the repository."; Dictionary::Ptr host_attrs = new Dictionary(); host_attrs->Set("__name", zone); host_attrs->Set("name", zone); host_attrs->Set("check_command", "cluster-zone"); Array::Ptr host_imports = new Array(); host_imports->Add("satellite-host"); //default host node template host_attrs->Set("import", host_imports); if (!RepositoryUtility::AddObject(object_paths, zone, "Host", host_attrs, changes, false)) { Log(LogWarning, "cli") << "Cannot add node host '" << zone << "' to the config repository!\n"; } if (repository) { ObjectLock olock(repository); BOOST_FOREACH(const Dictionary::Pair& kv, repository) { String host = kv.first; String host_pattern = host + ".conf"; bool skip_host = false; if (host == "localhost") { Log(LogWarning, "cli") << "Ignoring host '" << host << "'. Please make sure to configure a unique name on your node '" << endpoint << "'."; continue; } BOOST_FOREACH(const String& object_path, object_paths) { if (object_path.Contains(host_pattern)) { Log(LogNotice, "cli") << "Host '" << host << "' already exists."; skip_host = true; break; } } /* host has already been created above */ if (host == zone) skip_host = true; bool host_was_blacklisted = false; /* check against black/whitelist before trying to add host */ if (NodeUtility::CheckAgainstBlackAndWhiteList("blacklist", node_name, host, Empty) && !NodeUtility::CheckAgainstBlackAndWhiteList("whitelist", node_name, host, Empty)) { Log(LogWarning, "cli") << "Host '" << host << "' on node '" << node_name << "' is blacklisted, but not whitelisted. Not creating host object."; skip_host = true; host_was_blacklisted = true; //check this for services on this blacklisted host } if (!skip_host) { /* add a new host to the config repository */ Dictionary::Ptr host_attrs = new Dictionary(); host_attrs->Set("__name", host); host_attrs->Set("name", host); if (host == zone) host_attrs->Set("check_command", "cluster-zone"); else { host_attrs->Set("check_command", "dummy"); host_attrs->Set("zone", zone); } Array::Ptr host_imports = new Array(); host_imports->Add("satellite-host"); //default host node template host_attrs->Set("import", host_imports); RepositoryUtility::AddObject(object_paths, host, "Host", host_attrs, changes, false); } /* special condition: what if the host was blacklisted before, but the services should be generated? */ if (host_was_blacklisted) { Log(LogNotice, "cli") << "Host '" << host << "' was blacklisted. Won't generate any services."; continue; } Array::Ptr services = kv.second; if (services->GetLength() == 0) { Log(LogNotice, "cli") << "Host '" << host << "' without services."; continue; } ObjectLock xlock(services); BOOST_FOREACH(const String& service, services) { bool skip_service = false; String service_pattern = host + "/" + service + ".conf"; BOOST_FOREACH(const String& object_path, object_paths) { if (object_path.Contains(service_pattern)) { Log(LogNotice, "cli") << "Service '" << service << "' on Host '" << host << "' already exists."; skip_service = true; break; } } /* check against black/whitelist before trying to add service */ if (NodeUtility::CheckAgainstBlackAndWhiteList("blacklist", endpoint, host, service) && !NodeUtility::CheckAgainstBlackAndWhiteList("whitelist", endpoint, host, service)) { Log(LogWarning, "cli") << "Service '" << service << "' on host '" << host << "' on node '" << node_name << "' is blacklisted, but not whitelisted. Not creating service object."; skip_service = true; } if (skip_service) continue; /* add a new service for this host to the config repository */ Dictionary::Ptr service_attrs = new Dictionary(); String long_name = host + "!" + service; //use NameComposer? service_attrs->Set("__name", long_name); service_attrs->Set("name", service); service_attrs->Set("host_name", host); //Required for host-service relation service_attrs->Set("check_command", "dummy"); service_attrs->Set("zone", zone); Array::Ptr service_imports = new Array(); service_imports->Add("satellite-service"); //default service node template service_attrs->Set("import", service_imports); if (!RepositoryUtility::AddObject(object_paths, service, "Service", service_attrs, changes, false)) continue; } } } /* write a new zone and endpoint for the node */ Dictionary::Ptr endpoint_attrs = new Dictionary(); endpoint_attrs->Set("__name", endpoint); endpoint_attrs->Set("name", endpoint); Dictionary::Ptr settings = node->Get("settings"); if (settings) { if (settings->Contains("host")) endpoint_attrs->Set("host", settings->Get("host")); if (settings->Contains("port")) endpoint_attrs->Set("port", settings->Get("port")); } Log(LogInformation, "cli") << "Adding endpoint '" << endpoint << "' to the repository."; if (!RepositoryUtility::AddObject(object_paths, endpoint, "Endpoint", endpoint_attrs, changes, false)) { Log(LogWarning, "cli") << "Cannot add node endpoint '" << endpoint << "' to the config repository!\n"; } Dictionary::Ptr zone_attrs = new Dictionary(); Array::Ptr zone_members = new Array(); zone_members->Add(endpoint); zone_attrs->Set("__name", zone); zone_attrs->Set("name", zone); zone_attrs->Set("endpoints", zone_members); String parent_zone = VariableUtility::GetVariable("ZoneName"); if (parent_zone.IsEmpty()) { Log(LogWarning, "cli") << "Variable 'ZoneName' is not set. Falling back to using 'master' as default. Please verify the generated configuration."; parent_zone = "master"; } zone_attrs->Set("parent", parent_zone); Log(LogInformation, "cli") << "Adding zone '" << zone << "' to the repository."; if (!RepositoryUtility::AddObject(object_paths, zone, "Zone", zone_attrs, changes, false)) { Log(LogWarning, "cli") << "Cannot add node zone '" << zone << "' to the config repository!\n"; } } Log(LogInformation, "cli", "Committing node configuration."); RepositoryUtility::PrintChangeLog(std::cout); std::cout << "\n"; RepositoryUtility::CommitChangeLog(); /* store the new inventory for next run */ NodeUtility::CreateRepositoryPath(); Utility::SaveJsonFile(inventory_path, 0600, inventory); std::cout << "Make sure to reload Icinga 2 for these changes to take effect." << std::endl; return 0; }