mirror of
				https://github.com/Icinga/icinga2.git
				synced 2025-11-04 13:45:04 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			392 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/******************************************************************************
 | 
						|
 * Icinga 2                                                                   *
 | 
						|
 * Copyright (C) 2012-2014 Icinga Development Team (http://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 "base/logger.hpp"
 | 
						|
#include "base/application.hpp"
 | 
						|
#include "base/objectlock.hpp"
 | 
						|
#include "base/json.hpp"
 | 
						|
#include <boost/foreach.hpp>
 | 
						|
#include <boost/algorithm/string/join.hpp>
 | 
						|
#include <boost/algorithm/string/replace.hpp>
 | 
						|
#include <iostream>
 | 
						|
#include <fstream>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
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<std::string>& 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);
 | 
						|
 | 
						|
	Utility::LoadExtensionLibrary("icinga");
 | 
						|
 | 
						|
	std::vector<String> object_paths = RepositoryUtility::GetObjects();
 | 
						|
 | 
						|
	BOOST_FOREACH(const Dictionary::Ptr& node, NodeUtility::GetNodes()) {
 | 
						|
		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);
 | 
						|
 | 
						|
		Dictionary::Ptr host_services = new Dictionary();
 | 
						|
 | 
						|
		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(zone, "Host", host_attrs))
 | 
						|
			continue;
 | 
						|
 | 
						|
		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(LogWarning, "cli")
 | 
						|
					    << "Host '" << host << "' already existing. Skipping its creation.";
 | 
						|
					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. Skipping.";
 | 
						|
				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(host, "Host", host_attrs);
 | 
						|
			}
 | 
						|
 | 
						|
			/* 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(LogWarning, "cli")
 | 
						|
						    << "Service '" << service << "' on Host '" << host << "' already existing. Skipping its creation.";
 | 
						|
						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. Skipping.";
 | 
						|
					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(service, "Service", service_attrs))
 | 
						|
					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"));
 | 
						|
		}
 | 
						|
 | 
						|
		if (!RepositoryUtility::AddObject(endpoint, "Endpoint", endpoint_attrs)) {
 | 
						|
			Log(LogCritical, "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 node_parent_zone = "master"; //hardcode the name
 | 
						|
		String parent_zone;
 | 
						|
 | 
						|
		if (!node->Contains("parent_zone")) {
 | 
						|
			Log(LogWarning, "cli")
 | 
						|
			    << "Node '" << endpoint << "' does not have any parent zone defined. Using 'master' as default. Please verify the generated configuration.";
 | 
						|
			parent_zone = node_parent_zone;
 | 
						|
		} else {
 | 
						|
			parent_zone = node->Get("parent_zone");
 | 
						|
 | 
						|
			if (parent_zone.IsEmpty()) {
 | 
						|
				Log(LogWarning, "cli")
 | 
						|
				    << "Node '" << endpoint << "' does not have any parent zone defined. Using 'master' as default. Please verify the generated configuration.";
 | 
						|
				parent_zone = node_parent_zone;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		zone_attrs->Set("parent", parent_zone);
 | 
						|
 | 
						|
		if (!RepositoryUtility::AddObject(zone, "Zone", zone_attrs)) {
 | 
						|
			Log(LogCritical, "cli")
 | 
						|
			    << "Cannot add node zone '" << zone << "' to the config repository!\n";
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* check if there are objects inside the old_inventory which do not exist anymore */
 | 
						|
	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_inventory->Get(old_node_name);
 | 
						|
			Dictionary::Ptr old_node_repository = old_node->Get("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); //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);
 | 
						|
 | 
						|
			Dictionary::Ptr endpoint_attrs = new Dictionary();
 | 
						|
			endpoint_attrs->Set("name", endpoint);
 | 
						|
			RepositoryUtility::RemoveObject(endpoint, "Endpoint", endpoint_attrs);
 | 
						|
		} 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_inventory->Get(old_node_name);
 | 
						|
			Dictionary::Ptr old_node_repository = old_node->Get("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 << "' on node '" << old_node << "' 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); //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);
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	Log(LogInformation, "cli", "Committing node configuration.");
 | 
						|
 | 
						|
	RepositoryUtility::PrintChangeLog(std::cout);
 | 
						|
	std::cout << "\n";
 | 
						|
	RepositoryUtility::CommitChangeLog();
 | 
						|
 | 
						|
	/* store the new inventory for next run */
 | 
						|
	Utility::SaveJsonFile(inventory_path, inventory);
 | 
						|
 | 
						|
	std::cout << "Make sure to reload Icinga 2 for these changes to take effect." << std::endl;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 |