diff options
-rw-r--r-- | kitchen-tests/cookbooks/end_to_end/metadata.rb | 1 | ||||
-rw-r--r-- | kitchen-tests/cookbooks/end_to_end/recipes/default.rb | 10 | ||||
-rw-r--r-- | kitchen-tests/kitchen.travis.yml | 3 | ||||
-rw-r--r-- | lib/chef/resource/cron_access.rb | 70 | ||||
-rw-r--r-- | lib/chef/resource/cron_d.rb | 233 | ||||
-rw-r--r-- | lib/chef/resource/support/cron.d.erb | 28 | ||||
-rw-r--r-- | lib/chef/resource/support/cron_access.erb | 4 | ||||
-rw-r--r-- | lib/chef/resources.rb | 4 | ||||
-rw-r--r-- | spec/unit/resource/cron_access_spec.rb | 36 | ||||
-rw-r--r-- | spec/unit/resource/cron_d_spec.rb | 90 |
10 files changed, 475 insertions, 4 deletions
diff --git a/kitchen-tests/cookbooks/end_to_end/metadata.rb b/kitchen-tests/cookbooks/end_to_end/metadata.rb index c5300705c6..83cea5c9a8 100644 --- a/kitchen-tests/cookbooks/end_to_end/metadata.rb +++ b/kitchen-tests/cookbooks/end_to_end/metadata.rb @@ -15,7 +15,6 @@ depends "resolver" depends "selinux" depends "ubuntu" depends "users" -depends "cron" depends "git" supports "ubuntu" diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb index 77c5d2438e..3985cefd5a 100644 --- a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb +++ b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb @@ -63,12 +63,18 @@ include_recipe "nscd" include_recipe "logrotate" -include_recipe "cron" - include_recipe "git" directory "/etc/ssl" +cron_access "bob" + +cron_d "some random cron job" do + minute 0 + hour 23 + command "/usr/bin/true" +end + # Generate new key and certificate openssl_dhparam "/etc/ssl/dhparam.pem" do key_length 1024 diff --git a/kitchen-tests/kitchen.travis.yml b/kitchen-tests/kitchen.travis.yml index aa15da8795..83c6ee839d 100644 --- a/kitchen-tests/kitchen.travis.yml +++ b/kitchen-tests/kitchen.travis.yml @@ -35,6 +35,7 @@ platforms: pid_one_command: /sbin/init intermediate_instructions: - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers + - RUN yum install -y cronie # TODO: remove this anytime past 8/13 - name: amazonlinux-2 driver: @@ -63,6 +64,7 @@ platforms: pid_one_command: /sbin/init intermediate_instructions: - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers + - RUN yum install -y cronie # TODO: remove this anytime past 8/13 - name: centos-7 driver: @@ -78,6 +80,7 @@ platforms: pid_one_command: /usr/lib/systemd/systemd intermediate_instructions: - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers + - RUN yum install -y cronie # TODO: remove this anytime past 8/13 - name: ubuntu-14.04 driver: diff --git a/lib/chef/resource/cron_access.rb b/lib/chef/resource/cron_access.rb new file mode 100644 index 0000000000..dbcc0709b7 --- /dev/null +++ b/lib/chef/resource/cron_access.rb @@ -0,0 +1,70 @@ +# +# Author:: Sander Botman <sbotman@schubergphilis.com> +# Author:: Tim Smith <tsmith@chef.io> +# +# Copyright:: 2014-2018, Sander Botman +# Copyright:: 2018, 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 "chef/resource" + +class Chef + class Resource + class CronAccess < Chef::Resource + preview_resource true + resource_name :cron_access + provides(:cron_manage) # legacy name @todo in Chef 15 we should { true } this so it wins over the cookbook + + introduced "14.4" + description "Use the cron_access resource to manage the /etc/cron.allow and /etc/cron.deny files." + + property :user, String, + description: "The user to allow or deny. If not provided we'll use the resource name.", + name_property: true + + action :allow do + description "Add the user to the cron.deny file." + + with_run_context :root do + edit_resource(:template, "/etc/cron.allow") do |new_resource| + source ::File.expand_path("../support/cron_access.erb", __FILE__) + local true + mode "0600" + variables["users"] ||= [] + variables["users"] << new_resource.user + action :nothing + delayed_action :create + end + end + end + + action :deny do + description "Add the user to the cron.allow file." + + with_run_context :root do + edit_resource(:template, "/etc/cron.deny") do |new_resource| + source ::File.expand_path("../support/cron_access.erb", __FILE__) + local true + mode "0600" + variables["users"] ||= [] + variables["users"] << new_resource.user + action :nothing + delayed_action :create + end + end + end + end + end +end diff --git a/lib/chef/resource/cron_d.rb b/lib/chef/resource/cron_d.rb new file mode 100644 index 0000000000..ba78008ab8 --- /dev/null +++ b/lib/chef/resource/cron_d.rb @@ -0,0 +1,233 @@ +# +# Copyright:: 2008-2018, 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 "chef/resource" +require "shellwords" + +class Chef + class Resource + class CronD < Chef::Resource + preview_resource true + resource_name :cron_d + + introduced "14.4" + description "Use the cron_d resource to manage cron definitions in /etc/cron.d. This is similar to the 'cron' resource, but it does not use the monolithic /etc/crontab file." + + # validate a provided value is between two other provided values + # we also allow * as a valid input + # @param spec the value to validate + # @param min the lowest value allowed + # @param max the highest value allowed + # @return [Boolean] valid or not? + def self.validate_numeric(spec, min, max) + return true if spec == "*" + # binding.pry + if spec.respond_to? :to_int + return false unless spec >= min && spec <= max + return true + end + + # Lists of invidual values, ranges, and step values all share the validity range for type + spec.split(%r{\/|-|,}).each do |x| + next if x == "*" + return false unless x =~ /^\d+$/ + x = x.to_i + return false unless x >= min && x <= max + end + true + end + + # validate the provided month value to be jan - dec, 1 - 12, or * + # @param spec the value to validate + # @return [Boolean] valid or not? + def self.validate_month(spec) + return true if spec == "*" + if spec.respond_to? :to_int + validate_numeric(spec, 1, 12) + elsif spec.respond_to? :to_str + return true if spec == "*" + # Named abbreviations are permitted but not as part of a range or with stepping + return true if %w{jan feb mar apr may jun jul aug sep oct nov dec}.include? spec.downcase + # 1-12 are legal for months + validate_numeric(spec, 1, 12) + else + false + end + end + + # validate the provided day of the week is sun-sat, 0-7, or * + # @param spec the value to validate + # @return [Boolean] valid or not? + def self.validate_dow(spec) + return true if spec == "*" + if spec.respond_to? :to_int + validate_numeric(spec, 0, 7) + elsif spec.respond_to? :to_str + return true if spec == "*" + # Named abbreviations are permitted but not as part of a range or with stepping + return true if %w{sun mon tue wed thu fri sat}.include? spec.downcase + # 0-7 are legal for days of week + validate_numeric(spec, 0, 7) + else + false + end + end + + property :cron_name, String, + description: "Set the name of the cron job. If this isn't specified we'll use the resource name.", + name_property: true + + property :cookbook, String + + property :predefined_value, String, + description: 'Schedule your cron job with one of the special predefined value instead of ** * pattern. This correspond to "@reboot", "@yearly", "@annually", "@monthly", "@weekly", "@daily", "@midnight" or "@hourly".', + equal_to: %w{ @reboot @yearly @annually @monthly @weekly @daily @midnight @hourly } + + property :minute, [Integer, String], + description: "The minute to schedule the cron job to run at. Valid values: 0-59.", + default: "*", callbacks: { + "should be a valid minute spec" => ->(spec) { validate_numeric(spec, 0, 59) }, + } + + property :hour, [Integer, String], + description: "The hour to schedule the cron job to run at. Valid values: 0-23.", + default: "*", callbacks: { + "should be a valid hour spec" => ->(spec) { validate_numeric(spec, 0, 23) }, + } + + property :day, [Integer, String], + description: "The day to schedule the cron job to run at. Valid values: 1-31.", + default: "*", callbacks: { + "should be a valid day spec" => ->(spec) { validate_numeric(spec, 1, 31) }, + } + + property :month, [Integer, String], + description: "The month to schedule the cron job to run at. Valid values: 1-12, jan-dec, or *.", + default: "*", callbacks: { + "should be a valid month spec" => ->(spec) { validate_month(spec) }, + } + + property :weekday, [Integer, String], + description: "The day to schedule the cron job to run at. Valid values: 0-7, mon-sun, or *.", + default: "*", callbacks: { + "should be a valid weekday spec" => ->(spec) { validate_dow(spec) }, + } + + property :command, String, + description: "The command to run.", + required: true + + property :user, String, + description: "The user to run the cron job as.", + default: "root" + + property :mailto, [String, NilClass], + description: "Set the MAILTO environment variable in the cron.d file." + + property :path, [String, NilClass], + description: "Set the PATH environment variable in the cron.d file." + + property :home, [String, NilClass], + description: "Set the HOME environment variable in the cron.d file." + + property :shell, [String, NilClass], + description: "Set the HOME environment variable in the cron.d file." + + property :comment, [String, NilClass], + description: "A comment to place in the cron.d file." + + property :environment, Hash, + description: "A Hash containing additional arbitrary environment variables under which the cron job will be run.", + default: {} + + property :mode, [String, Integer], + description: "The octal mode of the generated crontab file.", + default: "0600" + + # warn if someone passes the deprecated cookbook property + def after_created + raise ArgumentError, "The 'cookbook' property for the cron_d resource is no longer supported now that this resource ships in Chef itself." if new_resource.cookbook + end + + action :create do + description "Add a cron definition file to /etc/cron.d." + + create_template(:create) + end + + action :create_if_missing do + description "Add a cron definition file to /etc/cron.d, but do not update an existing file." + + create_template(:create_if_missing) + end + + action :delete do + description "Remove a cron definition file from /etc/cron.d if it exists." + + # cleanup the legacy named job if it exists + file "legacy named cron.d file" do + path "/etc/cron.d/#{new_resource.cron_name}" + action :delete + end + + file "/etc/cron.d/#{sanitized_name}" do + action :delete + end + end + + action_class do + def sanitized_name + new_resource.cron_name.tr(".", "-") + end + + def create_template(create_action) + # cleanup the legacy named job if it exists + file "#{new_resource.cron_name} legacy named cron.d file" do + path "/etc/cron.d/#{new_resource.cron_name}" + action :delete + only_if { new_resource.cron_name != sanitized_name } + end + + # @todo this is Chef 12 era cleanup. Someday we should remove it all + template "/etc/cron.d/#{sanitized_name}" do + source ::File.expand_path("../support/cron.d.erb", __FILE__) + local true + mode new_resource.mode + variables( + name: sanitized_name, + predefined_value: new_resource.predefined_value, + minute: new_resource.minute, + hour: new_resource.hour, + day: new_resource.day, + month: new_resource.month, + weekday: new_resource.weekday, + command: new_resource.command, + user: new_resource.user, + mailto: new_resource.mailto, + path: new_resource.path, + home: new_resource.home, + shell: new_resource.shell, + comment: new_resource.comment, + environment: new_resource.environment + ) + action create_action + end + end + end + end + end +end diff --git a/lib/chef/resource/support/cron.d.erb b/lib/chef/resource/support/cron.d.erb new file mode 100644 index 0000000000..2f27ecb36b --- /dev/null +++ b/lib/chef/resource/support/cron.d.erb @@ -0,0 +1,28 @@ +# Generated by Chef. Changes will be overwritten. +<% if @mailto -%> +MAILTO=<%= @mailto %> +<% end -%> +<% if @path -%> +PATH=<%= @path %> +<% end -%> +<% if @shell -%> +SHELL=<%= @shell %> +<% end -%> +<% if @home -%> +HOME=<%= @home %> +<% end -%> +<% if @random_delay -%> +RANDOM_DELAY=<%= @random_delay %> +<% end -%> +<% @environment.each do |key, val| -%> +<%= key %>=<%= val.to_s.shellescape %> +<% end -%> + +<% if @comment -%> +# <%= @comment %> +<% end -%> +<% if @predefined_value -%> +<%= @predefined_value %> <%= @user %> <%= @command %> +<% else -%> +<%= @minute %> <%= @hour %> <%= @day %> <%= @month %> <%= @weekday %> <%= @user %> <%= @command %> +<% end -%> diff --git a/lib/chef/resource/support/cron_access.erb b/lib/chef/resource/support/cron_access.erb new file mode 100644 index 0000000000..fdf61172ab --- /dev/null +++ b/lib/chef/resource/support/cron_access.erb @@ -0,0 +1,4 @@ +# Generated by Chef. Changes will be overwritten. +<% @users.sort.uniq.each do |user| -%> +<%= user %> +<% end -%> diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index c13a01c352..069c35691f 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2010-2016, Chef Software, Inc. +# Copyright:: Copyright 2010-2018, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +31,8 @@ require "chef/resource/chocolatey_config" require "chef/resource/chocolatey_package" require "chef/resource/chocolatey_source" require "chef/resource/cron" +require "chef/resource/cron_access" +require "chef/resource/cron_d" require "chef/resource/csh" require "chef/resource/directory" require "chef/resource/dmg_package" diff --git a/spec/unit/resource/cron_access_spec.rb b/spec/unit/resource/cron_access_spec.rb new file mode 100644 index 0000000000..8fd65a9c60 --- /dev/null +++ b/spec/unit/resource/cron_access_spec.rb @@ -0,0 +1,36 @@ +# +# Copyright:: Copyright 2018, 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::CronAccess do + let(:resource) { Chef::Resource::CronAccess.new("bob") } + + it "has a default action of [:deny]" do + expect(resource.action).to eql([:allow]) + end + + it "accepts create or delete for action" do + expect { resource.action :allow }.not_to raise_error + expect { resource.action :deny }.not_to raise_error + expect { resource.action :lolcat }.to raise_error(ArgumentError) + end + + it "the user property is the name_property" do + expect(resource.user).to eql("bob") + end +end diff --git a/spec/unit/resource/cron_d_spec.rb b/spec/unit/resource/cron_d_spec.rb new file mode 100644 index 0000000000..6d29a17aaf --- /dev/null +++ b/spec/unit/resource/cron_d_spec.rb @@ -0,0 +1,90 @@ +# +# Copyright:: Copyright 2018, 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::CronD do + let(:resource) { Chef::Resource::CronD.new("cronify") } + + it "has a default action of [:create]" do + expect(resource.action).to eql([:create]) + end + + it "accepts create or delete for action" do + expect { resource.action :create }.not_to raise_error + expect { resource.action :delete }.not_to raise_error + expect { resource.action :lolcat }.to raise_error(ArgumentError) + end + + it "the cron_name property is the name_property" do + expect(resource.cron_name).to eql("cronify") + end + + context "#validate_dow" do + it "it accepts a string day" do + expect(Chef::Resource::CronD.validate_dow("mon")).to be true + end + + it "it accepts an integer day" do + expect(Chef::Resource::CronD.validate_dow(0)).to be true + end + + it "it accepts the string of *" do + expect(Chef::Resource::CronD.validate_dow("*")).to be true + end + + it "returns false for an out of range integer" do + expect(Chef::Resource::CronD.validate_dow(8)).to be false + end + + it "returns false for an invalid string" do + expect(Chef::Resource::CronD.validate_dow("monday")).to be false + end + end + + context "#validate_month" do + it "it accepts a string month" do + expect(Chef::Resource::CronD.validate_month("feb")).to be true + end + + it "it accepts an integer month" do + expect(Chef::Resource::CronD.validate_month(2)).to be true + end + + it "it accepts the string of *" do + expect(Chef::Resource::CronD.validate_month("*")).to be true + end + + it "returns false for an out of range integer" do + expect(Chef::Resource::CronD.validate_month(13)).to be false + end + + it "returns false for an invalid string" do + expect(Chef::Resource::CronD.validate_month("janurary")).to be false + end + end + + context "#validate_numeric" do + it "returns true if the value is in the allowed range" do + expect(Chef::Resource::CronD.validate_numeric(5, 1, 100)).to be true + end + + it "returns false if the value is out of the allowed range" do + expect(Chef::Resource::CronD.validate_numeric(-1, 1, 100)).to be false + end + end +end |