bolt 2.30.0 → 2.34.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +12 -12
  3. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -1
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +1 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
  12. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  14. data/guides/logging.txt +18 -0
  15. data/guides/module.txt +19 -0
  16. data/guides/modulepath.txt +25 -0
  17. data/lib/bolt/bolt_option_parser.rb +6 -1
  18. data/lib/bolt/cli.rb +82 -142
  19. data/lib/bolt/config/modulepath.rb +30 -0
  20. data/lib/bolt/config/options.rb +31 -13
  21. data/lib/bolt/config/transport/options.rb +2 -2
  22. data/lib/bolt/error.rb +13 -3
  23. data/lib/bolt/executor.rb +24 -12
  24. data/lib/bolt/inventory.rb +10 -9
  25. data/lib/bolt/inventory/group.rb +2 -1
  26. data/lib/bolt/module_installer.rb +117 -91
  27. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  28. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  29. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  30. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  31. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  32. data/lib/bolt/module_installer/resolver.rb +76 -0
  33. data/lib/bolt/module_installer/specs.rb +93 -0
  34. data/lib/bolt/module_installer/specs/forge_spec.rb +85 -0
  35. data/lib/bolt/module_installer/specs/git_spec.rb +179 -0
  36. data/lib/bolt/outputter.rb +0 -47
  37. data/lib/bolt/outputter/human.rb +46 -16
  38. data/lib/bolt/outputter/json.rb +17 -8
  39. data/lib/bolt/pal.rb +52 -40
  40. data/lib/bolt/pal/yaml_plan.rb +4 -2
  41. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  42. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  43. data/lib/bolt/plan_creator.rb +160 -0
  44. data/lib/bolt/plugin.rb +2 -2
  45. data/lib/bolt/project.rb +6 -11
  46. data/lib/bolt/project_migrator.rb +1 -1
  47. data/lib/bolt/project_migrator/base.rb +2 -2
  48. data/lib/bolt/project_migrator/config.rb +5 -4
  49. data/lib/bolt/project_migrator/inventory.rb +3 -3
  50. data/lib/bolt/project_migrator/modules.rb +23 -21
  51. data/lib/bolt/puppetdb/config.rb +5 -5
  52. data/lib/bolt/result.rb +23 -11
  53. data/lib/bolt/shell/bash.rb +14 -8
  54. data/lib/bolt/shell/powershell.rb +12 -7
  55. data/lib/bolt/task/run.rb +1 -1
  56. data/lib/bolt/transport/base.rb +18 -18
  57. data/lib/bolt/transport/docker.rb +23 -6
  58. data/lib/bolt/transport/orch.rb +26 -17
  59. data/lib/bolt/transport/remote.rb +3 -3
  60. data/lib/bolt/transport/simple.rb +6 -6
  61. data/lib/bolt/transport/ssh/connection.rb +1 -1
  62. data/lib/bolt/util.rb +5 -0
  63. data/lib/bolt/version.rb +1 -1
  64. data/lib/bolt_server/file_cache.rb +2 -0
  65. data/lib/bolt_server/schemas/partials/task.json +17 -2
  66. data/lib/bolt_server/transport_app.rb +92 -12
  67. data/lib/bolt_spec/bolt_context.rb +4 -2
  68. data/lib/bolt_spec/plans.rb +1 -1
  69. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  70. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  71. data/lib/bolt_spec/plans/mock_executor.rb +5 -5
  72. data/lib/bolt_spec/run.rb +1 -1
  73. metadata +24 -9
  74. data/lib/bolt/puppetfile.rb +0 -142
  75. data/lib/bolt/puppetfile/module.rb +0 -90
  76. data/lib/bolt_server/pe/pal.rb +0 -67
  77. data/modules/secure_env_vars/plans/init.pp +0 -20
@@ -42,7 +42,7 @@ module Bolt
42
42
  if targets.empty?
43
43
  Bolt::ResultSet.new([])
44
44
  else
