From 58f923f5f711f0b6515d7b20753c39322688730f Mon Sep 17 00:00:00 2001 From: Michael Insel Date: Wed, 18 Apr 2018 20:22:04 +0200 Subject: [PATCH 1/8] Allow to disable conf.d inclusion through node wizard/setup This implements a function to disable the conf.d directory through the node wizard/setup. refs #4508 --- lib/cli/nodesetupcommand.cpp | 36 +++++++++++++++++-- lib/cli/nodeutility.cpp | 68 +++++++++++++++++++++++++++++++++++ lib/cli/nodeutility.hpp | 1 + lib/cli/nodewizardcommand.cpp | 61 ++++++++++++++++++++++++++++++- 4 files changed, 163 insertions(+), 3 deletions(-) diff --git a/lib/cli/nodesetupcommand.cpp b/lib/cli/nodesetupcommand.cpp index 590b08fae..260d01349 100644 --- a/lib/cli/nodesetupcommand.cpp +++ b/lib/cli/nodesetupcommand.cpp @@ -66,7 +66,8 @@ void NodeSetupCommand::InitParameters(boost::program_options::options_descriptio ("accept-config", "Accept config from master") ("accept-commands", "Accept commands from master") ("master", "Use setup for a master instance") - ("global_zones", po::value >(), "The names of the additional global zones."); + ("global_zones", po::value >(), "The names of the additional global zones.") + ("dont-disable-confd", "Disables the conf.d directory during the setup"); hiddenDesc.add_options() ("master_zone", po::value(), "DEPRECATED: The name of the master zone") @@ -244,8 +245,22 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v Log(LogInformation, "cli") << "Edit the api feature config file '" << apipath << "' and set a secure 'ticket_salt' attribute."; - /* tell the user to reload icinga2 */ + if (!vm.count("dont-disable-confd")) { + /* Disable conf.d inclusion */ + NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; + std::ifstream apiUsersFile(apiUsersFilePath); + + /* Include api-users.conf */ + if(apiUsersFile) + NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); + else + Log(LogWarning, "cli") + << "Included file dosen't exist " << apiUsersFilePath; + } + + /* tell the user to reload icinga2 */ Log(LogInformation, "cli", "Make sure to restart Icinga 2."); return 0; @@ -555,5 +570,22 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm, Log(LogInformation, "cli", "Make sure to restart Icinga 2."); } + if (!vm.count("dont-disable-confd")) { + + /* Disable conf.d inclusion */ + NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + + String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; + std::ifstream apiUsersFile(apiUsersFilePath); + + if(apiUsersFile) + NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); + else + Log(LogWarning, "cli", "Included file dosen't exist " + apiUsersFilePath); + } + + /* tell the user to reload icinga2 */ + Log(LogInformation, "cli", "Make sure to restart Icinga 2."); + return 0; } diff --git a/lib/cli/nodeutility.cpp b/lib/cli/nodeutility.cpp index 645e19d94..ac58f939d 100644 --- a/lib/cli/nodeutility.cpp +++ b/lib/cli/nodeutility.cpp @@ -265,6 +265,74 @@ void NodeUtility::SerializeObject(std::ostream& fp, const Dictionary::Ptr& objec fp << "}\n\n"; } +/* + * include = false, will comment out the include statement + * include = true, will add an include statement or uncomment a statement if one is existing + * resursive = false, will search for a non-resursive include statement + * recursive = true, will search for a resursive include statement + */ +void NodeUtility::UpdateConfiguration(const String& value, const bool& include, const bool& recursive) +{ + String configurationFile = Application::GetSysconfDir() + "/icinga2/icinga2.conf"; + + Log(LogInformation, "cli") + << "Updating' " << value << "' include in '" << configurationFile << "'."; + + NodeUtility::CreateBackupFile(configurationFile); + + std::ifstream ifp(configurationFile.CStr()); + std::fstream ofp; + String tempFile = Utility::CreateTempFile(configurationFile + ".XXXXXX", 0644, ofp); + + String affectedInclude = value; + + recursive ? affectedInclude = "include_recursive " + affectedInclude : affectedInclude = "include " + affectedInclude; + + bool found = false; + + std::string line; + + while (std::getline(ifp, line)) { + if(include) { + if (line.find("//" + affectedInclude) != std::string::npos || line.find("// " + affectedInclude) != std::string::npos) { + found = true; + ofp << affectedInclude + "\n"; + } else if (line.find(affectedInclude) != std::string::npos) { + found = true; + + Log(LogInformation, "cli") + << "Include statement '" + affectedInclude + "' already set."; + + ofp << line << "\n"; + } else + ofp << line << "\n"; + } else { + if (line.find(affectedInclude) != std::string::npos) { + found = true; + ofp << "// " + affectedInclude + "\n"; + } else + ofp << line << "\n"; + } + } + + if (include && !found) + ofp << affectedInclude + "\n"; + + ifp.close(); + ofp.close(); + +#ifdef _WIN32 + _unlink(configurationFile.CStr()); +#endif /* _WIN32 */ + + if (rename(tempFile.CStr(), configurationFile.CStr()) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("rename") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(configurationFile)); + } +} + void NodeUtility::UpdateConstant(const String& name, const String& value) { String constantsConfPath = NodeUtility::GetConstantsConfPath(); diff --git a/lib/cli/nodeutility.hpp b/lib/cli/nodeutility.hpp index 2ba33c2f3..5ce662dfd 100644 --- a/lib/cli/nodeutility.hpp +++ b/lib/cli/nodeutility.hpp @@ -44,6 +44,7 @@ public: static bool WriteNodeConfigObjects(const String& filename, const Array::Ptr& objects); + static void UpdateConfiguration(const String& value, const bool& include, const bool& recursive); static void UpdateConstant(const String& name, const String& value); /* node setup helpers */ diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp index e6cdb827c..aebfd87a3 100644 --- a/lib/cli/nodewizardcommand.cpp +++ b/lib/cli/nodewizardcommand.cpp @@ -104,7 +104,8 @@ int NodeWizardCommand::Run(const boost::program_options::variables_map& vm, * 9. enable ApiListener feature * 10. generate zones.conf with endpoints and zone objects * 11. set NodeName = cn in constants.conf - * 12. reload icinga2, or tell the user to + * 12. disable conf.d directory? + * 13. reload icinga2, or tell the user to */ std::string answer; @@ -615,6 +616,24 @@ wizard_global_zone_loop_start: Log(LogInformation, "cli", "Make sure to restart Icinga 2."); } + /* Disable conf.d inclusion */ + std::cout << "\nDo you want to disable the inclusion of the conf.d directory [Y/n]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + if (choice.Contains("n")) + Log(LogInformation, "cli") + << "The deactivation of the conf.d directory was skipped."; + else { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Disable the inclusion of the conf.d directory...\n" + << ConsoleColorTag(Console_Normal); + + NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + } + return 0; } @@ -788,6 +807,12 @@ wizard_global_zone_loop_start: << Utility::GetFQDN() << "'. Requires an update for the NodeName constant in constants.conf!"; } + Log(LogInformation, "cli", "Updating constants.conf."); + + String constants_file = Application::GetSysconfDir() + "/icinga2/constants.conf"; + + NodeUtility::CreateBackupFile(constants_file); + NodeUtility::UpdateConstant("NodeName", cn); NodeUtility::UpdateConstant("ZoneName", cn); @@ -795,5 +820,39 @@ wizard_global_zone_loop_start: NodeUtility::UpdateConstant("TicketSalt", salt); + /* Disable conf.d inclusion */ + std::cout << "\nDo you want to disable the inclusion of the conf.d directory [Y/n]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + if (choice.Contains("n")) + Log(LogInformation, "cli") + << "The deactivation of the conf.d directory was skipped."; + else { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Disable the inclusion of the conf.d directory...\n" + << ConsoleColorTag(Console_Normal); + + NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + + /* Include api-users.conf */ + String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; + std::ifstream apiUsersFile(apiUsersFilePath); + + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Checking if api-users.conf exist...\n" + << ConsoleColorTag(Console_Normal); + + if(apiUsersFile) + NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); + else + Log(LogWarning, "cli") + << "Included file dosen't exist " << apiUsersFilePath; + } + + std::cout << "Done.\n\n"; + return 0; } From a43cf8e9efdbf41411d4b81bbe07842b71265219 Mon Sep 17 00:00:00 2001 From: Jean Flach Date: Wed, 2 May 2018 15:10:44 +0200 Subject: [PATCH 2/8] Warn in case of failed conf.d exclusion refs #4508 --- lib/cli/nodeutility.cpp | 5 ++++- lib/cli/nodeutility.hpp | 2 +- lib/cli/nodewizardcommand.cpp | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/cli/nodeutility.cpp b/lib/cli/nodeutility.cpp index ac58f939d..fab75b0e0 100644 --- a/lib/cli/nodeutility.cpp +++ b/lib/cli/nodeutility.cpp @@ -270,8 +270,9 @@ void NodeUtility::SerializeObject(std::ostream& fp, const Dictionary::Ptr& objec * include = true, will add an include statement or uncomment a statement if one is existing * resursive = false, will search for a non-resursive include statement * recursive = true, will search for a resursive include statement + * Returns true on success, false if option was not found */ -void NodeUtility::UpdateConfiguration(const String& value, const bool& include, const bool& recursive) +bool NodeUtility::UpdateConfiguration(const String& value, const bool& include, const bool& recursive) { String configurationFile = Application::GetSysconfDir() + "/icinga2/icinga2.conf"; @@ -331,6 +332,8 @@ void NodeUtility::UpdateConfiguration(const String& value, const bool& include, << boost::errinfo_errno(errno) << boost::errinfo_file_name(configurationFile)); } + + return (found || include); } void NodeUtility::UpdateConstant(const String& name, const String& value) diff --git a/lib/cli/nodeutility.hpp b/lib/cli/nodeutility.hpp index 5ce662dfd..cf6cb325c 100644 --- a/lib/cli/nodeutility.hpp +++ b/lib/cli/nodeutility.hpp @@ -44,7 +44,7 @@ public: static bool WriteNodeConfigObjects(const String& filename, const Array::Ptr& objects); - static void UpdateConfiguration(const String& value, const bool& include, const bool& recursive); + static bool UpdateConfiguration(const String& value, const bool& include, const bool& recursive); static void UpdateConstant(const String& name, const String& value); /* node setup helpers */ diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp index aebfd87a3..f729685f6 100644 --- a/lib/cli/nodewizardcommand.cpp +++ b/lib/cli/nodewizardcommand.cpp @@ -631,7 +631,12 @@ wizard_global_zone_loop_start: << "Disable the inclusion of the conf.d directory...\n" << ConsoleColorTag(Console_Normal); - NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + if(!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) + << "Failed to disable conf.d inclusion, it may already be disabled." + << ConsoleColorTag(Console_Normal); + } + } return 0; From 026359d4042879cabfacf12129e0566a49cae6bd Mon Sep 17 00:00:00 2001 From: Jean Flach Date: Wed, 2 May 2018 17:42:30 +0200 Subject: [PATCH 3/8] Rename "dont-disable-confd" Double negation is confusing. Also this would change the default behaviour and could lead to problems in automated environments. refs #4508 --- lib/cli/nodesetupcommand.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cli/nodesetupcommand.cpp b/lib/cli/nodesetupcommand.cpp index 260d01349..fbc47dbd9 100644 --- a/lib/cli/nodesetupcommand.cpp +++ b/lib/cli/nodesetupcommand.cpp @@ -67,7 +67,7 @@ void NodeSetupCommand::InitParameters(boost::program_options::options_descriptio ("accept-commands", "Accept commands from master") ("master", "Use setup for a master instance") ("global_zones", po::value >(), "The names of the additional global zones.") - ("dont-disable-confd", "Disables the conf.d directory during the setup"); + ("disable-confd", "Disables the conf.d directory during the setup"); hiddenDesc.add_options() ("master_zone", po::value(), "DEPRECATED: The name of the master zone") @@ -245,7 +245,7 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v Log(LogInformation, "cli") << "Edit the api feature config file '" << apipath << "' and set a secure 'ticket_salt' attribute."; - if (!vm.count("dont-disable-confd")) { + if (vm.count("disable-confd")) { /* Disable conf.d inclusion */ NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); @@ -570,7 +570,7 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm, Log(LogInformation, "cli", "Make sure to restart Icinga 2."); } - if (!vm.count("dont-disable-confd")) { + if (vm.count("disable-confd")) { /* Disable conf.d inclusion */ NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); From 85dde65d00f7b15bcc1ee7bc2bc46b430a7d469a Mon Sep 17 00:00:00 2001 From: Jean Flach Date: Wed, 2 May 2018 17:45:07 +0200 Subject: [PATCH 4/8] Allow conf.d exclusion in windows wizard refs #4508 --- .../SetupWizard.Designer.cs | 32 ++++++++++++++----- agent/windows-setup-agent/SetupWizard.cs | 8 +++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/agent/windows-setup-agent/SetupWizard.Designer.cs b/agent/windows-setup-agent/SetupWizard.Designer.cs index 46dd421e4..258139224 100644 --- a/agent/windows-setup-agent/SetupWizard.Designer.cs +++ b/agent/windows-setup-agent/SetupWizard.Designer.cs @@ -87,6 +87,7 @@ this.txtError = new System.Windows.Forms.TextBox(); this.lblError = new System.Windows.Forms.Label(); this.picBanner = new System.Windows.Forms.PictureBox(); + this.chkDisableConf = new System.Windows.Forms.CheckBox(); this.tabFinish.SuspendLayout(); this.tabConfigure.SuspendLayout(); this.tabParameters.SuspendLayout(); @@ -105,7 +106,7 @@ // btnBack // this.btnBack.Enabled = false; - this.btnBack.Location = new System.Drawing.Point(376, 556); + this.btnBack.Location = new System.Drawing.Point(376, 587); this.btnBack.Name = "btnBack"; this.btnBack.Size = new System.Drawing.Size(75, 23); this.btnBack.TabIndex = 1; @@ -115,7 +116,7 @@ // // btnNext // - this.btnNext.Location = new System.Drawing.Point(457, 556); + this.btnNext.Location = new System.Drawing.Point(457, 587); this.btnNext.Name = "btnNext"; this.btnNext.Size = new System.Drawing.Size(75, 23); this.btnNext.TabIndex = 2; @@ -126,7 +127,7 @@ // btnCancel // this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnCancel.Location = new System.Drawing.Point(538, 556); + this.btnCancel.Location = new System.Drawing.Point(538, 587); this.btnCancel.Name = "btnCancel"; this.btnCancel.Size = new System.Drawing.Size(75, 23); this.btnCancel.TabIndex = 3; @@ -196,7 +197,7 @@ this.tabParameters.Location = new System.Drawing.Point(4, 5); this.tabParameters.Name = "tabParameters"; this.tabParameters.Padding = new System.Windows.Forms.Padding(3); - this.tabParameters.Size = new System.Drawing.Size(617, 471); + this.tabParameters.Size = new System.Drawing.Size(617, 495); this.tabParameters.TabIndex = 3; this.tabParameters.Text = "Agent Parameters"; this.tabParameters.UseVisualStyleBackColor = true; @@ -275,6 +276,7 @@ // // groupBox3 // + this.groupBox3.Controls.Add(this.chkDisableConf); this.groupBox3.Controls.Add(this.txtUser); this.groupBox3.Controls.Add(this.chkRunServiceAsThisUser); this.groupBox3.Controls.Add(this.chkInstallNSCP); @@ -282,7 +284,7 @@ this.groupBox3.Controls.Add(this.chkAcceptCommands); this.groupBox3.Location = new System.Drawing.Point(308, 326); this.groupBox3.Name = "groupBox3"; - this.groupBox3.Size = new System.Drawing.Size(301, 139); + this.groupBox3.Size = new System.Drawing.Size(301, 163); this.groupBox3.TabIndex = 5; this.groupBox3.TabStop = false; this.groupBox3.Text = "Advanced Options"; @@ -377,7 +379,7 @@ this.groupBox2.Controls.Add(this.rdoListener); this.groupBox2.Location = new System.Drawing.Point(8, 326); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(298, 139); + this.groupBox2.Size = new System.Drawing.Size(298, 163); this.groupBox2.TabIndex = 2; this.groupBox2.TabStop = false; this.groupBox2.Text = "TCP Listener"; @@ -513,7 +515,7 @@ this.tbcPages.Margin = new System.Windows.Forms.Padding(0); this.tbcPages.Name = "tbcPages"; this.tbcPages.SelectedIndex = 0; - this.tbcPages.Size = new System.Drawing.Size(625, 480); + this.tbcPages.Size = new System.Drawing.Size(625, 504); this.tbcPages.SizeMode = System.Windows.Forms.TabSizeMode.Fixed; this.tbcPages.TabIndex = 0; this.tbcPages.SelectedIndexChanged += new System.EventHandler(this.tbcPages_SelectedIndexChanged); @@ -691,13 +693,26 @@ this.picBanner.TabIndex = 1; this.picBanner.TabStop = false; // + // chkDisableConf + // + this.chkDisableConf.AutoSize = true; + this.chkDisableConf.Checked = true; + this.chkDisableConf.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkDisableConf.Location = new System.Drawing.Point(9, 137); + this.chkDisableConf.Name = "chkDisableConf"; + this.chkDisableConf.Size = new System.Drawing.Size(138, 17); + this.chkDisableConf.TabIndex = 9; + this.chkDisableConf.Text = "Disable conf.d inclusion"; + this.chkDisableConf.UseVisualStyleBackColor = true; + this.chkDisableConf.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged); + // // SetupWizard // this.AcceptButton = this.btnNext; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.btnCancel; - this.ClientSize = new System.Drawing.Size(625, 587); + this.ClientSize = new System.Drawing.Size(625, 622); this.Controls.Add(this.btnCancel); this.Controls.Add(this.btnNext); this.Controls.Add(this.btnBack); @@ -794,6 +809,7 @@ private System.Windows.Forms.Button btnAddGlobalZone; private System.Windows.Forms.ListView lvwGlobalZones; private System.Windows.Forms.ColumnHeader colGlobalZoneName; + private System.Windows.Forms.CheckBox chkDisableConf; } } diff --git a/agent/windows-setup-agent/SetupWizard.cs b/agent/windows-setup-agent/SetupWizard.cs index fc903fd91..6aceaf16b 100644 --- a/agent/windows-setup-agent/SetupWizard.cs +++ b/agent/windows-setup-agent/SetupWizard.cs @@ -229,6 +229,9 @@ namespace Icinga args += " --global_zones " + lvi.SubItems[0].Text.Trim(); } + if (chkDisableConf.Checked) + args += " --disable-confd"; + if (!RunProcess(Program.Icinga2InstallDir + "\\sbin\\icinga2.exe", "node setup" + args, out output)) { @@ -568,6 +571,11 @@ namespace Icinga lvwGlobalZones.Items.Add(lvi2); } + + private void checkBox1_CheckedChanged(object sender, EventArgs e) + { + + } } } From 662d36fd165abd6e515da457eb0c75c773d9c021 Mon Sep 17 00:00:00 2001 From: Michael Insel Date: Thu, 3 May 2018 17:12:06 +0200 Subject: [PATCH 5/8] Warn in case of failed conf.d exclusion also on master setup refs #4508 --- lib/cli/nodewizardcommand.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp index f729685f6..3398aaa85 100644 --- a/lib/cli/nodewizardcommand.cpp +++ b/lib/cli/nodewizardcommand.cpp @@ -633,7 +633,7 @@ wizard_global_zone_loop_start: if(!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) - << "Failed to disable conf.d inclusion, it may already be disabled." + << "Failed to disable conf.d inclusion, it may already be disabled.\n" << ConsoleColorTag(Console_Normal); } @@ -840,7 +840,11 @@ wizard_global_zone_loop_start: << "Disable the inclusion of the conf.d directory...\n" << ConsoleColorTag(Console_Normal); - NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) + << "Failed to disable conf.d inclusion, it may already be disabled.\n" + << ConsoleColorTag(Console_Normal); + } /* Include api-users.conf */ String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; From b8fc3a3ffaba2af424c3955678c8b55a63a7bc9e Mon Sep 17 00:00:00 2001 From: Jean Flach Date: Fri, 4 May 2018 11:38:30 +0200 Subject: [PATCH 6/8] Small changes to node wizard/command refs #4508 --- lib/cli/nodesetupcommand.cpp | 7 ++++++- lib/cli/nodewizardcommand.cpp | 6 ++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/cli/nodesetupcommand.cpp b/lib/cli/nodesetupcommand.cpp index fbc47dbd9..89df313f5 100644 --- a/lib/cli/nodesetupcommand.cpp +++ b/lib/cli/nodesetupcommand.cpp @@ -247,7 +247,12 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v if (vm.count("disable-confd")) { /* Disable conf.d inclusion */ - NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); + if (NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) + Log(LogInformation, "cli") + << "Disabled conf.d inclusion"; + else + Log(LogWarning, "cli") + << "Tried to disable conf.d inclusion but failed, possibly it's already disabled."; String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; std::ifstream apiUsersFile(apiUsersFilePath); diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp index 3398aaa85..615f3a4d3 100644 --- a/lib/cli/nodewizardcommand.cpp +++ b/lib/cli/nodewizardcommand.cpp @@ -628,7 +628,7 @@ wizard_global_zone_loop_start: << "The deactivation of the conf.d directory was skipped."; else { std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) - << "Disable the inclusion of the conf.d directory...\n" + << "Disabling the inclusion of the conf.d directory...\n" << ConsoleColorTag(Console_Normal); if(!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { @@ -814,9 +814,7 @@ wizard_global_zone_loop_start: Log(LogInformation, "cli", "Updating constants.conf."); - String constants_file = Application::GetSysconfDir() + "/icinga2/constants.conf"; - - NodeUtility::CreateBackupFile(constants_file); + NodeUtility::CreateBackupFile(NodeUtility::GetConstantsConfPath()); NodeUtility::UpdateConstant("NodeName", cn); NodeUtility::UpdateConstant("ZoneName", cn); From f0f0b47057a2baf18a68d54ddc75cf3a08f2ec75 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Tue, 8 May 2018 16:06:10 +0200 Subject: [PATCH 7/8] Fix style and logging from review; enhance output refs #4508 --- doc/06-distributed-monitoring.md | 3 +++ lib/cli/apisetuputility.cpp | 7 ++++++- lib/cli/apisetuputility.hpp | 1 + lib/cli/nodesetupcommand.cpp | 26 ++++++++--------------- lib/cli/nodeutility.cpp | 36 +++++++++++++++++++++----------- lib/cli/nodeutility.hpp | 2 +- lib/cli/nodewizardcommand.cpp | 35 +++++++++++++++++-------------- 7 files changed, 63 insertions(+), 47 deletions(-) diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index 91b8e9748..f83f61542 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -236,7 +236,9 @@ Enabling feature api. Make sure to restart Icinga 2 for these changes to take ef Master zone name [master]: +Default global zones: global-templates director-global Do you want to specify additional global zones? [y/N]: N + Please specify the API bind host/port (optional): Bind Host []: Bind Port []: @@ -548,6 +550,7 @@ Press `Enter` or choose `n`, if you don't want to add any additional. ``` Reconfiguring Icinga... +Default global zones: global-templates director-global Do you want to specify additional global zones? [y/N]: N ``` diff --git a/lib/cli/apisetuputility.cpp b/lib/cli/apisetuputility.cpp index 29983226c..070e78cff 100644 --- a/lib/cli/apisetuputility.cpp +++ b/lib/cli/apisetuputility.cpp @@ -40,7 +40,12 @@ using namespace icinga; String ApiSetupUtility::GetConfdPath() { - return Application::GetSysconfDir() + "/icinga2/conf.d"; + return Application::GetSysconfDir() + "/icinga2/conf.d"; +} + +String ApiSetupUtility::GetApiUsersConfPath() +{ + return ApiSetupUtility::GetConfdPath() + "/api-users.conf"; } bool ApiSetupUtility::SetupMaster(const String& cn, bool prompt_restart) diff --git a/lib/cli/apisetuputility.hpp b/lib/cli/apisetuputility.hpp index eeacd8daa..9523ebffc 100644 --- a/lib/cli/apisetuputility.hpp +++ b/lib/cli/apisetuputility.hpp @@ -45,6 +45,7 @@ public: static bool SetupMasterUpdateConstants(const String& cn); static String GetConfdPath(); + static String GetApiUsersConfPath(); private: ApiSetupUtility(); diff --git a/lib/cli/nodesetupcommand.cpp b/lib/cli/nodesetupcommand.cpp index 89df313f5..fa188e7f0 100644 --- a/lib/cli/nodesetupcommand.cpp +++ b/lib/cli/nodesetupcommand.cpp @@ -66,7 +66,7 @@ void NodeSetupCommand::InitParameters(boost::program_options::options_descriptio ("accept-config", "Accept config from master") ("accept-commands", "Accept commands from master") ("master", "Use setup for a master instance") - ("global_zones", po::value >(), "The names of the additional global zones.") + ("global_zones", po::value >(), "The names of the additional global zones to 'global-templates' and 'director-global'.") ("disable-confd", "Disables the conf.d directory during the setup"); hiddenDesc.add_options() @@ -247,22 +247,23 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v if (vm.count("disable-confd")) { /* Disable conf.d inclusion */ - if (NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) + if (NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { Log(LogInformation, "cli") << "Disabled conf.d inclusion"; - else + } else { Log(LogWarning, "cli") << "Tried to disable conf.d inclusion but failed, possibly it's already disabled."; - - String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; - std::ifstream apiUsersFile(apiUsersFilePath); + } /* Include api-users.conf */ - if(apiUsersFile) + String apiUsersFilePath = ApiSetupUtility::GetApiUsersConfPath(); + + if (Utility::PathExists(apiUsersFilePath)) { NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); - else + } else { Log(LogWarning, "cli") << "Included file dosen't exist " << apiUsersFilePath; + } } /* tell the user to reload icinga2 */ @@ -576,17 +577,8 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm, } if (vm.count("disable-confd")) { - /* Disable conf.d inclusion */ NodeUtility::UpdateConfiguration("\"conf.d\"", false, true); - - String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; - std::ifstream apiUsersFile(apiUsersFilePath); - - if(apiUsersFile) - NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); - else - Log(LogWarning, "cli", "Included file dosen't exist " + apiUsersFilePath); } /* tell the user to reload icinga2 */ diff --git a/lib/cli/nodeutility.cpp b/lib/cli/nodeutility.cpp index fab75b0e0..38584a11e 100644 --- a/lib/cli/nodeutility.cpp +++ b/lib/cli/nodeutility.cpp @@ -272,12 +272,12 @@ void NodeUtility::SerializeObject(std::ostream& fp, const Dictionary::Ptr& objec * recursive = true, will search for a resursive include statement * Returns true on success, false if option was not found */ -bool NodeUtility::UpdateConfiguration(const String& value, const bool& include, const bool& recursive) +bool NodeUtility::UpdateConfiguration(const String& value, bool include, bool recursive) { String configurationFile = Application::GetSysconfDir() + "/icinga2/icinga2.conf"; Log(LogInformation, "cli") - << "Updating' " << value << "' include in '" << configurationFile << "'."; + << "Updating ' " << value << "' include in '" << configurationFile << "'."; NodeUtility::CreateBackupFile(configurationFile); @@ -287,37 +287,49 @@ bool NodeUtility::UpdateConfiguration(const String& value, const bool& include, String affectedInclude = value; - recursive ? affectedInclude = "include_recursive " + affectedInclude : affectedInclude = "include " + affectedInclude; + if (recursive) + affectedInclude = "include_recursive " + affectedInclude; + else + affectedInclude = "include " + affectedInclude; bool found = false; std::string line; while (std::getline(ifp, line)) { - if(include) { + if (include) { if (line.find("//" + affectedInclude) != std::string::npos || line.find("// " + affectedInclude) != std::string::npos) { found = true; - ofp << affectedInclude + "\n"; + ofp << "// Added by the node setup CLI command on " + << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) + << "\n" + affectedInclude + "\n"; } else if (line.find(affectedInclude) != std::string::npos) { found = true; - + Log(LogInformation, "cli") << "Include statement '" + affectedInclude + "' already set."; - + ofp << line << "\n"; - } else + } else { ofp << line << "\n"; + } } else { if (line.find(affectedInclude) != std::string::npos) { found = true; - ofp << "// " + affectedInclude + "\n"; - } else + ofp << "// Disabled by the node setup CLI command on " + << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) + << "\n// " + affectedInclude + "\n"; + } else { ofp << line << "\n"; + } } } - if (include && !found) - ofp << affectedInclude + "\n"; + if (include && !found) { + ofp << "// Added by the node setup CLI command on " + << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) + << "\n" + affectedInclude + "\n"; + } ifp.close(); ofp.close(); diff --git a/lib/cli/nodeutility.hpp b/lib/cli/nodeutility.hpp index cf6cb325c..3017a8ead 100644 --- a/lib/cli/nodeutility.hpp +++ b/lib/cli/nodeutility.hpp @@ -44,7 +44,7 @@ public: static bool WriteNodeConfigObjects(const String& filename, const Array::Ptr& objects); - static bool UpdateConfiguration(const String& value, const bool& include, const bool& recursive); + static bool UpdateConfiguration(const String& value, bool include, bool recursive); static void UpdateConstant(const String& name, const String& value); /* node setup helpers */ diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp index 615f3a4d3..b5e143730 100644 --- a/lib/cli/nodewizardcommand.cpp +++ b/lib/cli/nodewizardcommand.cpp @@ -527,6 +527,7 @@ wizard_ticket: /* Global zones. */ std::vector globalZones { "global-templates", "director-global" }; + std::cout << "\nDefault global zones: " << boost::algorithm::join(globalZones, " "); std::cout << "\nDo you want to specify additional global zones? [y/N]: "; std::getline(std::cin, answer); @@ -625,18 +626,21 @@ wizard_global_zone_loop_start: if (choice.Contains("n")) Log(LogInformation, "cli") - << "The deactivation of the conf.d directory was skipped."; + << "conf.d directory has not been disabled."; else { std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) << "Disabling the inclusion of the conf.d directory...\n" << ConsoleColorTag(Console_Normal); - if(!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { + if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) - << "Failed to disable conf.d inclusion, it may already be disabled.\n" + << "Failed to disable the conf.d inclusion, it may already have been disabled.\n" << ConsoleColorTag(Console_Normal); } + /* Satellite/Clients should not include the api-users.conf file. + * The configuration should instead be managed via config sync or automation tools. + */ } return 0; @@ -707,6 +711,7 @@ int NodeWizardCommand::MasterSetup() const /* Global zones. */ std::vector globalZones { "global-templates", "director-global" }; + std::cout << "\nDefault global zones: " << boost::algorithm::join(globalZones, " "); std::cout << "\nDo you want to specify additional global zones? [y/N]: "; std::getline(std::cin, answer); @@ -813,7 +818,7 @@ wizard_global_zone_loop_start: } Log(LogInformation, "cli", "Updating constants.conf."); - + NodeUtility::CreateBackupFile(NodeUtility::GetConstantsConfPath()); NodeUtility::UpdateConstant("NodeName", cn); @@ -832,34 +837,32 @@ wizard_global_zone_loop_start: if (choice.Contains("n")) Log(LogInformation, "cli") - << "The deactivation of the conf.d directory was skipped."; + << "conf.d directory has not been disabled."; else { std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) - << "Disable the inclusion of the conf.d directory...\n" + << "Disabling the inclusion of the conf.d directory...\n" << ConsoleColorTag(Console_Normal); if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) - << "Failed to disable conf.d inclusion, it may already be disabled.\n" + << "Failed to disable the conf.d inclusion, it may already have been disabled.\n" << ConsoleColorTag(Console_Normal); } /* Include api-users.conf */ String apiUsersFilePath = Application::GetSysconfDir() + "/icinga2/conf.d/api-users.conf"; - std::ifstream apiUsersFile(apiUsersFilePath); std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) - << "Checking if api-users.conf exist...\n" + << "Checking if the api-users.conf file exists...\n" << ConsoleColorTag(Console_Normal); - if(apiUsersFile) - NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); - else - Log(LogWarning, "cli") - << "Included file dosen't exist " << apiUsersFilePath; + if (Utility::PathExists(apiUsersFilePath)) { + NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); + } else { + Log(LogWarning, "cli") + << "Included file '" << apiUsersFilePath << "' does not exist."; + } } - std::cout << "Done.\n\n"; - return 0; } From 81de396294183cbe905ea9036f6961b8e8ad1ef9 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Tue, 8 May 2018 16:31:06 +0200 Subject: [PATCH 8/8] Add documentation for CLI command parameters refs #4508 --- doc/06-distributed-monitoring.md | 108 +++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index f83f61542..491d2b5f7 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -205,6 +205,7 @@ ensure to collect the required information: Global zones | **Optional.** Allows to specify more global zones in addition to `global-templates` and `director-global`. Defaults to `n`. API bind host | **Optional.** Allows to specify the address the ApiListener is bound to. For advanced usage only. API bind port | **Optional.** Allows to specify the port the ApiListener is bound to. For advanced usage only (requires changing the default port 5665 everywhere). + Disable conf.d | **Optional.** Allows to disable the `include_recursive "conf.d"` directive except for the `api-users.conf` file in the `icinga2.conf` file. Defaults to `y`. Configuration on the master is discussed below. The setup wizard will ensure that the following steps are taken: @@ -213,6 +214,7 @@ The setup wizard will ensure that the following steps are taken: * Create a certificate for this node signed by the CA key. * Update the [zones.conf](04-configuring-icinga-2.md#zones-conf) file with the new zone hierarchy. * Update the [ApiListener](06-distributed-monitoring.md#distributed-monitoring-apilistener) and [constants](04-configuring-icinga-2.md#constants-conf) configuration. +* Update the [icinga2.conf](04-configuring-icinga-2.md#icinga2-conf) to disable the `conf.d` inclusion, and add the `api-users.conf` file inclusion. Here is an example of a master setup for the `icinga2-master1.localdomain` node on CentOS 7: @@ -243,6 +245,10 @@ Please specify the API bind host/port (optional): Bind Host []: Bind Port []: +Do you want to disable the inclusion of the conf.d directory [Y/n]: +Disabling the inclusion of the conf.d directory... +Checking if the api-users.conf file exists... + Done. Now restart your Icinga 2 daemon to finish the installation! @@ -337,7 +343,7 @@ object with at least the `actions/generate-ticket` permission. Retrieve the ticket on the master node `icinga2-master1.localdomain` with `curl`, for example: [root@icinga2-master1.localdomain /]# curl -k -s -u client-pki-ticket:bea11beb7b810ea9ce6ea -H 'Accept: application/json' \ - -X POST 'https://icinga2-master1.localdomain:5665/v1/actions/generate-ticket' -d '{ "cn": "icinga2-client1.localdomain" }' + -X POST 'https://localhost:5665/v1/actions/generate-ticket' -d '{ "cn": "icinga2-client1.localdomain" }' Store that ticket number for the satellite/client setup below. @@ -554,6 +560,16 @@ Default global zones: global-templates director-global Do you want to specify additional global zones? [y/N]: N ``` +Last but not least the wizard asks you whether you want to disable the inclusion of the local configuration +directory in `conf.d`, or not. Defaults to disabled, as clients either are checked via command endpoint, or +they receive configuration synced from the parent zone. + +``` +Do you want to disable the inclusion of the conf.d directory [Y/n]: Y +Disabling the inclusion of the conf.d directory... +``` + + The wizard proceeds and you are good to go. ``` @@ -595,6 +611,7 @@ Here is an overview of all parameters in detail: Local zone name | **Optional.** Allows to specify the name for the local zone. This comes in handy when this instance is a satellite, not a client. Defaults to the FQDN. Parent zone name | **Optional.** Allows to specify the name for the parent zone. This is important if the client has a satellite instance as parent, not the master. Defaults to `master`. Global zones | **Optional.** Allows to specify more global zones in addition to `global-templates` and `director-global`. Defaults to `n`. + Disable conf.d | **Optional.** Allows to disable the inclusion of the `conf.d` directory which holds local example configuration. Clients should retrieve their configuration from the parent node, or act as command endpoint execution bridge. Defaults to `y`. The setup wizard will ensure that the following steps are taken: @@ -605,7 +622,7 @@ The setup wizard will ensure that the following steps are taken: * Store the signed client certificate and ca.crt in `/var/lib/icinga2/certs`. * Update the `zones.conf` file with the new zone hierarchy. * Update `/etc/icinga2/features-enabled/api.conf` (`accept_config`, `accept_commands`) and `constants.conf`. - +* Update `/etc/icinga2/icinga2.conf` and comment out `include_recursive "conf.d"`. You can verify that the certificate files are stored in the `/var/lib/icinga2/certs` directory. @@ -782,11 +799,15 @@ Add a [global zone](06-distributed-monitoring.md#distributed-monitoring-global-z for syncing check commands later. Navigate to `C:\ProgramData\icinga2\etc\icinga2` and open the `zones.conf` file in your preferred editor. Add the following lines if not existing already: - object Zone "global-templates" { - global = true - } +``` +object Zone "global-templates" { + global = true +} +``` -Note: Packages >= 2.8 provide this configuration by default. +> **Note:** +> +> Packages >= 2.8 provide this configuration by default. You don't need any local configuration on the client except for CheckCommand definitions which can be synced using the global zone @@ -796,14 +817,23 @@ Navigate to `C:\ProgramData\icinga2\etc\icinga2` and open the `icinga2.conf` file in your preferred editor. Remove or comment (`//`) the following line: - // Commented out, not required on a client with top down mode - //include_recursive "conf.d" +``` +// Commented out, not required on a client with top down mode +//include_recursive "conf.d" +``` + +> **Note** +> +> Packages >= 2.9 provide an option in the setup wizard to disable this. +> Defaults to disabled. Validate the configuration on Windows open an administrator terminal and run the following command: - C:\WINDOWS\system32>cd "C:\Program Files\ICINGA2\sbin" - C:\Program Files\ICINGA2\sbin>icinga2.exe daemon -C +``` +C:\WINDOWS\system32>cd "C:\Program Files\ICINGA2\sbin" +C:\Program Files\ICINGA2\sbin>icinga2.exe daemon -C +``` **Note**: You have to run this command in a shell with `administrator` privileges. @@ -921,23 +951,34 @@ The `master` zone is a parent of the `icinga2-client1.localdomain` zone: In addition, add a [global zone](06-distributed-monitoring.md#distributed-monitoring-global-zone-config-sync) for syncing check commands later: - [root@icinga2-client1.localdomain /]# vim /etc/icinga2/zones.conf +``` +[root@icinga2-client1.localdomain /]# vim /etc/icinga2/zones.conf - object Zone "global-templates" { - global = true - } +object Zone "global-templates" { + global = true +} +``` -Note: Packages >= 2.8 provide this configuration by default. +> **Note:** +> +> Packages >= 2.8 provide this configuration by default. You don't need any local configuration on the client except for CheckCommand definitions which can be synced using the global zone above. Therefore disable the inclusion of the `conf.d` directory in `/etc/icinga2/icinga2.conf`. - [root@icinga2-client1.localdomain /]# vim /etc/icinga2/icinga2.conf +``` +[root@icinga2-client1.localdomain /]# vim /etc/icinga2/icinga2.conf - // Commented out, not required on a client as command endpoint - //include_recursive "conf.d" +// Commented out, not required on a client as command endpoint +//include_recursive "conf.d" +``` + +> **Note** +> +> Packages >= 2.9 provide an option in the setup wizard to disable this. +> Defaults to disabled. Edit the `api` feature on the client `icinga2-client1.localdomain` in the `/etc/icinga2/features-enabled/api.conf` file and make sure to set @@ -2560,16 +2601,25 @@ be passed (defaults to the FQDN). Common name (CN) | **Optional.** Specified with the `--cn` parameter. By convention this should be the host's FQDN. Defaults to the FQDN. Zone name | **Optional.** Specified with the `--zone` parameter. Defaults to `master`. Listen on | **Optional.** Specified with the `--listen` parameter. Syntax is `host,port`. + Disable conf.d | **Optional.** Specified with the `disable-confd` parameter. If provided, this disables the `include_recursive "conf.d"` directive and adds the `api-users.conf` file inclusion to `icinga2.conf`. Available since v2.9+. Not set by default for compatibility reasons with Puppet, Ansible, Chef, etc. Example: - [root@icinga2-master1.localdomain /]# icinga2 node setup --master +``` +[root@icinga2-master1.localdomain /]# icinga2 node setup --master +``` In case you want to bind the `ApiListener` object to a specific host/port you can specify it like this: --listen 192.68.56.101,5665 +In case you don't need anything in `conf.d`, use the following command line: + +``` +[root@icinga2-master1.localdomain /]# icinga2 node setup --master --disable-confd +``` + #### Node Setup with Satellites/Clients @@ -2634,6 +2684,7 @@ Pass the following details to the `node setup` CLI command: Accept config | **Optional.** Whether this node accepts configuration sync from the master node (required for [config sync mode](06-distributed-monitoring.md#distributed-monitoring-top-down-config-sync)). Accept commands | **Optional.** Whether this node accepts command execution messages from the master node (required for [command endpoint mode](06-distributed-monitoring.md#distributed-monitoring-top-down-command-endpoint)). Global zones | **Optional.** Allows to specify more global zones in addition to `global-templates` and `director-global`. + Disable conf.d | **Optional.** Specified with the `disable-confd` parameter. If provided, this disables the `include_recursive "conf.d"` directive in `icinga2.conf`. Available since v2.9+. Not set by default for compatibility reasons with Puppet, Ansible, Chef, etc. > **Note** > @@ -2641,14 +2692,17 @@ Pass the following details to the `node setup` CLI command: Example for Icinga 2 v2.9: - [root@icinga2-client1.localdomain /]# icinga2 node setup --ticket ead2d570e18c78abf285d6b85524970a0f69c22d \ - --cn icinga2-client1.localdomain \ - --endpoint icinga2-master1.localdomain \ - --zone icinga2-client1.localdomain \ - --parent_zone master \ - --parent_host icinga2-master1.localdomain \ - --trustedcert /var/lib/icinga2/certs/trusted-parent.crt \ - --accept-commands --accept-config +``` +[root@icinga2-client1.localdomain /]# icinga2 node setup --ticket ead2d570e18c78abf285d6b85524970a0f69c22d \ +--cn icinga2-client1.localdomain \ +--endpoint icinga2-master1.localdomain \ +--zone icinga2-client1.localdomain \ +--parent_zone master \ +--parent_host icinga2-master1.localdomain \ +--trustedcert /var/lib/icinga2/certs/trusted-parent.crt \ +--accept-commands --accept-config \ +--disable-confd +``` In case the client should connect to the master node, you'll need to modify the `--endpoint` parameter using the format `cn,host,port`: