Class: Puppet::Module::Task

Inherits:
Object show all
Defined in:
lib/puppet/module/task.rb

Defined Under Namespace

Classes: Error, InvalidFile, InvalidMetadata, InvalidName, InvalidTask, TaskNotFound

Constant Summary collapse

FORBIDDEN_EXTENSIONS =
%w{.conf .md}
MOUNTS =
%w[lib files tasks]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pup_module, task_name, module_executables, metadata_file = nil) ⇒ Task

file paths must be relative to the modules task directory



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/puppet/module/task.rb', line 196

def initialize(pup_module, task_name,  module_executables,  = nil)
  if !Puppet::Module::Task.is_task_name?(task_name)
    raise InvalidName, _("Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores")
  end

  name = task_name == "init" ? pup_module.name : "#{pup_module.name}::#{task_name}"

  @module = pup_module
  @name = name
   = 
  @module_executables = module_executables || []
end

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



193
194
195
# File 'lib/puppet/module/task.rb', line 193

def 
  
end

#metadata_fileObject (readonly)

Returns the value of attribute metadata_file.



193
194
195
# File 'lib/puppet/module/task.rb', line 193

def 
  
end

#moduleObject (readonly)

Returns the value of attribute module.



193
194
195
# File 'lib/puppet/module/task.rb', line 193

def module
  @module
end

#nameObject (readonly)

Returns the value of attribute name.



193
194
195
# File 'lib/puppet/module/task.rb', line 193

def name
  @name
end

Class Method Details

.find_files(files, mod) ⇒ Object

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



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/puppet/module/task.rb', line 77

def self.find_files(files, mod)
  env = mod.environment.respond_to?(:name) ? mod.environment.name : 'production'

  file_list = files.flat_map do |file|
    module_name, mount, endpath = file.split("/", 3)
    # If there's a mount directory with no trailing slash this will be nil
    # We want it to be empty to construct a path
    endpath ||= ''

    pup_module = Puppet::Module.find(module_name, env)
    if pup_module.nil?
      msg = _("Could not find module %{module_name} containing task file %{filename}" %
              {module_name: module_name, filename: endpath})
      raise .new(msg, 'puppet.tasks/invalid-metadata')
    end

    unless MOUNTS.include? mount
      msg = _("Files must be saved in module directories that Puppet makes available via mount points: %{mounts}" %
              {mounts: MOUNTS.join(', ')})
      raise .new(msg, 'puppet.tasks/invalid-metadata')
    end

    path = File.join(pup_module.path, mount, endpath)
    unless File.absolute_path(path) == File.path(path).chomp('/')
      msg = _("File pathnames cannot include relative paths")
      raise .new(msg, 'puppet.tasks/invalid-metadata')
    end

    unless File.exist?(path)
      msg = _("Could not find %{path} on disk" % { path: path })
      raise InvalidFile.new(msg)
    end

    last_char = file[-1] == '/'
    if File.directory?(path)
      unless last_char
        msg = _("Directories specified in task metadata must include a trailing slash: %{dir}" % { dir: file } )
        raise .new(msg, 'puppet.tasks/invalid-metadata')
      end
      dir_files = Dir.glob("#{path}**/*").select { |f| File.file?(f) }
      files = dir_files.map { |f| get_file_details(f, pup_module) }
    else
      if last_char
        msg = _("Files specified in task metadata cannot include a trailing slash: %{file}" % { file: file } )
        raise .new(msg, 'puppet.task/invalid-metadata')
      end
      files = get_file_details(path, pup_module)
    end

    files
  end
  return file_list
end

.find_implementations(name, directory, metadata, executables) ⇒ Object

Copied from TaskInstantiator so we can use the Error classes here TODO: harmonize on one implementation Executables list should contain the full path of all possible implementation files



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/puppet/module/task.rb', line 134

def self.find_implementations(name, directory, , executables)
  basename = name.split('::')[1] || 'init'
  # If 'implementations' is defined, it needs to mention at least one
  # implementation, and everything it mentions must exist.
   ||= {}
  if .key?('implementations')
    unless ['implementations'].is_a?(Array)
      msg = _("Task metadata for task %{name} does not specify implementations as an array" % { name: name })
      raise .new(msg, 'puppet.tasks/invalid-metadata')
    end

    implementations = ['implementations'].map do |impl|
      path = executables.find { |real_impl| File.basename(real_impl) == impl['name'] }
      unless path
        msg = _("Task metadata for task %{name} specifies missing implementation %{implementation}" % { name: name, implementation: impl['name'] })
        raise InvalidTask.new(msg, 'puppet.tasks/missing-implementation', { missing: [impl['name']] } )
      end
      { "name" => impl['name'], "requirements" => impl.fetch('requirements', []), "path" => path }
    end
    return implementations
  end

  # If implementations isn't defined, then we use executables matching the
  # task name, and only one may exist.
  implementations = executables.select { |impl| File.basename(impl, '.*') == basename }
  if implementations.empty?
    msg = _('No source besides task metadata was found in directory %{directory} for task %{name}') %
      { name: name, directory: directory }
    raise InvalidTask.new(msg, 'puppet.tasks/no-implementation')
  elsif implementations.length > 1
    msg =_("Multiple executables were found in directory %{directory} for task %{name}; define 'implementations' in metadata to differentiate between them") %
      { name: name, directory: implementations[0] }
    raise InvalidTask.new(msg, 'puppet.tasks/multiple-implementations')
  end

  [{ "name" => File.basename(implementations.first), "path" => implementations.first, "requirements" => [] }]