45
- result = executor.run_task(targets, task, params, options)
45
+ result = executor.run_task_with_minimal_logging(targets, task, params, options)
46
46
 
47
47
  if !result.ok && !options[:catch_errors]
48
48
  raise Bolt::RunFailure.new(result, 'run_task', task.name)
@@ -43,13 +43,13 @@ module Bolt
43
43
  @logger = Bolt::Logger.logger(self)
44
44
  end
45
45
 
46
- def with_events(target, callback, action)
46
+ def with_events(target, callback, action, position)
47
47
  callback&.call(type: :node_start, target: target)
48
48
 
49
49
  result = begin
50
50
  yield
51
51
  rescue StandardError, NotImplementedError => e
52
- Bolt::Result.from_exception(target, e, action: action)
52
+ Bolt::Result.from_exception(target, e, action: action, position: position)
53
53
  end
54
54
 
55
55
  callback&.call(type: :node_result, result: result)
@@ -100,12 +100,12 @@ module Bolt
100
100
  # The default implementation only supports batches of size 1 and will fail otherwise.
101
101
  #
102
102
  # Transports may override this method to implement their own batch processing.
103
- def batch_task(targets, task, arguments, options = {}, &callback)
103
+ def batch_task(targets, task, arguments, options = {}, position = [], &callback)
104
104
  assert_batch_size_one("batch_task()", targets)
105
105
  target = targets.first
106
- with_events(target, callback, 'task') do
106
+ with_events(target, callback, 'task', position) do
107
107
  @logger.debug { "Running task '#{task.name}' on #{target.safe_name}" }
108
- run_task(target, task, arguments, options)
108
+ run_task(target, task, arguments, options, position)
109
109
  end
110
110
  end
111
111
 
@@ -114,14 +114,14 @@ module Bolt
114
114
  # The default implementation only supports batches of size 1 and will fail otherwise.
115
115
  #
116
116
  # Transports may override this method to implment their own batch processing.
117
- def batch_task_with(targets, task, target_mapping, options = {}, &callback)
117
+ def batch_task_with(targets, task, target_mapping, options = {}, position = [], &callback)
118
118
  assert_batch_size_one("batch_task_with()", targets)
119
119
  target = targets.first
120
120
  arguments = target_mapping[target]
121
121
 
122
- with_events(target, callback, 'task') do
122
+ with_events(target, callback, 'task', position) do
123
123
  @logger.debug { "Running task '#{task.name}' on #{target.safe_name} with '#{arguments.to_json}'" }
124
- run_task(target, task, arguments, options)
124
+ run_task(target, task, arguments, options, position)
125
125
  end
126
126
  end
127
127
 
@@ -130,12 +130,12 @@ module Bolt
130
130
  # The default implementation only supports batches of size 1 and will fail otherwise.
131
131
  #
132
132
  # Transports may override this method to implement their own batch processing.
133
- def batch_command(targets, command, options = {}, &callback)
133
+ def batch_command(targets, command, options = {}, position = [], &callback)
134
134
  assert_batch_size_one("batch_command()", targets)
135
135
  target = targets.first
136
- with_events(target, callback, 'command') do
136
+ with_events(target, callback, 'command', position) do
137
137
  @logger.debug("Running command '#{command}' on #{target.safe_name}")
138
- run_command(target, command, options)
138
+ run_command(target, command, options, position)
139
139
  end
140
140
  end
141
141
 
@@ -144,12 +144,12 @@ module Bolt
144
144
  # The default implementation only supports batches of size 1 and will fail otherwise.
145
145
  #
146
146
  # Transports may override this method to implement their own batch processing.
147
- def batch_script(targets, script, arguments, options = {}, &callback)
147
+ def batch_script(targets, script, arguments, options = {}, position = [], &callback)
148
148
  assert_batch_size_one("batch_script()", targets)
149
149
  target = targets.first
