summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2020-04-09 12:07:03 -0700
committerGitHub <noreply@github.com>2020-04-09 12:07:03 -0700
commit86f96689f5c90c0ce3222a1df4acdd8488c023b6 (patch)
treece72a508e51812101fdc94bea41045fe00709efe
parent92022175c0682723606abd563edf87b1e0200106 (diff)
parentba4d8adf0f53d812edbc69ef2b8f83c274221907 (diff)
downloadchef-86f96689f5c90c0ce3222a1df4acdd8488c023b6.tar.gz
Merge pull request #9624 from chef/systemd_timer
Add chef_client_systemd_timer resource
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/default.rb6
-rw-r--r--lib/chef/resource/chef_client_systemd_timer.rb177
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/unit/resource/chef_client_systemd_timer_spec.rb70
4 files changed, 253 insertions, 1 deletions
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
index c1a335d98c..af1e516030 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
@@ -67,7 +67,6 @@ end
include_recipe "chef-client::delete_validation"
include_recipe "chef-client::config"
-include_recipe "chef-client"
include_recipe "openssh"
@@ -149,6 +148,11 @@ chef_client_cron "Run chef-client with base recipe" do
daemon_options ["--override-runlist mycorp_base::default"]
end
+chef_client_systemd_timer "Run chef-client as a systemd timer" do
+ interval "1hr"
+ only_if { systemd? }
+end
+
include_recipe "::chef-vault" unless includes_recipe?("end_to_end::chef-vault")
include_recipe "::alternatives"
include_recipe "::tests"
diff --git a/lib/chef/resource/chef_client_systemd_timer.rb b/lib/chef/resource/chef_client_systemd_timer.rb
new file mode 100644
index 0000000000..359fe951a0
--- /dev/null
+++ b/lib/chef/resource/chef_client_systemd_timer.rb
@@ -0,0 +1,177 @@
+#
+# Copyright:: 2020, Chef Software Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+require_relative "../dist"
+
+class Chef
+ class Resource
+ class ChefClientSystemdTimer < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_systemd_timer
+
+ description "Use the chef_client_systemd_timer resource to setup the #{Chef::Dist::PRODUCT} to run as a systemd timer."
+ introduced "16.0"
+ examples <<~DOC
+ Setup #{Chef::Dist::PRODUCT} to run using the default 30 minute cadence
+ ```ruby
+ chef_client_systemd_timer "Run chef-client as a systemd timer"
+ ```
+
+ Run #{Chef::Dist::PRODUCT} every 1 hour
+ ```ruby
+ chef_client_systemd_timer "Run chef-client every 1 hour" do
+ interval "1hr"
+ end
+ ```
+
+ Run #{Chef::Dist::PRODUCT} with extra options passed to the client
+ ```ruby
+ chef_client_systemd_timer "Run an override recipe" do
+ daemon_options ["--override-runlist mycorp_base::default"]
+ end
+ ```
+ DOC
+
+ property :job_name, String,
+ description: "The name of the system timer to create.",
+ default: Chef::Dist::CLIENT
+
+ property :description, String,
+ description: "The description to add to the systemd timer. This will be displayed when running `systemctl status` for the timer.",
+ default: "#{Chef::Dist::PRODUCT} periodic execution"
+
+ property :user, String,
+ description: "The name of the user that #{Chef::Dist::PRODUCT} runs as.",
+ default: "root"
+
+ property :delay_after_boot, String,
+ description: "The time to wait after booting before the interval starts. This is expressed as a systemd time span such as `300seconds`, `1hr`, or `1m`. See <https://www.freedesktop.org/software/systemd/man/systemd.time.html> for a complete list of allowed time span values.",
+ default: "1min"
+
+ property :interval, String,
+ description: "The interval to wait between executions. This is expressed as a systemd time span such as `300seconds`, `1hr`, or `1m`. See <https://www.freedesktop.org/software/systemd/man/systemd.time.html> for a complete list of allowed time span values.",
+ default: "30min"
+
+ property :splay, String,
+ description: "A interval between 0 and X to add to the interval so that all #{Chef::Dist::CLIENT} commands don't execute at the same time. This is expressed as a systemd time span such as `300seconds`, `1hr`, or `1m`. See <https://www.freedesktop.org/software/systemd/man/systemd.time.html> for a complete list of allowed time span values.",
+ default: "5min"
+
+ property :accept_chef_license, [true, false],
+ description: "Accept the Chef Online Master License and Services Agreement. See https://www.chef.io/online-master-agreement/",
+ default: false
+
+ property :run_on_battery, [true, false],
+ description: "Run the timer for #{Chef::Dist::PRODUCT} if the system is on battery.",
+ default: true
+
+ property :config_directory, String,
+ description: "The path of the config directory.",
+ default: Chef::Dist::CONF_DIR
+
+ property :chef_binary_path, String,
+ description: "The path to the #{Chef::Dist::CLIENT} binary.",
+ default: "/opt/#{Chef::Dist::DIR_SUFFIX}/bin/#{Chef::Dist::CLIENT}"
+
+ property :daemon_options, Array,
+ description: "An array of options to pass to the #{Chef::Dist::CLIENT} command.",
+ default: lazy { [] }
+
+ property :environment, Hash,
+ description: "A Hash containing additional arbitrary environment variables under which the systemd timer will be run in the form of ``({'ENV_VARIABLE' => 'VALUE'})``.",
+ default: lazy { {} }
+
+ action :add do
+ systemd_unit "#{new_resource.job_name}.service" do
+ content service_content
+ action :create
+ end
+
+ systemd_unit "#{new_resource.job_name}.timer" do
+ content timer_content
+ action %i{create enable start}
+ end
+ end
+
+ action :remove do
+ systemd_unit "#{new_resource.job_name}.service" do
+ action :remove
+ end
+
+ systemd_unit "#{new_resource.job_name}.timer" do
+ action :remove
+ end
+ end
+
+ action_class do
+ #
+ # The chef-client command to run in the systemd unit.
+ #
+ # @return [String]
+ #
+ def chef_client_cmd
+ cmd = "#{new_resource.chef_binary_path}"
+ cmd << " #{new_resource.daemon_options.join(" ")}" unless new_resource.daemon_options.empty?
+ cmd << " --chef-license accept" if new_resource.accept_chef_license
+ cmd << " -c #{::File.join(new_resource.config_directory, "client.rb")}"
+ cmd
+ end
+
+ #
+ # The timer content to pass to the systemd_unit
+ #
+ # @return [Hash]
+ #
+ def timer_content
+ {
+ "Unit" => { "Description" => new_resource.description },
+ "Timer" => {
+ "OnBootSec" => new_resource.delay_after_boot,
+ "OnUnitActiveSec" => new_resource.interval,
+ "RandomizedDelaySec" => new_resource.splay,
+ },
+ "Install" => { "WantedBy" => "timers.target" },
+ }
+ end
+
+ #
+ # The service content to pass to the systemd_unit
+ #
+ # @return [Hash]
+ #
+ def service_content
+ unit = {
+ "Unit" => {
+ "Description" => new_resource.description,
+ "After" => "network.target auditd.service",
+ },
+ "Service" => {
+ "Type" => "oneshot",
+ "ExecStart" => chef_client_cmd,
+ "SuccessExitStatus" => [3, 213, 35, 37, 41],
+ },
+ "Install" => { "WantedBy" => "multi-user.target" },
+ }
+
+ unit["Service"]["ConditionACPower"] = "true" unless new_resource.run_on_battery
+ unit["Service"]["Environment"] = new_resource.environment.collect { |k, v| "\"#{k}=#{v}\"" } unless new_resource.environment.empty?
+ unit
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 66bec89658..0d1ffeb273 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -29,6 +29,7 @@ require_relative "resource/build_essential"
require_relative "resource/cookbook_file"
require_relative "resource/chef_client_cron"
require_relative "resource/chef_client_scheduled_task"
+require_relative "resource/chef_client_systemd_timer"
require_relative "resource/chef_gem"
require_relative "resource/chef_handler"
require_relative "resource/chef_sleep"
diff --git a/spec/unit/resource/chef_client_systemd_timer_spec.rb b/spec/unit/resource/chef_client_systemd_timer_spec.rb
new file mode 100644
index 0000000000..139d4c6ba3
--- /dev/null
+++ b/spec/unit/resource/chef_client_systemd_timer_spec.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: 2020, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::ChefClientSystemdTimer do
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:resource) { Chef::Resource::ChefClientSystemdTimer.new("fakey_fakerton", run_context) }
+ let(:provider) { resource.provider_for_action(:add) }
+
+ it "sets the default action as :add" do
+ expect(resource.action).to eql([:add])
+ end
+
+ it "user defaults to root" do
+ expect(resource.user).to eql("root")
+ end
+
+ it "builds a default value for chef_binary_path dist values" do
+ expect(resource.chef_binary_path).to eql("/opt/chef/bin/chef-client")
+ end
+
+ it "supports :add and :remove actions" do
+ expect { resource.action :add }.not_to raise_error
+ expect { resource.action :remove }.not_to raise_error
+ end
+
+ describe "#chef_client_cmd" do
+ it "creates a valid command if using all default properties" do
+ expect(provider.chef_client_cmd).to eql("/opt/chef/bin/chef-client -c /etc/chef/client.rb")
+ end
+
+ it "uses daemon_options if set" do
+ resource.daemon_options ["--foo 1", "--bar 2"]
+ expect(provider.chef_client_cmd).to eql("/opt/chef/bin/chef-client --foo 1 --bar 2 -c /etc/chef/client.rb")
+ end
+
+ it "uses custom config dir if set" do
+ resource.config_directory "/etc/some_other_dir"
+ expect(provider.chef_client_cmd).to eql("/opt/chef/bin/chef-client -c /etc/some_other_dir/client.rb")
+ end
+
+ it "uses custom chef-client binary if set" do
+ resource.chef_binary_path "/usr/local/bin/chef-client"
+ expect(provider.chef_client_cmd).to eql("/usr/local/bin/chef-client -c /etc/chef/client.rb")
+ end
+
+ it "sets the license acceptance flag if set" do
+ resource.accept_chef_license true
+ expect(provider.chef_client_cmd).to eql("/opt/chef/bin/chef-client --chef-license accept -c /etc/chef/client.rb")
+ end
+ end
+end