class Puppet::Module::Task

Constants

FORBIDDEN_EXTENSIONS
MOUNTS

Attributes

metadata_file[R]
module[R]
name[R]

Public Class Methods

find_files(name, directory, metadata, executables, envname = nil) click to toggle source
    # File lib/puppet/module/task.rb
193 def self.find_files(name, directory, metadata, executables, envname = nil)
194   # PXP agent relies on 'impls' (which is the task file) being first if there is no metadata
195   find_implementations(name, directory, metadata, executables) + find_extra_files(metadata, envname)
196 end
is_task_name?(name) click to toggle source
   # File lib/puppet/module/task.rb
51 def self.is_task_name?(name)
52   return true if name =~ /^[a-z][a-z0-9_]*$/
53   return false
54 end
is_tasks_executable_filename?(name) click to toggle source
    # File lib/puppet/module/task.rb
202 def self.is_tasks_executable_filename?(name)
203   is_tasks_filename?(name) && !name.end_with?('.json')
204 end
is_tasks_file?(path) click to toggle source
   # File lib/puppet/module/task.rb
56 def self.is_tasks_file?(path)
57   File.file?(path) && is_tasks_filename?(path)
58 end
is_tasks_filename?(path) click to toggle source

Determine whether a file has a legal name for either a task's executable or metadata file.

   # File lib/puppet/module/task.rb
61 def self.is_tasks_filename?(path)
62   name_less_extension = File.basename(path, '.*')
63   return false if not is_task_name?(name_less_extension)
64   FORBIDDEN_EXTENSIONS.each do |ext|
65     return false if path.end_with?(ext)
66   end
67   return true
68 end
is_tasks_metadata_filename?(name) click to toggle source
    # File lib/puppet/module/task.rb
198 def self.is_tasks_metadata_filename?(name)
199   is_tasks_filename?(name) && name.end_with?('.json')
200 end
new(pup_module, task_name, module_executables, metadata_file = nil) click to toggle source

file paths must be relative to the modules task directory

    # File lib/puppet/module/task.rb
222 def initialize(pup_module, task_name,  module_executables, metadata_file = nil)
223   if !Puppet::Module::Task.is_task_name?(task_name)
224     raise InvalidName, _("Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores")
225   end
226 
227   name = task_name == "init" ? pup_module.name : "#{pup_module.name}::#{task_name}"
228 
229   @module = pup_module
230   @name = name
231   @metadata_file = metadata_file
232   @module_executables = module_executables || []
233 end
read_metadata(file) click to toggle source
    # File lib/puppet/module/task.rb
235 def self.read_metadata(file)
236   if file
237     content = Puppet::FileSystem.read(file, :encoding => 'utf-8')
238     content.empty? ? {} : Puppet::Util::Json.load(content)
239   end
240 rescue SystemCallError, IOError => err
241   msg = _("Error reading metadata: %{message}" % {message: err.message})
242   raise InvalidMetadata.new(msg, 'puppet.tasks/unreadable-metadata')
243 rescue Puppet::Util::Json::ParseError => err
244   raise InvalidMetadata.new(err.message, 'puppet.tasks/unparseable-metadata')
245 end
tasks_in_module(pup_module) click to toggle source
    # File lib/puppet/module/task.rb
206 def self.tasks_in_module(pup_module)
207   task_files = Dir.glob(File.join(pup_module.tasks_directory, '*'))
208     .keep_if { |f| is_tasks_file?(f) }
209 
210   module_executables = task_files.reject(&method(:is_tasks_metadata_filename?)).map.to_a
211 
212   tasks = task_files.group_by { |f| task_name_from_path(f) }
213 
214   tasks.map do |task, executables|
215     new_with_files(pup_module, task, executables, module_executables)
216   end
217 end

Private Class Methods

find_extra_files(metadata, envname = nil) click to toggle source

Find task's required lib files and retrieve paths for both 'files' and 'implementation:files' metadata keys

    # File lib/puppet/module/task.rb
 82 def self.find_extra_files(metadata, envname = nil)
 83   return [] if metadata.nil?
 84   
 85   files = metadata.fetch('files', [])
 86   unless files.is_a?(Array)
 87     msg = _("The 'files' task metadata expects an array, got %{files}.") % {files: files}
 88     raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
 89   end
 90   impl_files = metadata.fetch('implementations', []).flat_map do |impl| 
 91     file_array = impl.fetch('files', [])
 92     unless file_array.is_a?(Array)
 93       msg = _("The 'files' task metadata expects an array, got %{files}.") % {files: file_array}
 94       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
 95     end
 96     file_array
 97   end
 98 
 99   combined_files = files + impl_files
