class Puppet::Module::Task

Constants

FORBIDDEN_EXTENSIONS
MOUNTS

Attributes

metadata[R]
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
188 def self.find_files(name, directory, metadata, executables, envname = nil)
189   # PXP agent relies on 'impls' (which is the task file) being first if there is no metadata
190   find_implementations(name, directory, metadata, executables) + find_extra_files(metadata, envname)
191 end
is_task_name?(name) click to toggle source
   # File lib/puppet/module/task.rb
50 def self.is_task_name?(name)
51   return true if name =~ /^[a-z][a-z0-9_]*$/
52   return false
53 end
is_tasks_executable_filename?(name) click to toggle source
    # File lib/puppet/module/task.rb
197 def self.is_tasks_executable_filename?(name)
198   is_tasks_filename?(name) && !name.end_with?('.json')
199 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
56 def self.is_tasks_filename?(path)
57   name_less_extension = File.basename(path, '.*')
58   return false if not is_task_name?(name_less_extension)
59   FORBIDDEN_EXTENSIONS.each do |ext|
60     return false if path.end_with?(ext)
61   end
62   return true
63 end
is_tasks_metadata_filename?(name) click to toggle source
    # File lib/puppet/module/task.rb
193 def self.is_tasks_metadata_filename?(name)
194   is_tasks_filename?(name) && name.end_with?('.json')
195 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
217 def initialize(pup_module, task_name,  module_executables, metadata_file = nil)
218   if !Puppet::Module::Task.is_task_name?(task_name)
219     raise InvalidName, _("Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores")
220   end
221 
222   name = task_name == "init" ? pup_module.name : "#{pup_module.name}::#{task_name}"
223 
224   @module = pup_module
225   @name = name
226   @metadata_file = metadata_file
227   @module_executables = module_executables || []
228 end
read_metadata(file) click to toggle source
    # File lib/puppet/module/task.rb
230 def self.read_metadata(file)
231   Puppet::Util::Json.load(Puppet::FileSystem.read(file, :encoding => 'utf-8')) if file
232 rescue SystemCallError, IOError => err
233   msg = _("Error reading metadata: %{message}" % {message: err.message})
234   raise InvalidMetadata.new(msg, 'puppet.tasks/unreadable-metadata')
235 rescue Puppet::Util::Json::ParseError => err
236   raise InvalidMetadata.new(err.message, 'puppet.tasks/unparseable-metadata')
237 end
tasks_in_module(pup_module) click to toggle source
    # File lib/puppet/module/task.rb
201 def self.tasks_in_module(pup_module)
202   task_files = Dir.glob(File.join(pup_module.tasks_directory, '*'))
203     .keep_if { |f| is_tasks_filename?(f) }
204 
205   module_executables = task_files.reject(&method(:is_tasks_metadata_filename?)).map.to_a
206 
207   tasks = task_files.group_by { |f| task_name_from_path(f) }
208 
209   tasks.map do |task, executables|
210     new_with_files(pup_module, task, executables, module_executables)
211   end
212 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
 77 def self.find_extra_files(metadata, envname = nil)
 78   return [] if metadata.nil?
 79   
 80   files = metadata.fetch('files', [])
 81   unless files.is_a?(Array)
 82     msg = _("The 'files' task metadata expects an array, got %{files}.") % {files: files}
 83     raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
 84   end
 85   impl_files = metadata.fetch('implementations', []).flat_map do |impl| 
 86     file_array = impl.fetch('files', [])
 87     unless file_array.is_a?(Array)
 88       msg = _("The 'files' task metadata expects an array, got %{files}.") % {files: file_array}
 89       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
 90     end
 91     file_array
 92   end
 93 
 94   combined_files = files + impl_files
 95   combined_files.uniq.flat_map do |file|
 96     module_name, mount, endpath = file.split("/", 3)
 97     # If there's a mount directory with no trailing slash this will be nil
 98     # We want it to be empty to construct a path
 99     endpath ||= ''