150
- with_events(target, callback, 'script') do
150
+ with_events(target, callback, 'script', position) do
151
151
  @logger.debug { "Running script '#{script}' on #{target.safe_name}" }
152
- run_script(target, script, arguments, options)
152
+ run_script(target, script, arguments, options, position)
153
153
  end
154
154
  end
155
155
 
@@ -158,10 +158,10 @@ module Bolt
158
158
  # The default implementation only supports batches of size 1 and will fail otherwise.
159
159
  #
160
160
  # Transports may override this method to implement their own batch processing.
161
- def batch_upload(targets, source, destination, options = {}, &callback)
161
+ def batch_upload(targets, source, destination, options = {}, position = [], &callback)
162
162
  assert_batch_size_one("batch_upload()", targets)
163
163
  target = targets.first
164
- with_events(target, callback, 'upload') do
164
+ with_events(target, callback, 'upload', position) do
165
165
  @logger.debug { "Uploading: '#{source}' to #{destination} on #{target.safe_name}" }
166
166
  upload(target, source, destination, options)
167
167
  end
@@ -173,12 +173,12 @@ module Bolt
173
173
  # The default implementation only supports batches of size 1 and will fail otherwise.
174
174
  #
175
175
  # Transports may override this method to implement their own batch processing.
176
- def batch_download(targets, source, destination, options = {}, &callback)
176
+ def batch_download(targets, source, destination, options = {}, position = [], &callback)
177
177
  require 'erb'
178
178
 
179
179
  assert_batch_size_one("batch_download()", targets)
180
180
  target = targets.first
181
- with_events(target, callback, 'download') do
181
+ with_events(target, callback, 'download', position) do
182
182
  escaped_name = ERB::Util.url_encode(target.safe_name)
183
183
  target_destination = File.expand_path(escaped_name, destination)
184
184
  @logger.debug { "Downloading: '#{source}' on #{target.safe_name} to #{target_destination}" }
@@ -46,7 +46,7 @@ module Bolt
46
46
  end
47
47
  end
48
48
 
49
- def run_command(target, command, options = {})
49
+ def run_command(target, command, options = {}, position = [])
50
50
  execute_options = {}
51
51
  execute_options[:tty] = target.options['tty']
52
52
  execute_options[:environment] = options[:env_vars]
@@ -58,11 +58,17 @@ module Bolt
58
58
  end
59
59
  with_connection(target) do |conn|
60
60
  stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), execute_options)
61
- Bolt::Result.for_command(target, stdout, stderr, exitcode, 'command', command)
61
+ Bolt::Result.for_command(target,
62
+ stdout,
63
+ stderr,
64
+ exitcode,
65
+ 'command',
66
+ command,
67
+ position)
62
68
  end
63
69
  end
64
70
 
65
- def run_script(target, script, arguments, options = {})
71
+ def run_script(target, script, arguments, options = {}, position = [])
66
72
  # unpack any Sensitive data
67
73
  arguments = unwrap_sensitive_args(arguments)
68
74
  execute_options = {}
@@ -72,12 +78,18 @@ module Bolt
72
78
  conn.with_remote_tmpdir do |dir|
73
79
  remote_path = conn.write_remote_executable(dir, script)
74
80
  stdout, stderr, exitcode = conn.execute(remote_path, *arguments, execute_options)
75
- Bolt::Result.for_command(target, stdout, stderr, exitcode, 'script', script)
81
+ Bolt::Result.for_command(target,
82
+ stdout,
83
+ stderr,
84
+ exitcode,
85
+ 'script',
86
+ script,
87
+ position)
76
88
  end
77
89
  end
78
90
  end
79
91
 
80
- def run_task(target, task, arguments, _options = {})
92
+ def run_task(target, task, arguments, _options = {}, position = [])
81
93
  implementation = task.select_implementation(target, provided_features)
82
94
  executable = implementation['path']
83
95
  input_method = implementation['input_method']
@@ -113,7 +125,12 @@ module Bolt
113
125
  end
114
126
 
