bolt 2.23.0 → 2.27.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  5. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
  6. data/exe/bolt +1 -0
  7. data/guides/inventory.txt +19 -0
  8. data/guides/project.txt +22 -0
  9. data/lib/bolt/analytics.rb +11 -7
  10. data/lib/bolt/applicator.rb +11 -10
  11. data/lib/bolt/bolt_option_parser.rb +75 -13
  12. data/lib/bolt/catalog.rb +4 -2
  13. data/lib/bolt/cli.rb +156 -176
  14. data/lib/bolt/config.rb +55 -25
  15. data/lib/bolt/config/options.rb +28 -6
  16. data/lib/bolt/executor.rb +5 -3
  17. data/lib/bolt/inventory.rb +8 -1
  18. data/lib/bolt/inventory/group.rb +4 -4
  19. data/lib/bolt/inventory/inventory.rb +1 -1
  20. data/lib/bolt/inventory/target.rb +1 -1
  21. data/lib/bolt/logger.rb +12 -6
  22. data/lib/bolt/outputter/human.rb +10 -0
  23. data/lib/bolt/outputter/json.rb +11 -0
  24. data/lib/bolt/outputter/logger.rb +3 -3
  25. data/lib/bolt/outputter/rainbow.rb +15 -0
  26. data/lib/bolt/pal.rb +23 -12
  27. data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
  28. data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
  29. data/lib/bolt/plugin/puppetdb.rb +1 -1
  30. data/lib/bolt/project.rb +63 -17
  31. data/lib/bolt/project_migrate.rb +138 -0
  32. data/lib/bolt/puppetdb/client.rb +1 -1
  33. data/lib/bolt/puppetdb/config.rb +1 -1
  34. data/lib/bolt/puppetfile.rb +160 -0
  35. data/lib/bolt/puppetfile/installer.rb +43 -0
  36. data/lib/bolt/puppetfile/module.rb +66 -0
  37. data/lib/bolt/r10k_log_proxy.rb +1 -1
  38. data/lib/bolt/rerun.rb +2 -2
  39. data/lib/bolt/result.rb +23 -0
  40. data/lib/bolt/shell.rb +1 -1
  41. data/lib/bolt/shell/bash.rb +7 -7
  42. data/lib/bolt/task.rb +1 -1
  43. data/lib/bolt/transport/base.rb +1 -1
  44. data/lib/bolt/transport/docker/connection.rb +10 -10
  45. data/lib/bolt/transport/local/connection.rb +3 -3
  46. data/lib/bolt/transport/orch.rb +3 -3
  47. data/lib/bolt/transport/ssh.rb +1 -1
  48. data/lib/bolt/transport/ssh/connection.rb +6 -6
  49. data/lib/bolt/transport/ssh/exec_connection.rb +5 -5
  50. data/lib/bolt/transport/winrm.rb +1 -1
  51. data/lib/bolt/transport/winrm/connection.rb +9 -9
  52. data/lib/bolt/util.rb +2 -2
  53. data/lib/bolt/util/puppet_log_level.rb +4 -3
  54. data/lib/bolt/version.rb +1 -1
  55. data/lib/bolt_server/base_config.rb +2 -2
  56. data/lib/bolt_server/config.rb +1 -1
  57. data/lib/bolt_server/file_cache.rb +1 -1
  58. data/lib/bolt_server/transport_app.rb +189 -14
  59. data/lib/bolt_spec/plans.rb +1 -1
  60. data/lib/bolt_spec/run.rb +3 -0
  61. metadata +12 -12
@@ -16,7 +16,7 @@ module Bolt
16
16
  @target = target
17
17
  # The familiar problem: Etc.getlogin is broken on osx
18
18
  @user = ENV['USER'] || Etc.getlogin
19
- @logger = Logging.logger[self]
19
+ @logger = Bolt::Logger.logger(self)
20
20
  end
21
21
 
22
22
  def shell