100 
101     pup_module = Puppet::Module.find(module_name, envname)
102     if pup_module.nil?
103       msg = _("Could not find module %{module_name} containing task file %{filename}" %
104               {module_name: module_name, filename: endpath})
105       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
106     end
107 
108     unless MOUNTS.include? mount
109       msg = _("Files must be saved in module directories that Puppet makes available via mount points: %{mounts}" %
110               {mounts: MOUNTS.join(', ')})
111       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
112     end
113 
114     path = File.join(pup_module.path, mount, endpath)
115     unless File.absolute_path(path) == File.path(path).chomp('/')
116       msg = _("File pathnames cannot include relative paths")
117       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
118     end
119 
120     unless File.exist?(path)
121       msg = _("Could not find %{path} on disk" % { path: path })
122       raise InvalidFile.new(msg)
123     end
124 
125     last_char = file[-1] == '/'
126     if File.directory?(path)
127       unless last_char
128         msg = _("Directories specified in task metadata must include a trailing slash: %{dir}" % { dir: file } )
129         raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
130       end
131       dir_files = Dir.glob("#{path}**/*").select { |f| File.file?(f) }
132       dir_files.map { |f| get_file_details(f, pup_module) }
133     else
134       if last_char
135         msg = _("Files specified in task metadata cannot include a trailing slash: %{file}" % { file: file } )
136         raise InvalidMetadata.new(msg, 'puppet.task/invalid-metadata')
137       end
138       get_file_details(path, pup_module)
139     end
140   end
141 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
145 def self.find_implementations(name, directory, metadata, executables)
146   basename = name.split('::')[1] || 'init'
147   # If 'implementations' is defined, it needs to mention at least one
148   # implementation, and everything it mentions must exist.
149   metadata ||= {}
150   if metadata.key?('implementations')
151     unless metadata['implementations'].is_a?(Array)
152       msg = _("Task metadata for task %{name} does not specify implementations as an array" % { name: name })
153       raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
154     end
155 
156     implementations = metadata['implementations'].map do |impl|
157       unless impl['requirements'].is_a?(Array) || impl['requirements'].nil?
158         msg = _("Task metadata for task %{name} does not specify requirements as an array" % { name: name })
159         raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')
160       end
161       path = executables.find { |real_impl| File.basename(real_impl) == impl['name'] }
162       unless path
163         msg = _("Task metadata for task %{name} specifies missing implementation %{implementation}" % { name: name, implementation: impl['name'] })
164         raise InvalidTask.new(msg, 'puppet.tasks/missing-implementation', { missing: [impl['name']] } )
165       end
166       { "name" => impl['name'], "path" => path }
167     end
168     return implementations
169   end
170 
171   # If implementations isn't defined, then we use executables matching the
172   # task name, and only one may exist.
173   implementations = executables.select { |impl| File.basename(impl, '.*') == basename }
174   if implementations.empty?
175     msg = _('No source besides task metadata was found in directory %{directory} for task %{name}') %
176       { name: name, directory: directory }
177     raise InvalidTask.new(msg, 'puppet.tasks/no-implementation')
178   elsif implementations.length > 1
179     msg =_("Multiple executables were found in directory %{directory} for task %{name}; define 'implementations' in metadata to differentiate between them") %
180       { name: name, directory: implementations[0] }
181     raise InvalidTask.new(msg, 'puppet.tasks/multiple-implementations')
182   end
183 
184   [{ "name" => File.basename(implementations.first), "path" => implementations.first }]
185 end
get_file_details(path, mod) click to toggle source
   # File lib/puppet/module/task.rb
65 def self.get_file_details(path, mod)
66   # This gets the path from the starting point onward
67   # For files this should be the file subpath from the metadata
68   # For directories it should be the directory subpath plus whatever we globbed
69   # Partition matches on the first instance it finds of the parameter
70   name = "#{mod.name}#{path.partition(mod.path).last}"
71 
72   { "name" => name, "path" =>  path }
73 end
new_with_files(pup_module, name, task_files, module_executables) click to toggle source
    # File lib/puppet/module/task.rb
262 def self.new_with_files(pup_module, name, task_files, module_executables)
263   metadata_file = task_files.find { |f| is_tasks_metadata_filename?(f) }
264   Puppet::Module::Task.new(pup_module, name, module_executables, metadata_file)
265 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
269 def self.task_name_from_path(path)
270   return File.basename(path, '.*')
271 end

Public Instance Methods

==(other) click to toggle source
    # File lib/puppet/module/task.rb
252 def ==(other)
253   self.name == other.name &&
254   self.module == other.module
255 end
files() click to toggle source
    # File lib/puppet/module/task.rb
243 def files
244   @files ||= self.class.find_files(@name, @module.tasks_directory, metadata, @module_executables, environment_name)
245 end
validate() click to toggle source
    # File lib/puppet/module/task.rb
247 def validate
248   files
249   true
250 end

Private Instance Methods

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