115
127
  stdout, stderr, exitcode = conn.execute(remote_task_path, execute_options)
116
- Bolt::Result.for_task(target, stdout, stderr, exitcode, task.name)
128
+ Bolt::Result.for_task(target,
129
+ stdout,
130
+ stderr,
131
+ exitcode,
132
+ task.name,
133
+ position)
117
134
  end
118
135
  end
119
136
  end
@@ -10,10 +10,10 @@ require 'bolt/transport/orch/connection'
10
10
  module Bolt
11
11
  module Transport
12
12
  class Orch < Base
13
- CONF_FILE = if !ENV['HOME'].nil?
14
- File.expand_path('~/.puppetlabs/client-tools/orchestrator.conf')
15
- else
13
+ CONF_FILE = if ENV['HOME'].nil?
16
14
  '/etc/puppetlabs/client-tools/orchestrator.conf'
15
+ else
16
+ File.expand_path('~/.puppetlabs/client-tools/orchestrator.conf')
17
17
  end
18
18
  BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
19
19
  BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
@@ -53,7 +53,7 @@ module Bolt
53
53
  conn
54
54
  end
55
55
 
56
- def process_run_results(targets, results, task_name)
56
+ def process_run_results(targets, results, task_name, position = [])
57
57
  targets_by_name = Hash[targets.map { |t| t.host || t.name }.zip(targets)]
58
58
  results.map do |node_result|
59
59
  target = targets_by_name[node_result['name']]
@@ -63,25 +63,31 @@ module Bolt
63
63
  # If it's finished or already has a proper error simply pass it to the
64
64
  # the result otherwise make sure an error is generated
65
65
  if state == 'finished' || (result && result['_error'])
66
+ if result['_error']
67
+ file_line = %w[file line].zip(position).to_h.compact
68
+ result['_error']['details'].merge!(file_line) unless result['_error']['details']['file']
69
+ end
70
+
66
71
  Bolt::Result.new(target, value: result, action: 'task', object: task_name)
67
72
  elsif state == 'skipped'
73
+ details = %w[file line].zip(position).to_h.compact
68
74
  Bolt::Result.new(
69
75
  target,
70
76
  value: { '_error' => {
71
77
  'kind' => 'puppetlabs.tasks/skipped-node',
72
78
  'msg' => "Target #{target.safe_name} was skipped",
73
- 'details' => {}
79
+ 'details' => details
74
80
  } },
75
81
  action: 'task', object: task_name
76
82
  )
77
83
  else
78
84
  # Make a generic error with a unkown exit_code
79
- Bolt::Result.for_task(target, result.to_json, '', 'unknown', task_name)
85
+ Bolt::Result.for_task(target, result.to_json, '', 'unknown', task_name, position)
80
86
  end
81
87
  end
82
88
  end
83
89
 
84
- def batch_command(targets, command, options = {}, &callback)
90
+ def batch_command(targets, command, options = {}, position = [], &callback)
85
91
  if options[:env_vars] && !options[:env_vars].empty?
86
92
  raise NotImplementedError, "pcp transport does not support setting environment variables"
87
93
  end
@@ -93,6 +99,7 @@ module Bolt
93
99
  BOLT_COMMAND_TASK,
94
100
  params,
95
101
  options,
102
+ position,
96
103
  &callback)
97
104
  callback ||= proc {}
98
105
  results.map! { |result| unwrap_bolt_result(result.target, result, 'command', command) }
@@ -101,7 +108,7 @@ module Bolt
101
108
  end
102
109
  end
103
110
 
104
- def batch_script(targets, script, arguments, options = {}, &callback)
111
+ def batch_script(targets, script, arguments, options = {}, position = [], &callback)
105
112
  if options[:env_vars] && !options[:env_vars].empty?
106
113
  raise NotImplementedError, "pcp transport does not support setting environment variables"
107
114
  end
@@ -114,7 +121,7 @@ module Bolt
114
121
  'name' => Pathname(script).basename.to_s
