diff options
author | Tim Smith <tsmith@chef.io> | 2020-04-09 12:07:03 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-09 12:07:03 -0700 |
commit | 86f96689f5c90c0ce3222a1df4acdd8488c023b6 (patch) | |
tree | ce72a508e51812101fdc94bea41045fe00709efe | |
parent | 92022175c0682723606abd563edf87b1e0200106 (diff) | |
parent | ba4d8adf0f53d812edbc69ef2b8f83c274221907 (diff) | |
download | chef-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.rb | 6 | ||||
-rw-r--r-- | lib/chef/resource/chef_client_systemd_timer.rb | 177 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 | ||||
-rw-r--r-- | spec/unit/resource/chef_client_systemd_timer_spec.rb | 70 |
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 |