diff --git a/doc/19-language-reference.md b/doc/19-language-reference.md
index 746704f22..a63b16e65 100644
--- a/doc/19-language-reference.md
+++ b/doc/19-language-reference.md
@@ -517,6 +517,29 @@ recursively included.
The file names need to match the pattern given in the second parameter.
When no pattern is specified the default pattern "*.conf" is used.
+## Zone Includes
+
+The `include_zones` recursively includes all subdirectories for the
+given path.
+
+In addition to that it sets the `zone` attribute for all objects created
+in these subdirectories to the name of the subdirectory.
+
+Example:
+
+ include_zones "etc", "zones.d", "*.conf"
+ include_zones "puppet", "puppet-zones"
+
+The first parameter specifies a tag name for this directive. Each `include_zones`
+invocation should use a unique tag name. When copying the zones' configuration
+files Icinga uses the tag name as the name for the destination directory in
+`/var/lib/icinga2/api/config`.
+
+The second parameter specifies the directory which contains the subdirectories.
+
+The file names need to match the pattern given in the third parameter.
+When no pattern is specified the default pattern "*.conf" is used.
+
## Library directive
The `library` directive can be used to manually load additional
diff --git a/doc/4-configuring-icinga-2.md b/doc/4-configuring-icinga-2.md
index f5686365e..a85d83e13 100644
--- a/doc/4-configuring-icinga-2.md
+++ b/doc/4-configuring-icinga-2.md
@@ -136,6 +136,16 @@ and their generated configuration described in
You can put your own configuration files in the [conf.d](4-configuring-icinga-2.md#conf-d) directory. This
directive makes sure that all of your own configuration files are included.
+ /**
+ * The zones.d directory contains configuration files for satellite
+ * instances.
+ */
+ include_zones "etc", "zones.d"
+
+Configuration files for satellite instances are managed in 'zones'. This directive ensures
+that all configuration files in the `zones.d` directory are included and that the `zones`
+attribute for objects defined in this directory is set appropriately.
+
### constants.conf
The `constants.conf` configuration file can be used to define global constants.
diff --git a/etc/icinga2/icinga2.conf b/etc/icinga2/icinga2.conf
index 1d7335465..825ffe19b 100644
--- a/etc/icinga2/icinga2.conf
+++ b/etc/icinga2/icinga2.conf
@@ -49,3 +49,8 @@ include_recursive "repository.d"
*/
include_recursive "conf.d"
+/**
+ * The zones.d directory contains configuration files for satellite
+ * instances.
+ */
+include_zones "etc", "zones.d"
diff --git a/lib/cli/daemonutility.cpp b/lib/cli/daemonutility.cpp
index deffb3caa..c09de0346 100644
--- a/lib/cli/daemonutility.cpp
+++ b/lib/cli/daemonutility.cpp
@@ -85,13 +85,6 @@ bool DaemonUtility::ValidateConfigFiles(const std::vector& configs,
* unfortunately moving it there is somewhat non-trivial. */
success = true;
- String zonesEtcDir = Application::GetZonesDir();
- if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir))
- Utility::Glob(zonesEtcDir + "/*", boost::bind(&IncludeZoneDirRecursive, _1, boost::ref(success)), GlobDirectory);
-
- if (!success)
- return false;
-
String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
if (Utility::PathExists(zonesVarDir))
Utility::Glob(zonesVarDir + "/*", boost::bind(&IncludeNonLocalZone, _1, boost::ref(success)), GlobDirectory);
diff --git a/lib/config/config_lexer.ll b/lib/config/config_lexer.ll
index dc0a7eed5..9455eb1b4 100644
--- a/lib/config/config_lexer.ll
+++ b/lib/config/config_lexer.ll
@@ -171,6 +171,7 @@ object return T_OBJECT;
template return T_TEMPLATE;
include return T_INCLUDE;
include_recursive return T_INCLUDE_RECURSIVE;
+include_zones return T_INCLUDE_ZONES;
library return T_LIBRARY;
null return T_NULL;
true { yylval->boolean = 1; return T_BOOLEAN; }
diff --git a/lib/config/config_parser.yy b/lib/config/config_parser.yy
index 1abcb16a4..2e63f6f56 100644
--- a/lib/config/config_parser.yy
+++ b/lib/config/config_parser.yy
@@ -149,6 +149,7 @@ static void MakeRBinaryOp(Expression** result, Expression *left, Expression *rig
%token T_TEMPLATE "template (T_TEMPLATE)"
%token T_INCLUDE "include (T_INCLUDE)"
%token T_INCLUDE_RECURSIVE "include_recursive (T_INCLUDE_RECURSIVE)"
+%token T_INCLUDE_ZONES "include_zones (T_INCLUDE_ZONES)"
%token T_LIBRARY "library (T_LIBRARY)"
%token T_INHERITS "inherits (T_INHERITS)"
%token T_APPLY "apply (T_APPLY)"
@@ -197,7 +198,7 @@ static void MakeRBinaryOp(Expression** result, Expression *left, Expression *rig
%type object_declaration
%right T_FOLLOWS
-%right T_INCLUDE T_INCLUDE_RECURSIVE T_OBJECT T_TEMPLATE T_APPLY T_IMPORT T_ASSIGN T_IGNORE T_WHERE
+%right T_INCLUDE T_INCLUDE_RECURSIVE T_INCLUDE_ZONES T_OBJECT T_TEMPLATE T_APPLY T_IMPORT T_ASSIGN T_IGNORE T_WHERE
%right T_FUNCTION T_FOR
%left T_SET T_SET_ADD T_SET_SUBTRACT T_SET_MULTIPLY T_SET_DIVIDE T_SET_MODULO T_SET_XOR T_SET_BINARY_AND T_SET_BINARY_OR
%left T_LOGICAL_OR
@@ -464,6 +465,14 @@ lterm: library
free($2);
free($4);
}
+ | T_INCLUDE_ZONES T_STRING ',' T_STRING
+ {
+ $$ = context->HandleIncludeZones($2, $4, "*.conf", @$);
+ }
+ | T_INCLUDE_ZONES T_STRING ',' T_STRING ',' T_STRING
+ {
+ $$ = context->HandleIncludeZones($2, $4, $6, @$);
+ }
| T_IMPORT rterm
{
$$ = new ImportExpression($2, @$);
diff --git a/lib/config/configcompiler.cpp b/lib/config/configcompiler.cpp
index 8fc48c64f..80c8850d9 100644
--- a/lib/config/configcompiler.cpp
+++ b/lib/config/configcompiler.cpp
@@ -30,6 +30,7 @@
using namespace icinga;
std::vector ConfigCompiler::m_IncludeSearchDirs;
+std::map > ConfigCompiler::m_ZoneDirs;
/**
* Constructor for the ConfigCompiler class.
@@ -165,6 +166,47 @@ Expression *ConfigCompiler::HandleIncludeRecursive(const String& path, const Str
return new DictExpression(expressions);
}
+void ConfigCompiler::HandleIncludeZone(const String& tag, const String& path, const String& pattern, std::vector& expressions)
+{
+ String zoneName = Utility::BaseName(path);
+
+ String ppath;
+
+ if (path.GetLength() > 0 && path[0] == '/')
+ ppath = path;
+ else
+ ppath = Utility::DirName(GetPath()) + "/" + path;
+
+ ZoneFragment zf;
+ zf.Tag = tag;
+ zf.Path = ppath;
+ m_ZoneDirs[zoneName].push_back(zf);
+
+ Utility::GlobRecursive(ppath, pattern, boost::bind(&ConfigCompiler::CollectIncludes, boost::ref(expressions), _1, zoneName), GlobFile);
+}
+
+/**
+ * Handles zone includes.
+ *
+ * @param tag The tag name.
+ * @param path The directory path.
+ * @param pattern The file pattern.
+ * @param debuginfo Debug information.
+ */
+Expression *ConfigCompiler::HandleIncludeZones(const String& tag, const String& path, const String& pattern, const DebugInfo&)
+{
+ String ppath;
+
+ if (path.GetLength() > 0 && path[0] == '/')
+ ppath = path;
+ else
+ ppath = Utility::DirName(GetPath()) + "/" + path;
+
+ std::vector expressions;
+ Utility::Glob(ppath + "/*", boost::bind(&ConfigCompiler::HandleIncludeZone, this, tag, _1, pattern, boost::ref(expressions)), GlobDirectory);
+ return new DictExpression(expressions);
+}
+
/**
* Handles the library directive.
*
@@ -272,3 +314,12 @@ void ConfigCompiler::AddIncludeSearchDir(const String& dir)
m_IncludeSearchDirs.push_back(dir);
}
+std::vector ConfigCompiler::GetZoneDirs(const String& zone)
+{
+ std::map >::const_iterator it;
+ it = m_ZoneDirs.find(zone);
+ if (it == m_ZoneDirs.end())
+ return std::vector();
+ else
+ return it->second;
+}
diff --git a/lib/config/configcompiler.hpp b/lib/config/configcompiler.hpp
index f0a429b29..e4626758b 100644
--- a/lib/config/configcompiler.hpp
+++ b/lib/config/configcompiler.hpp
@@ -64,6 +64,12 @@ struct EItemInfo
CompilerDebugInfo DebugInfo;
};
+struct ZoneFragment
+{
+ String Tag;
+ String Path;
+};
+
/**
* The configuration compiler can be used to compile a configuration file
* into a number of configuration items.
@@ -94,11 +100,14 @@ public:
/* internally used methods */
Expression *HandleInclude(const String& include, bool search, const DebugInfo& debuginfo = DebugInfo());
Expression *HandleIncludeRecursive(const String& path, const String& pattern, const DebugInfo& debuginfo = DebugInfo());
+ Expression *HandleIncludeZones(const String& tag, const String& path, const String& pattern, const DebugInfo& debuginfo = DebugInfo());
void HandleLibrary(const String& library);
size_t ReadInput(char *buffer, size_t max_bytes);
void *GetScanner(void) const;
+ static std::vector GetZoneDirs(const String& zone);
+
private:
boost::promise > m_Promise;
@@ -109,12 +118,15 @@ private:
void *m_Scanner;
static std::vector m_IncludeSearchDirs;
+ static std::map > m_ZoneDirs;
void InitializeScanner(void);
void DestroyScanner(void);
void CompileHelper(void);
+ void HandleIncludeZone(const String& tag, const String& path, const String& pattern, std::vector& expressions);
+
public:
bool m_Eof;
int m_OpenBraces;
diff --git a/lib/remote/apilistener-sync.cpp b/lib/remote/apilistener-sync.cpp
index 21002109a..7bca5731c 100644
--- a/lib/remote/apilistener-sync.cpp
+++ b/lib/remote/apilistener-sync.cpp
@@ -19,6 +19,7 @@
#include "remote/apilistener.hpp"
#include "remote/apifunction.hpp"
+#include "config/configcompiler.hpp"
#include "base/dynamictype.hpp"
#include "base/logger.hpp"
#include "base/convert.hpp"
@@ -121,11 +122,20 @@ bool ApiListener::UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictio
void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
{
- String newDir = Application::GetZonesDir() + "/" + zone->GetName();
+ Dictionary::Ptr newConfig = new Dictionary();
+ BOOST_FOREACH(const ZoneFragment& zf, ConfigCompiler::GetZoneDirs(zone->GetName())) {
+ Dictionary::Ptr newConfigPart = LoadConfigDir(zf.Path);
+
+ ObjectLock olock(newConfigPart);
+ BOOST_FOREACH(const Dictionary::Pair& kv, newConfigPart) {
+ newConfig->Set(zf.Tag + "/" + kv.first, kv.second);
+ }
+ }
+
String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
Log(LogInformation, "ApiListener")
- << "Copying zone configuration files from '" << newDir << "' to '" << oldDir << "'.";
+ << "Copying zone configuration files for zone '" << zone->GetName() << "' to '" << oldDir << "'.";
if (!Utility::MkDir(oldDir, 0700)) {
Log(LogCritical, "ApiListener")
@@ -137,7 +147,6 @@ void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
<< boost::errinfo_file_name(oldDir));
}
- Dictionary::Ptr newConfig = LoadConfigDir(newDir);
Dictionary::Ptr oldConfig = LoadConfigDir(oldDir);
UpdateConfigDir(oldConfig, newConfig, oldDir, true);