100   combined_files.uniq.flat_map do |file|
101     module_name, mount, endpath = file.split("/", 3)
102     # If there's a mount directory with no trailing slash this will be nil
103     # We want it to be empty to construct a path
104     endpath ||= ''
105 
106     pup_module = Puppet::Module.find(module_name, envname)
107     if pup_module.nil?
108       msg = _("Could not find module %{module_name} containing task file %{filename}" %
109               {module_name: module_name, filename: endpath})
110       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
111     end
112 
113     unless MOUNTS.include? mount
114       msg = _("Files must be saved in module directories that Puppet makes available via mount points: %{mounts}" %
115               {mounts: MOUNTS.join(', ')})
116       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
117     end
118 
119     path = File.join(pup_module.path, mount, endpath)
120     unless File.absolute_path(path) == File.path(path).chomp('/')
121       msg = _("File pathnames cannot include relative paths")
122       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
123     end
124 
125     unless File.exist?(path)
126       msg = _("Could not find %{path} on disk" % { path: path })
127       raise InvalidFile.new(msg)
128     end
129 
130     last_char = file[-1] == '/'
131     if File.directory?(path)
132       unless last_char
133         msg = _("Directories specified in task metadata must include a trailing slash: %{dir}" % { dir: file } )
134         raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
135       end
136       dir_files = Dir.glob("#{path}**/*").select { |f| File.file?(f) }
137       dir_files.map { |f| get_file_details(f, pup_module) }
138     else
139       if last_char
140         msg = _("Files specified in task metadata cannot include a trailing slash: %{file}" % { file: file } )
141         raise InvalidMetadata.new(msg, 'puppet.task/invalid-metadata')
142       end
143       get_file_details(path, pup_module)
144     end
145   end
146 end
find_implementations(name, directory, metadata, executables) click to toggle source

Executables list should contain the full path of all possible implementation files

    # File lib/puppet/module/task.rb
150 def self.find_implementations(name, directory, metadata, executables)
151   basename = name.split('::')[1] || 'init'
152   # If 'implementations' is defined, it needs to mention at least one
153   # implementation, and everything it mentions must exist.
154   metadata ||= {}
155   if metadata.key?('implementations')
156     unless metadata['implementations'].is_a?(Array)
157       msg = _("Task metadata for task %{name} does not specify implementations as an array" % { name: name })
158       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
159     end
160 
161     implementations = metadata['implementations'].map do |impl|
162       unless impl['requirements'].is_a?(Array) || impl['requirements'].nil?
163         msg = _("Task metadata for task %{name} does not specify requirements as an array" % { name: name })
164         raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
165       end
166       path = executables.find { |real_impl| File.basename(real_impl) == impl['name'] }
167       unless path
168         msg = _("Task metadata for task %{name} specifies missing implementation %{implementation}" % { name: name, implementation: impl['name'] })
169         raise InvalidTask.new(msg, 'puppet.tasks/missing-implementation', { missing: [impl['name']] } )
170       end
171       { "name" => impl['name'], "path" => path }
172     end
173     return implementations
174   end
175 
176   # If implementations isn't defined, then we use executables matching the
177   # task name, and only one may exist.
178   implementations = executables.select { |impl| File.basename(impl, '.*') == basename }
179   if implementations.empty?
180     msg = _('No source besides task metadata was found in directory %{directory} for task %{name}') %
181       { name: name, directory: directory }
182     raise InvalidTask.new(msg, 'puppet.tasks/no-implementation')
183   elsif implementations.length > 1
184     msg =_("Multiple executables were found in directory %{directory} for task %{name}; define 'implementations' in metadata to differentiate between them") %
185       { name: name, directory: implementations[0] }
186     raise InvalidTask.new(msg, 'puppet.tasks/multiple-implementations')
187   end
188 
189   [{ "name" => File.basename(implementations.first), "path" => implementations.first }]
190 end
get_file_details(path, mod) click to toggle source
   # File lib/puppet/module/task.rb
70 def self.get_file_details(path, mod)
71   # This gets the path from the starting point onward
72   # For files this should be the file subpath from the metadata
73   # For directories it should be the directory subpath plus whatever we globbed
74   # Partition matches on the first instance it finds of the parameter
75   name = "#{mod.name}#{path.partition(mod.path).last}"
76 
77   { "name" => name, "path" =>  path }
78 end
new_with_files(pup_module, name, task_files, module_executables) click to toggle source
    # File lib/puppet/module/task.rb
270 def self.new_with_files(pup_module, name, task_files, module_executables)
271   metadata_file = task_files.find { |f| is_tasks_metadata_filename?(f) }
272   Puppet::Module::Task.new(pup_module, name, module_executables, metadata_file)
273 end
task_name_from_path(path) click to toggle source

Abstracted here so we can add support for subdirectories later

    # File lib/puppet/module/task.rb
277 def self.task_name_from_path(path)
278   return File.basename(path, '.*')
279 end

Public Instance Methods

==(other) click to toggle source
    # File lib/puppet/module/task.rb
260 def ==(other)
261   self.name == other.name &&
262   self.module == other.module
263 end
files() click to toggle source
    # File lib/puppet/module/task.rb
251 def files
252   @files ||= self.class.find_files(@name, @module.tasks_directory, metadata, @module_executables, environment_name)
253 end
metadata() click to toggle source
    # File lib/puppet/module/task.rb
247 def metadata
248   @metadata ||= self.class.read_metadata(@metadata_file)
249 end
validate() click to toggle source
    # File lib/puppet/module/task.rb
255 def validate
256   files
257   true
258 end

Private Instance Methods

environment_name() click to toggle source
    # File lib/puppet/module/task.rb
265 def environment_name
266   @module.environment.respond_to?(:name) ? @module.environment.name : 'production'
267 end