@@ -28,7 +28,7 @@ module Bolt
28
28
  end
29
29
 
30
30
  def upload_file(source, dest)
31
- @logger.debug { "Uploading #{source}, to #{dest}" }
31
+ @logger.trace { "Uploading #{source} to #{dest}" }
32
32
  if source.is_a?(StringIO)
33
33
  Tempfile.create(File.basename(dest)) do |f|
34
34
  f.write(source.read)
@@ -46,7 +46,7 @@ module Bolt
46
46
  end
47
47
 
48
48
  def download_file(source, dest, _download)
49
- @logger.debug { "Downloading #{source} to #{dest}" }
49
+ @logger.trace { "Downloading #{source} to #{dest}" }
50
50
  # Create the destination directory for the target, or the
51
51
  # copied file will have the target's name
52
52
  FileUtils.mkdir_p(dest)
@@ -38,7 +38,7 @@ module Bolt
38
38
  @connections.each_value do |conn|
39
39
  conn.finish_plan(result)
40
40
  rescue StandardError => e
41
- @logger.debug("Failed to finish plan on #{conn.key}: #{e.message}")
41
+ @logger.trace("Failed to finish plan on #{conn.key}: #{e.message}")
42
42
  end
43
43
  end
44
44
  end
@@ -133,7 +133,7 @@ module Bolt
133
133
  next unless File.file?(file)
134
134
 
135
135
  tar_path = Pathname.new(file).relative_path_from(Pathname.new(directory))
136
- @logger.debug("Packing #{file} to #{tar_path}")
136
+ @logger.trace("Packing #{file} to #{tar_path}")
137
137
  stat = File.stat(file)
138
138
  content = File.binread(file)