115
122
  }
116
123
  callback ||= proc {}
117
- results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
124
+ results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, position, &callback)
118
125
  results.map! { |result| unwrap_bolt_result(result.target, result, 'script', script) }
119
126
  results.each do |result|
120
127
  callback.call(type: :node_result, result: result)
@@ -155,7 +162,7 @@ module Bolt
155
162
  output&.close
156
163
  end
157
164
 
158
- def batch_upload(targets, source, destination, options = {}, &callback)
165
+ def batch_upload(targets, source, destination, options = {}, position = [], &callback)
159
166
  stat = File.stat(source)
160
167
  content = if stat.directory?
161
168
  pack(source)
@@ -171,7 +178,7 @@ module Bolt
171
178
  'directory' => stat.directory?
172
179
  }
173
180
  callback ||= proc {}
174
- results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options, &callback)
181
+ results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options, position, &callback)
175
182
  results.map! do |result|
176
183
  if result.error_hash
177
184
  result
@@ -200,7 +207,7 @@ module Bolt
200
207
  targets.group_by { |target| Connection.get_key(target.options) }.values
201
208
  end
202
209
 
203
- def run_task_job(targets, task, arguments, options)
210
+ def run_task_job(targets, task, arguments, options, position)
204
211
  targets.each do |target|
205
212
  yield(type: :node_start, target: target) if block_given?
206
213
  end
@@ -210,7 +217,7 @@ module Bolt
210
217
  arguments = unwrap_sensitive_args(arguments)
211
218
  results = get_connection(targets.first.options).run_task(targets, task, arguments, options)
212
219
 
213
- process_run_results(targets, results, task.name)
220
+ process_run_results(targets, results, task.name, position)
214
221
  rescue OrchestratorClient::ApiError => e
215
222
  targets.map do |target|
216
223
  Bolt::Result.new(target, error: e.data)
@@ -222,15 +229,15 @@ module Bolt
222
229
  end
223
230
  end
224
231
 
225
- def batch_task(targets, task, arguments, options = {}, &callback)
232
+ def batch_task(targets, task, arguments, options = {}, position = [], &callback)
226
233
  callback ||= proc {}
227
- results = run_task_job(targets, task, arguments, options, &callback)
234
+ results = run_task_job(targets, task, arguments, options, position, &callback)
228
235
  results.each do |result|
229
236
  callback.call(type: :node_result, result: result)
230
237
  end
231
238
  end
232
239
 
233
- def batch_task_with(_targets, _task, _target_mapping, _options = {})
240
+ def batch_task_with(_targets, _task, _target_mapping, _options = {}, _position = [])
234
241
  raise NotImplementedError, "pcp transport does not support run_task_with()"
235
242
  end
236
243
 
@@ -248,11 +255,13 @@ module Bolt
248
255
  return result
249
256
  end
250
257
 
258
+ # If we get here, there's no error so we don't need the file or line
259
+ # number
251
260
  Bolt::Result.for_command(target,
252
261
  result.value['stdout'],
253
262
  result.value['stderr'],
254
263
  result.value['exit_code'],
255
- action, obj)
264
+ action, obj, [])
256
265
  end
257
266
  end
258
267
  end
@@ -26,14 +26,14 @@ module Bolt
26
26
  end
27
27
 
28
28
  # Cannot batch because arugments differ
29
- def run_task(target, task, arguments, options = {})
29
+ def run_task(target, task, arguments, options = {}, position = [])
30
30
  proxy_target = get_proxy(target)
31
31
  transport = @executor.transport(proxy_target.transport)
32
- arguments = arguments.merge('_target' => target.to_h.reject { |_, v| v.nil? })
32
+ arguments = arguments.merge('_target' => target.to_h.compact)
33
33
 
34
34
  remote_task = task.remote_instance
35
35
 
