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