139
139
  output.tar.add_file_simple(
@@ -146,7 +146,7 @@ module Bolt
146
146
  end
147
147
 
148
148
  duration = Time.now - start_time
149
- @logger.debug("Packed upload in #{duration * 1000} ms")
149
+ @logger.trace("Packed upload in #{duration * 1000} ms")
150
150
 
151
151
  output.close
152
152
  io.string
@@ -17,7 +17,7 @@ module Bolt
17
17
  rescue LoadError
18
18
  logger.debug("Authentication method 'gssapi-with-mic' (Kerberos) is not available.")
19
19
  end
20
- @transport_logger = Logging.logger[Net::SSH]
20
+ @transport_logger = Bolt::Logger.logger(Net::SSH)
21
21
  @transport_logger.level = :warn
22
22
  end
23
23
 
@@ -26,9 +26,9 @@ module Bolt
26
26
  @user = @target.user || ssh_config[:user] || Etc.getlogin
27
27
  @strict_host_key_checking = ssh_config[:strict_host_key_checking]
28
28
 
29
- @logger = Logging.logger[@target.safe_name]
29
+ @logger = Bolt::Logger.logger(@target.safe_name)
30
30
  @transport_logger = transport_logger
31
- @logger.debug("Initializing ssh connection to #{@target.safe_name}")
31
+ @logger.trace("Initializing ssh connection to #{@target.safe_name}")
32
32
 
33
33
  if target.options['private-key']&.instance_of?(String)
34
34
  begin
@@ -131,7 +131,7 @@ module Bolt
131
131
 
132
132
  @session = Net::SSH.start(target.host, @user, options)
133
133
  validate_ssh_version
134
- @logger.debug { "Opened session" }
134
+ @logger.trace { "Opened session" }
135
135
  rescue Net::SSH::AuthenticationFailed => e
136
136
  raise Bolt::Node::ConnectError.new(
137
137
  e.message,
@@ -161,7 +161,7 @@ module Bolt
161
161
  rescue Timeout::Error
162
162
  @session.shutdown!
163
163
  end
164
- @logger.debug { "Closed session" }
164
+ @logger.trace { "Closed session" }
165
165
  end
166
166
  end
167
167
 
@@ -237,7 +237,7 @@ module Bolt
237
237
 
238
238
  def upload_file(source, destination)
239
239
  # Do not log wrapper script content
240
- @logger.debug { "Uploading #{source}, to #{destination}" } unless source.is_a?(StringIO)
240
+ @logger.trace { "Uploading #{source} to #{destination}" } unless source.is_a?(StringIO)
241
241
  @session.scp.upload!(source, destination, recursive: true)
242
242
  rescue StandardError => e
243
243
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
@@ -245,7 +245,7 @@ module Bolt
245
245
 
246
246
  def download_file(source, destination, _download)
247
247
  # Do not log wrapper script content
248
- @logger.debug { "Downloading #{source} to #{destination}" }
248
+ @logger.trace { "Downloading #{source} to #{destination}" }
249
249
  @session.scp.download!(source, destination, recursive: true)
250
250
  rescue StandardError => e
251
251
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
@@ -14,7 +14,7 @@ module Bolt
14
14
  @target = target
15
15
  ssh_config = Net::SSH::Config.for(target.host)
16
16
  @user = @target.user || ssh_config[:user] || Etc.getlogin
17
- @logger = Logging.logger[self]
17
+ @logger = Bolt::Logger.logger(self)
18
18
  end
19
19
 
20
20
  # This is used to verify we can connect to targets with `connected?`
@@ -66,7 +66,7 @@ module Bolt
66
66
  end
67
67
 
68
68
  def upload_file(source, dest)
69
- @logger.debug { "Uploading #{source}, to #{userhost}:#{dest}" } unless source.is_a?(StringIO)
69
+ @logger.trace { "Uploading #{source} to #{dest}" } unless source.is_a?(StringIO)
70
70
 
71
71
  cp_conf = @target.transport_config['copy-command'] || ["scp", "-r"]
72
72
  cp_cmd = Array(cp_conf)
@@ -87,7 +87,7 @@ module Bolt
87
87
  end
88
88
 
89
89
  if stat.success?
90
- @logger.debug "Successfully uploaded #{source} to #{dest}"
90
+ @logger.trace "Successfully uploaded #{source} to #{dest}"
91
91
  else
92
92
  message = "Could not copy file to #{dest}: #{err}"
93
93
  raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
@@ -95,7 +95,7 @@ module Bolt
95
95
  end
96
96
 
97
97
  def download_file(source, dest, _download)
98
- @logger.debug { "Downloading #{userhost}:#{source} to #{dest}" }
98
+ @logger.trace { "Downloading #{userhost}:#{source} to #{dest}" }
99
99
 
100
100
  FileUtils.mkdir_p(dest)
101
101
 
@@ -108,7 +108,7 @@ module Bolt
108
108
  _, err, stat = Open3.capture3(*cp_cmd)
109
109
 
110
110
  if stat.success?
111
- @logger.debug "Successfully downloaded #{userhost}:#{source} to #{dest}"
111
+ @logger.trace "Successfully downloaded #{userhost}:#{source} to #{dest}"
112
112
  else
113
113
  message = "Could not copy file to #{dest}: #{err}"
114
114
  raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
@@ -11,7 +11,7 @@ module Bolt
11
11
  require 'winrm'
12
12
  require 'winrm-fs'
13
13
 
14
- @transport_logger = Logging.logger[::WinRM]
14
+ @transport_logger = Bolt::Logger.logger(::WinRM)
15
15
  @transport_logger.level = :warn
16
16
  end
17
17
 
@@ -18,8 +18,8 @@ module Bolt
18
18
  @user = @target.user
19
19
  # Build set of extensions from extensions config as well as interpreters
20
20
 
21
- @logger = Logging.logger[@target.safe_name]
22
- logger.debug("Initializing winrm connection to #{@target.safe_name}")
21
+ @logger = Bolt::Logger.logger(@target.safe_name)
22
+ logger.trace("Initializing winrm connection to #{@target.safe_name}")
23
23
  @transport_logger = transport_logger
24
24
  end
25
25
 
@@ -55,7 +55,7 @@ module Bolt
55
55
 
56
56
  @session = @connection.shell(:powershell)
57
57
  @session.run('$PSVersionTable.PSVersion')
58
- @logger.debug { "Opened session" }
58
+ @logger.trace { "Opened session" }
59
59
  end
60
60
  rescue Timeout::Error
61
61
  # If we're using the default port with SSL, a timeout probably means the
@@ -97,11 +97,11 @@ module Bolt
97
97
  def disconnect
98
98
  @session&.close
99
99
  @client&.disconnect!
100
- @logger.debug { "Closed session" }
100
+ @logger.trace { "Closed session" }
101
101
  end
102
102
 
103
103
  def execute(command)
104
- @logger.debug { "Executing command: #{command}" }
104
+ @logger.trace { "Executing command: #{command}" }
105
105
 
106
106
  inp = StringIO.new
107
107
  # This transport doesn't accept stdin, so close the stream to ensure
@@ -134,12 +134,12 @@ module Bolt
134
134
  "with 'ulimit -n 1024'. See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
135
135
  raise Bolt::Error.new(msg, 'bolt/too-many-files')
136
136
  rescue StandardError
137
- @logger.debug { "Command aborted" }
137
+ @logger.trace { "Command aborted" }
138
138
  raise
139
139
  end
140
140
 
141
141
  def upload_file(source, destination)
142
- @logger.debug { "Uploading #{source}, to #{destination}" }
142
+ @logger.trace { "Uploading #{source} to #{destination}" }
143
143
  if target.options['file-protocol'] == 'smb'
144
144
  upload_file_smb(source, destination)
145
145
  else
@@ -185,7 +185,7 @@ module Bolt
185
185
  end
186
186
 
187
187
  def download_file(source, destination, download)
188
- @logger.debug { "Downloading #{source} to #{destination}" }
188
+ @logger.trace { "Downloading #{source} to #{destination}" }
189
189
  if target.options['file-protocol'] == 'smb'
190
190
  download_file_smb(source, destination)
191
191
  else
@@ -257,7 +257,7 @@ module Bolt
257
257
  status = @client.login
258
258
  case status
259
259
  when WindowsError::NTStatus::STATUS_SUCCESS
260
- @logger.debug { "Connected to #{@client.dns_host_name}" }
260
+ @logger.trace { "Connected to #{@client.dns_host_name}" }
261
261
  when WindowsError::NTStatus::STATUS_LOGON_FAILURE
262
262
  raise Bolt::Node::ConnectError.new(
263
263
  "SMB authentication failed for #{target.safe_name}",
@@ -6,14 +6,14 @@ module Bolt
6
6
  def read_yaml_hash(path, file_name)
7
7
  require 'yaml'
8
8
 
9
- logger = Logging.logger[self]
9
+ logger = Bolt::Logger.logger(self)
10
10
  path = File.expand_path(path)
11
11
  content = File.open(path, "r:UTF-8") { |f| YAML.safe_load(f.read) } || {}
12
12
  unless content.is_a?(Hash)
13
13
  msg = "Invalid content for #{file_name} file: #{path} should be a Hash or empty, not #{content.class}"
14
14
  raise Bolt::FileError.new(msg, path)
15
15
  end
16
- logger.debug("Loaded #{file_name} from #{path}")
16
+ logger.trace("Loaded #{file_name} from #{path}")
17
17
  content
18
18
  rescue Errno::ENOENT
19
19
  raise Bolt::FileError.new("Could not read #{file_name} file: #{path}", path)
@@ -4,9 +4,10 @@ module Bolt
4
4
  module Util
5
5
  module PuppetLogLevel
6
6
  MAPPING = {
7
- debug: :debug,
8
- info: :info,
9
- notice: :notice,
7
+ # Demote Puppet's logs by one level, since Puppet is an implementation detail of Bolt
8
+ debug: :trace,
9
+ info: :debug,
10
+ notice: :info,
10
11
  warning: :warn,
11
12
  err: :error,
12
13
  # The following are used by Puppet functions of the same name, and are all treated as
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.23.0'
4
+ VERSION = '2.27.0'
5
5
  end
@@ -7,7 +7,7 @@ module BoltServer
7
7
  class BaseConfig
8
8
  def config_keys
9
9
  %w[host port ssl-cert ssl-key ssl-ca-cert
10
- ssl-cipher-suites loglevel logfile whitelist]
10
+ ssl-cipher-suites loglevel logfile whitelist projects-dir]
11
11
  end
12
12
 
13
13
  def env_keys
@@ -16,7 +16,7 @@ module BoltServer
16
16
 
17
17
  def defaults
18
18
  { 'host' => '127.0.0.1',
19
- 'loglevel' => 'notice',
19
+ 'loglevel' => 'warn',
20
20
  'ssl-cipher-suites' => %w[ECDHE-ECDSA-AES256-GCM-SHA384
21
21
  ECDHE-RSA-AES256-GCM-SHA384
22
22
  ECDHE-ECDSA-CHACHA20-POLY1305
@@ -7,7 +7,7 @@ require 'bolt/error'
7
7
  module BoltServer
8
8
  class Config < BoltServer::BaseConfig
9
9
  def config_keys
10
- super + %w[concurrency cache-dir file-server-conn-timeout file-server-uri]
10
+ super + %w[concurrency cache-dir file-server-conn-timeout file-server-uri projects-dir]
11
11
  end
12
12
 
13
13
  def env_keys
@@ -33,7 +33,7 @@ module BoltServer
33
33
  @executor = executor
34
34
  @cache_dir = config['cache-dir']
35
35
  @config = config
36
- @logger = Logging.logger[self]
36
+ @logger = Bolt::Logger.logger(self)
37
37
  @cache_dir_mutex = cache_dir_mutex
38
38
 
39
39
  if do_purge
@@ -5,6 +5,7 @@ require 'addressable/uri'
5
5
  require 'bolt'
6
6
  require 'bolt/error'
7
7
  require 'bolt/inventory'
8
+ require 'bolt/project'
8
9
  require 'bolt/target'
9
10
  require 'bolt_server/file_cache'
10
11
  require 'bolt/task/puppet_server'
@@ -191,20 +192,44 @@ module BoltServer
191
192
  end
192
193
 
193
194
  def in_pe_pal_env(environment)
194
- if environment.nil?
195
- [400, '`environment` is a required argument']
196
- else
197
- @pal_mutex.synchronize do
198
- pal = BoltServer::PE::PAL.new({}, environment)
199
- yield pal
200
- rescue Puppet::Environments::EnvironmentNotFound
201
- [400, {
202
- "class" => 'bolt/unknown-environment',
203
- "message" => "Environment #{environment} not found"
204
- }.to_json]
205
- rescue Bolt::Error => e
206
- [400, e.to_json]
207
- end
195
+ return [400, '`environment` is a required argument'] if environment.nil?
196
+ @pal_mutex.synchronize do
197
+ pal = BoltServer::PE::PAL.new({}, environment)
198
+ yield pal
199
+ rescue Puppet::Environments::EnvironmentNotFound
200
+ [400, {
201
+ "class" => 'bolt/unknown-environment',
202
+ "message" => "Environment #{environment} not found"
203
+ }.to_json]
204
+ rescue Bolt::Error => e
205
+ [400, e.to_json]
206
+ end
207
+ end
208
+
209
+ def in_bolt_project(bolt_project)
210
+ return [400, '`project_ref` is a required argument'] if bolt_project.nil?
211
+ project_dir = File.join(@config['projects-dir'], bolt_project)
212
+ return [400, "`project_ref`: #{project_dir} does not exist"] unless Dir.exist?(project_dir)
213
+ @pal_mutex.synchronize do
214
+ project = Bolt::Project.create_project(project_dir)
215
+ bolt_config = Bolt::Config.from_project(project, {})
216
+ pal = Bolt::PAL.new(bolt_config.modulepath, nil, nil, nil, nil, nil, bolt_config.project)
217
+ module_path = [
218
+ BoltServer::PE::PAL::PE_BOLTLIB_PATH,
219
+ Bolt::PAL::BOLTLIB_PATH,
220
+ *bolt_config.modulepath,
221
+ Bolt::PAL::MODULES_PATH
222
+ ]
223
+ # CODEREVIEW: I *think* this is the only thing we need to make different between bolt's PAL. Is it acceptable
224
+ # to hack this? Modulepath is currently a readable attribute, could we make it writeable?
225
+ pal.instance_variable_set(:@modulepath, module_path)
226
+ context = {
227
+ pal: pal,
228
+ config: bolt_config
229
+ }
230
+ yield context
231
+ rescue Bolt::Error => e
232
+ [400, e.to_json]
208
233
  end
209
234
  end
210
235
 
@@ -221,6 +246,67 @@ module BoltServer
221
246
  plan_info
222
247
  end
223
248
 
249
+ def build_puppetserver_uri(file_identifier, module_name, parameters)
250
+ segments = file_identifier.split('/', 3)
251
+ if segments.size == 1
252
+ {
253
+ 'path' => "/puppet/v3/file_content/tasks/#{module_name}/#{file_identifier}",
254
+ 'params' => parameters
255
+ }
256
+ else
257
+ module_segment, mount_segment, name_segment = *segments
258
+ {
259
+ 'path' => case mount_segment
260
+ when 'files'
261
+ "/puppet/v3/file_content/modules/#{module_segment}/#{name_segment}"
262
+ when 'tasks'
263
+ "/puppet/v3/file_content/tasks/#{module_segment}/#{name_segment}"
264
+ when 'lib'
265
+ "/puppet/v3/file_content/plugins/#{name_segment}"
266
+ end,
267
+ 'params' => parameters
268
+ }
269
+ end
270
+ end
271
+
272
+ def pe_task_info(pal, module_name, task_name, parameters)
273
+ # Handle case where task name is simply module name with special `init` task
274
+ task_name = if task_name == 'init' || task_name.nil?
275
+ module_name
276
+ else
277
+ "#{module_name}::#{task_name}"
278
+ end
279
+ task = pal.get_task(task_name)
280
+ files = task.files.map do |file_hash|
281
+ {
282
+ 'filename' => file_hash['name'],
283
+ 'sha256' => Digest::SHA256.hexdigest(File.read(file_hash['path'])),
284
+ 'size_bytes' => File.size(file_hash['path']),
285
+ 'uri' => build_puppetserver_uri(file_hash['name'], module_name, parameters)
286
+ }
287
+ end
288
+ {
289
+ 'metadata' => task.metadata,
290
+ 'name' => task.name,
291
+ 'files' => files
292
+ }
293
+ end
294
+
295
+ def allowed_helper(metadata, allowlist)
296
+ allowed = allowlist.nil? || allowlist.include?(metadata['name']) ? true : false
297
+ metadata.merge({ 'allowed' => allowed })
298
+ end
299
+
300
+ def task_list(pal)
301
+ tasks = pal.list_tasks
302
+ tasks.map { |task_name, _description| { 'name' => task_name } }
303
+ end
304
+
305
+ def plan_list(pal)
306
+ plans = pal.list_plans.flatten
307
+ plans.map { |plan_name| { 'name' => plan_name } }
308
+ end
309
+
224
310
  get '/' do
225
311
  200
226
312
  end
@@ -351,6 +437,44 @@ module BoltServer
351
437
  end
352
438
  end
353
439
 
440
+ # Fetches the metadata for a single plan
441
+ #
442
+ # @param project_ref [String] the project to fetch the plan from
443
+ get '/project_plans/:module_name/:plan_name' do
444
+ in_bolt_project(params['project_ref']) do |context|
445
+ plan_info = pe_plan_info(context[:pal], params[:module_name], params[:plan_name])
446
+ plan_info = allowed_helper(plan_info, context[:config].project.plans)
447
+ [200, plan_info.to_json]
448
+ end
449
+ end
450
+
451
+ # Fetches the metadata for a single task
452
+ #
453
+ # @param environment [String] the environment to fetch the task from
454
+ get '/tasks/:module_name/:task_name' do
455
+ in_pe_pal_env(params['environment']) do |pal|
456
+ ps_parameters = {
457
+ 'environment' => params['environment']
458
+ }
459
+ task_info = pe_task_info(pal, params[:module_name], params[:task_name], ps_parameters)
460
+ [200, task_info.to_json]
461
+ end
462
+ end
463
+
464
+ # Fetches the metadata for a single task
465
+ #
466
+ # @param bolt_project_ref [String] the reference to the bolt-project directory to load task metadata from
467
+ get '/project_tasks/:module_name/:task_name' do
468
+ in_bolt_project(params['project_ref']) do |context|
469
+ ps_parameters = {
470
+ 'project' => params['project_ref']
471
+ }
472
+ task_info = pe_task_info(context[:pal], params[:module_name], params[:task_name], ps_parameters)
473
+ task_info = allowed_helper(task_info, context[:config].project.tasks)
474
+ [200, task_info.to_json]
475
+ end
476
+ end
477
+
354
478
  # Fetches the list of plans for an environment, optionally fetching all metadata for each plan
355
479
  #
356
480
  # @param environment [String] the environment to fetch the list of plans from
@@ -375,6 +499,57 @@ module BoltServer
375
499
  end
376
500
  end
377
501
 
502
+ # Fetches the list of plans for a project
503
+ #
504
+ # @param project_ref [String] the project to fetch the list of plans from
505
+ get '/project_plans' do
506
+ in_bolt_project(params['project_ref']) do |context|
507
+ plans_response = plan_list(context[:pal])
508
+
509
+ # Dig in context for the allowlist of plans from project object
510
+ plans_response.map! { |metadata| allowed_helper(metadata, context[:config].project.plans) }
511
+
512
+ # We structure this array of plans to be an array of hashes so that it matches the structure
513
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
514
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
515
+ # to bolt-server smaller/simpler.
516
+ [200, plans_response.to_json]
517
+ end
518
+ end
519
+
520
+ # Fetches the list of tasks for an environment
521
+ #
522
+ # @param environment [String] the environment to fetch the list of tasks from
523
+ get '/tasks' do
524
+ in_pe_pal_env(params['environment']) do |pal|
525
+ tasks_response = task_list(pal).to_json
526
+
527
+ # We structure this array of tasks to be an array of hashes so that it matches the structure
528
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
529
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
530
+ # to bolt-server smaller/simpler.
531
+ [200, tasks_response]
532
+ end
533
+ end
534
+
535
+ # Fetches the list of tasks for a bolt-project
536
+ #
537
+ # @param project_ref [String] the project to fetch the list of tasks from
538
+ get '/project_tasks' do
539
+ in_bolt_project(params['project_ref']) do |context|
540
+ tasks_response = task_list(context[:pal])
541
+
542
+ # Dig in context for the allowlist of tasks from project object
543
+ tasks_response.map! { |metadata| allowed_helper(metadata, context[:config].project.tasks) }
544
+
545
+ # We structure this array of tasks to be an array of hashes so that it matches the structure
546
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
547
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
548
+ # to bolt-server smaller/simpler.
549
+ [200, tasks_response.to_json]
550
+ end
551
+ end
552
+
378
553
  error 404 do
379
554
  err = Bolt::Error.new("Could not find route #{request.path}",
380
555
  'boltserver/not-found')