mirror of
				https://github.com/Icinga/icinga2.git
				synced 2025-11-03 21:25:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			424 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			424 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/******************************************************************************
 | 
						|
 * 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 <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);
 | 
						|
 | 
						|
	/* cache all existing object configs only once and pass it to AddObject() */
 | 
						|
	std::vector<String> 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<Dictionary::Ptr> 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;
 | 
						|
}
 |