36
- result = transport.run_task(proxy_target, remote_task, arguments, options)
36
+ result = transport.run_task(proxy_target, remote_task, arguments, options, position)
37
37
  Bolt::Result.new(target, value: result.value, action: 'task', object: task.name)
38
38
  end
39
39
  end
@@ -20,9 +20,9 @@ module Bolt
20
20
  false
21
21
  end
22
22
 
23
- def run_command(target, command, options = {})
23
+ def run_command(target, command, options = {}, position = [])
24
24
  with_connection(target) do |conn|
25
- conn.shell.run_command(command, options)
25
+ conn.shell.run_command(command, options, position)
26
26
  end
27
27
  end
28
28
 
@@ -38,15 +38,15 @@ module Bolt
38
38
  end
39
39
  end
40
40
 
41
- def run_script(target, script, arguments, options = {})
41
+ def run_script(target, script, arguments, options = {}, position = [])
42
42
  with_connection(target) do |conn|
43
- conn.shell.run_script(script, arguments, options)
43
+ conn.shell.run_script(script, arguments, options, position)
44
44
  end
45
45
  end
46
46
 
47
- def run_task(target, task, arguments, options = {})
47
+ def run_task(target, task, arguments, options = {}, position = [])
48
48
  with_connection(target) do |conn|
49
- conn.shell.run_task(task, arguments, options)
49
+ conn.shell.run_task(task, arguments, options, position)
50
50
  end
51
51
  end
52
52
  end
@@ -30,7 +30,7 @@ module Bolt
30
30
  @transport_logger = transport_logger
31
31
  @logger.trace("Initializing ssh connection to #{@target.safe_name}")
32
32
 
33
- if target.options['private-key']&.instance_of?(String)
33
+ if target.options['private-key'].instance_of?(String)
34
34
  begin
35
35
  Bolt::Util.validate_file('ssh key', target.options['private-key'])
36
36
  rescue Bolt::FileError => e
@@ -273,6 +273,11 @@ module Bolt
273
273
  !!File::ALT_SEPARATOR
274
274
  end
275
275
 
276
+ # Returns true if running in PowerShell.
277
+ def powershell?
278
+ !!ENV['PSModulePath']
279
+ end
280
+
276
281
  # Accept hash and return hash with top level keys of type "String" converted to symbols.
277
282
  def symbolize_top_level_keys(hsh)
278
283
  hsh.each_with_object({}) { |(k, v), h| k.is_a?(String) ? h[k.to_sym] = v : h[k] = v }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.30.0'
4
+ VERSION = '2.34.0'
5
5
  end
@@ -63,6 +63,7 @@ module BoltServer
63
63
  end
64
64
 
65
65
  def client
66
+ # rubocop:disable Naming/VariableNumber
66
67
  @client ||= begin
67
68
  uri = URI(@config['file-server-uri'])
68
69
  https = Net::HTTP.new(uri.host, uri.port)
@@ -75,6 +76,7 @@ module BoltServer
75
76
  https.open_timeout = @config['file-server-conn-timeout']
76
77
  https
77
78
  end
79
+ # rubocop:enable Naming/VariableNumber
78
80
  end
79
81
 
80
82
  def request_file(path, params, file)
@@ -63,9 +63,24 @@
63
63
  "environment": {
64
64
  "description": "Environment the task is in",
65
65
  "type": "string"
66
+ },
67
+ "project": {
68
+ "description": "Project the task is in",
69
+ "type": "string"
66
70
  }
67
71
  },
68
- "required": ["environment"],
72
+ "oneOf": [
73
+ {
74
+ "required": [
75
+ "environment"
76
+ ]
77
+ },
78
+ {
79
+ "required": [
80
+ "project"
81
+ ]
82
+ }
83
+ ],
69
84
  "additionalProperties": true
70
85
  }
71
86
  },
@@ -90,5 +105,5 @@
90
105
  }
91
106
  },
92
107
  "required": ["name", "files"],
93
- "additionalProperties": false
108
+ "additionalProperties": true
94
109
  }