aboutsummaryrefslogtreecommitdiff
path: root/lib/puppet/provider/cron
diff options
context:
space:
mode:
authorJorie Tappa <jorie@jorietappa.com>2018-07-31 14:56:46 -0500
committerJorie Tappa <jorie@jorietappa.com>2018-07-31 16:07:37 -0500
commita2af7dd0b9713f279724d2c7e6f17bfd8ce2d95b (patch)
tree6a90efd716578c7449ec69051279150308884414 /lib/puppet/provider/cron
parente74dce11298298889a40879aad1e2fcc27fa0559 (diff)
downloadpuppet-cron_core-a2af7dd0b9713f279724d2c7e6f17bfd8ce2d95b.tar.gz
puppet-cron_core-a2af7dd0b9713f279724d2c7e6f17bfd8ce2d95b.tar.bz2
Initial cron import from puppet 7a4c5f07bdf61a7bc7aa32a50e99489a604eac52
Diffstat (limited to 'lib/puppet/provider/cron')
-rw-r--r--lib/puppet/provider/cron/crontab.rb297
1 files changed, 297 insertions, 0 deletions
diff --git a/lib/puppet/provider/cron/crontab.rb b/lib/puppet/provider/cron/crontab.rb
new file mode 100644
index 0000000..b24ed14
--- /dev/null
+++ b/lib/puppet/provider/cron/crontab.rb
@@ -0,0 +1,297 @@
+require 'puppet/provider/parsedfile'
+
+Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFile, :default_target => ENV["USER"] || "root") do
+ commands :crontab => "crontab"
+
+ text_line :comment, :match => %r{^\s*#}, :post_parse => proc { |record|
+ record[:name] = $1 if record[:line] =~ /Puppet Name: (.+)\s*$/
+ }
+
+ text_line :blank, :match => %r{^\s*$}
+
+ text_line :environment, :match => %r{^\s*\w+\s*=}
+
+ def self.filetype
+ tabname = case Facter.value(:osfamily)
+ when "Solaris"
+ :suntab
+ when "AIX"
+ :aixtab
+ else
+ :crontab
+ end
+
+ Puppet::Util::FileType.filetype(tabname)
+ end
+
+ self::TIME_FIELDS = [:minute, :hour, :monthday, :month, :weekday]
+
+ record_line :crontab,
+ :fields => %w{time command},
+ :match => %r{^\s*(@\w+|\S+\s+\S+\s+\S+\s+\S+\s+\S+)\s+(.+)$},
+ :absent => '*',
+ :block_eval => :instance do
+
+ def post_parse(record)
+ time = record.delete(:time)
+ if match = /@(\S+)/.match(time)
+ # is there another way to access the constant?
+ Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.each { |f| record[f] = :absent }
+ record[:special] = match.captures[0]
+ elsif match = /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/.match(time)
+ record[:special] = :absent
+ Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.zip(match.captures).each do |field,value|
+ if value == self.absent
+ record[field] = :absent
+ else
+ record[field] = value.split(",")
+ end
+ end
+ else
+ raise Puppet::Error, _("Line got parsed as a crontab entry but cannot be handled. Please file a bug with the contents of your crontab")
+ end
+ record
+ end
+
+ def pre_gen(record)
+ if record[:special] and record[:special] != :absent
+ record[:special] = "@#{record[:special]}"
+ end
+
+ Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.each do |field|
+ if vals = record[field] and vals.is_a?(Array)
+ record[field] = vals.join(",")
+ end
+ end
+ record
+ end
+
+ def to_line(record)
+ str = ""
+ record[:name] = nil if record[:unmanaged]
+ str = "# Puppet Name: #{record[:name]}\n" if record[:name]
+ if record[:environment] and record[:environment] != :absent
+ str += record[:environment].map {|line| "#{line}\n"}.join('')
+ end
+ if record[:special] and record[:special] != :absent
+ fields = [:special, :command]
+ else
+ fields = Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS + [:command]
+ end
+ str += record.values_at(*fields).map do |field|
+ if field.nil? or field == :absent
+ self.absent
+ else
+ field
+ end
+ end.join(self.joiner)
+ str
+ end
+ end
+
+ def create
+ if resource.should(:command) then
+ super
+ else
+ resource.err _("no command specified, cannot create")
+ end
+ end
+
+ # Look up a resource with a given name whose user matches a record target
+ #
+ # @api private
+ #
+ # @note This overrides the ParsedFile method for finding resources by name,
+ # so that only records for a given user are matched to resources of the
+ # same user so that orphaned records in other crontabs don't get falsely
+ # matched (#2251)
+ #
+ # @param [Hash<Symbol, Object>] record
+ # @param [Array<Puppet::Resource>] resources
+ #
+ # @return [Puppet::Resource, nil] The resource if found, else nil
+ def self.resource_for_record(record, resources)
+ resource = super
+
+ if resource
+ target = resource[:target] || resource[:user]
+ if record[:target] == target
+ resource
+ end
+ end
+ end
+
+ # Return the header placed at the top of each generated file, warning
+ # users that modifying this file manually is probably a bad idea.
+ def self.header
+%{# HEADER: This file was autogenerated at #{Time.now} by puppet.
+# HEADER: While it can still be managed manually, it is definitely not recommended.
+# HEADER: Note particularly that the comments starting with 'Puppet Name' should
+# HEADER: not be deleted, as doing so could cause duplicate cron jobs.\n}
+ end
+
+ # Regex for finding one vixie cron header.
+ def self.native_header_regex
+ /# DO NOT EDIT THIS FILE.*?Cron version.*?vixie.*?\n/m
+ end
+
+ # If a vixie cron header is found, it should be dropped, cron will insert
+ # a new one in any case, so we need to avoid duplicates.
+ def self.drop_native_header
+ true
+ end
+
+ # See if we can match the record against an existing cron job.
+ def self.match(record, resources)
+ # if the record is named, do not even bother (#19876)
+ # except the resource name was implicitly generated (#3220)
+ return false if record[:name] and !record[:unmanaged]
+ resources.each do |name, resource|
+ # Match the command first, since it's the most important one.
+ next unless record[:target] == resource[:target]
+ next unless record[:command] == resource.value(:command)
+
+ # Now check the time fields
+ compare_fields = self::TIME_FIELDS + [:special]
+
+ matched = true
+ compare_fields.each do |field|
+ # If the resource does not manage a property (say monthday) it should
+ # always match. If it is the other way around (e.g. resource defines
+ # a should value for :special but the record does not have it, we do
+ # not match
+ next unless resource[field]
+ unless record.include?(field)
+ matched = false
+ break
+ end
+
+ if record_value = record[field] and resource_value = resource.value(field)
+ # The record translates '*' into absent in the post_parse hook and
+ # the resource type does exactly the opposite (alias :absent to *)
+ next if resource_value == '*' and record_value == :absent
+ next if resource_value == record_value
+ end
+ matched =false
+ break
+ end
+ return resource if matched
+ end
+ false
+ end
+
+ @name_index = 0
+
+ # Collapse name and env records.
+ def self.prefetch_hook(records)
+ name = nil
+ envs = nil
+ result = records.each { |record|
+ case record[:record_type]
+ when :comment
+ if record[:name]
+ name = record[:name]
+ record[:skip] = true
+
+ # Start collecting env values
+ envs = []
+ end
+ when :environment
+ # If we're collecting env values (meaning we're in a named cronjob),
+ # store the line and skip the record.
+ if envs
+ envs << record[:line]
+ record[:skip] = true
+ end
+ when :blank
+ # nothing
+ else
+ if name
+ record[:name] = name
+ name = nil
+ else
+ cmd_string = record[:command].gsub(/\s+/, "_")
+ index = ( @name_index += 1 )
+ record[:name] = "unmanaged:#{cmd_string}-#{ index.to_s }"
+ record[:unmanaged] = true
+ end
+ if envs.nil? or envs.empty?
+ record[:environment] = :absent
+ else
+ # Collect all of the environment lines, and mark the records to be skipped,
+ # since their data is included in our crontab record.
+ record[:environment] = envs
+
+ # And turn off env collection again
+ envs = nil
+ end
+ end
+ }.reject { |record| record[:skip] }
+ result
+ end
+
+ def self.to_file(records)
+ text = super
+ # Apparently Freebsd will "helpfully" add a new TZ line to every
+ # single cron line, but not in all cases (e.g., it doesn't do it
+ # on my machine). This is my attempt to fix it so the TZ lines don't
+ # multiply.
+ if text =~ /(^TZ=.+\n)/
+ tz = $1
+ text.sub!(tz, '')
+ text = tz + text
+ end
+ text
+ end
+
+ def user=(user)
+ # we have to mark the target as modified first, to make sure that if
+ # we move a cronjob from userA to userB, userA's crontab will also
+ # be rewritten
+ mark_target_modified
+ @property_hash[:user] = user
+ @property_hash[:target] = user
+ end
+
+ def user
+ @property_hash[:user] || @property_hash[:target]
+ end
+
+ CRONTAB_DIR = case Facter.value("osfamily")
+ when "Debian", "HP-UX"
+ "/var/spool/cron/crontabs"
+ when /BSD/
+ "/var/cron/tabs"
+ when "Darwin"
+ "/usr/lib/cron/tabs/"
+ else
+ "/var/spool/cron"
+ end
+
+ # Yield the names of all crontab files stored on the local system.
+ #
+ # @note Ignores files that are not writable for the puppet process.
+ #
+ # @api private
+ def self.enumerate_crontabs
+ Puppet.debug "looking for crontabs in #{CRONTAB_DIR}"
+ return unless File.readable?(CRONTAB_DIR)
+ Dir.foreach(CRONTAB_DIR) do |file|
+ path = "#{CRONTAB_DIR}/#{file}"
+ yield(file) if File.file?(path) and File.writable?(path)
+ end
+ end
+
+
+ # Include all plausible crontab files on the system
+ # in the list of targets (#11383 / PUP-1381)
+ def self.targets(resources = nil)
+ targets = super(resources)
+ enumerate_crontabs do |target|
+ targets << target
+ end
+ targets.uniq
+ end
+
+end
+