end

.get_file_details(path, mod) ⇒ Object



65
66
67
68
69
70
71
72
73
# File 'lib/puppet/module/task.rb', line 65

def self.get_file_details(path, mod)
  # This gets the path from the starting point onward
  # For files this should be the file subpath from the metadata
  # For directories it should be the directory subpath plus whatever we globbed
  # Partition matches on the first instance it finds of the parameter
  name = "#{mod.name}#{path.partition(mod.path).last}"

  { "name" => name, "path" =>  path }
end

.is_task_name?(name) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
53
# File 'lib/puppet/module/task.rb', line 50

def self.is_task_name?(name)
  return true if name =~ /^[a-z][a-z0-9_]*$/
  return false
end

.is_tasks_executable_filename?(name) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
# File 'lib/puppet/module/task.rb', line 176

def self.is_tasks_executable_filename?(name)
  is_tasks_filename?(name) && !name.end_with?('.json')
end

.is_tasks_filename?(path) ⇒ Boolean

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

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
# File 'lib/puppet/module/task.rb', line 56

def self.is_tasks_filename?(path)
  name_less_extension = File.basename(path, '.*')
  return false if not is_task_name?(name_less_extension)
  FORBIDDEN_EXTENSIONS.each do |ext|
    return false if path.end_with?(ext)
  end
  return true
end

.is_tasks_metadata_filename?(name) ⇒ Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/puppet/module/task.rb', line 172

def self.(name)
  is_tasks_filename?(name) && name.end_with?('.json')
end

.tasks_in_module(pup_module) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/puppet/module/task.rb', line 180

def self.tasks_in_module(pup_module)
  task_files = Dir.glob(File.join(pup_module.tasks_directory, '*'))
    .keep_if { |f| is_tasks_filename?(f) }

  module_executables = task_files.reject(&method(:is_tasks_metadata_filename?)).map.to_a

  tasks = task_files.group_by { |f| task_name_from_path(f) }

  tasks.map do |task, executables|
    new_with_files(pup_module, task, executables, module_executables)
  end
end

Instance Method Details

#==(other) ⇒ Object



250
251
252
253
# File 'lib/puppet/module/task.rb', line 250

def ==(other)
  self.name == other.name &&
  self.module == other.module
end

#filesObject



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/puppet/module/task.rb', line 226

def files
  md = 
  outer_files = []
  impl_lib_files = []
  lib_files = []

  unless md.nil?
    outer_files = md['files'] if md.key?('files')
    # There's definitely a more elegant way to do this...
    if md.key?('implementations')
      md['implementations'].each { |impl| impl_lib_files << impl['files'] if impl.key?('files') }
    end
    lib_files = self.class.find_files((impl_lib_files.flatten.uniq + outer_files).uniq, @module)
  end
  task_file = implementations.map {|imp| { 'name' => imp['name'], 'path' => imp['path'] } }
  # PXP agent relies on 'impls' (which is the task file) being first if there is no metadata
  task_file + lib_files
end

#implementationsObject



222
223
224
# File 'lib/puppet/module/task.rb', line 222

def implementations
  @implementations ||= self.class.find_implementations(@name, @module.tasks_directory, , @module_executables)
end

#read_metadata(file) ⇒ Object



209
210
211
212
213
214
215
216
# File 'lib/puppet/module/task.rb', line 209

def (file)
  Puppet::Util::Json.load(Puppet::FileSystem.read(file, :encoding => 'utf-8')) if file
rescue SystemCallError, IOError => err
  msg = _("Error reading metadata: %{message}" % {message: err.message})
  raise .new(msg, 'puppet.tasks/unreadable-metadata')
rescue Puppet::Util::Json::ParseError => err
  raise .new(err.message, 'puppet.tasks/unparseable-metadata')
end

#validateObject



245
246
247
248
# File 'lib/puppet/module/task.rb', line 245

def validate
  implementations
  true
end