bolt 1.43.0 → 1.44.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c237cb6fec506c63071962c4d8cf16fafb0b196abe720c5e0af8b9d8d350f11
4
- data.tar.gz: b7505bc572e3e123b265b869ed2386bbb784882365102a3b816ab1b5e5599ab1
3
+ metadata.gz: 3e7033e924c3721ad51ea3ba221527831174bf0a962a09be2d01af56eea9d29a
4
+ data.tar.gz: 400125a4441d5df92ac45fae11e222be1b0d8925ea501144e61f82f4a60fc62a
5
5
  SHA512:
6
- metadata.gz: '0178352d34985cdd7c2960a018ff4a502b813763df806e72dd21f302bf26b173eeaa54eb2b44092b340e82b682b141fca5f72ba6e11bda6475556983dcbcf0ee'
7
- data.tar.gz: 4473441d248c6023b3fe87364439f6c32498b6a6ac71b61925116838ab72eab22a457e309189cac2d4a0ebd58b57e7ded0140c902dc2367c5f45d76cbba5932a
6
+ metadata.gz: b25b113a4b89a14c84aec41fe2e8e1fad3a41a82ac4b6e58e8ff3901e46ac2be2a71eafee0cc80545f9d03cdb6d93affd1e44dc90f426fdb415bbfd26e17d16d
7
+ data.tar.gz: 6b08cf2a56959c286622c1279b2583ef312c673cb08c719727ba4c29142f8e9ea2d449f12a811eba671a7658b1619a735203905d98bae6dfc66ecb7c00b958a0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Join file paths.
4
+ Puppet::Functions.create_function(:'file::join') do
5
+ # @param paths The paths to join.
6
+ # @example Join file paths
7
+ # file::join('./path', 'to/files')
8
+ dispatch :join do
9
+ required_repeated_param 'String', :paths
10
+ return_type 'String'
11
+ end
12
+
13
+ def join(*paths)
14
+ Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
15
+ File.join(paths)
16
+ end
17
+ end
@@ -223,7 +223,7 @@ module Bolt
223
223
  if @future
224
224
  to_expand = %w[private-key cacert token-file] & selected.keys
225
225
  to_expand.each do |opt|
226
- selected[opt] = File.expand_path(selected[opt], @boltdir.path) if opt.is_a?(String)
226
+ selected[opt] = File.expand_path(selected[opt], @boltdir.path) if selected[opt].is_a?(String)
227
227
  end
228
228
  end
229
229
 
@@ -169,12 +169,23 @@ module Bolt
169
169
  # references to the same object and prevents endless recursion.
170
170
  # Credit to Jan Molic via https://github.com/rubyworks/facets/blob/master/LICENSE.txt
171
171
  def deep_clone(obj, cloned = {})
172
- cloned[obj.object_id] if cloned.include?(obj.object_id)
172
+ return cloned[obj.object_id] if cloned.include?(obj.object_id)
173
+
174
+ # The `defined?` method will not reliably find the Java::JavaLang::CloneNotSupportedException constant
175
+ # presumably due to some sort of optimization that short-cuts doing a bunch of Java introspection.
176
+ # Java::JavaLang::<...> IS defining the constant (via const_missing or const_get magic perhaps) so
177
+ # it is safe to reference it in the error_types array when a JRuby interpreter is evaluating the code
178
+ # (detected by RUBY_PLATFORM == `java`). SO instead of conditionally adding the CloneNotSupportedException
179
+ # constant to the error_types array based on `defined?` detecting the Java::JavaLang constant it is added
180
+ # based on detecting a JRuby interpreter.
181
+ # TypeError handles unclonable Ruby ojbects (TrueClass, Fixnum, ...)
182
+ # CloneNotSupportedException handles uncloneable Java objects (JRuby only)
183
+ error_types = [TypeError]
184
+ error_types << Java::JavaLang::CloneNotSupportedException if RUBY_PLATFORM == 'java'
173
185
 
174
186
  begin
175
187
  cl = obj.clone
176
- rescue TypeError
177
- # unclonable (TrueClass, Fixnum, ...)
188
+ rescue *error_types
178
189
  cloned[obj.object_id] = obj
179
190
  obj
180
191
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.43.0'
4
+ VERSION = '1.44.0'
5
5
  end
@@ -188,6 +188,11 @@ module BoltServer
188
188
  [200, GC.stat.to_json]
189
189
  end
190
190
 
191
+ get '/admin/status' do
192
+ stats = Puma.stats
193
+ [200, stats.is_a?(Hash) ? stats.to_json : stats]
194
+ end
195
+
191
196
  get '/500_error' do
192
197
  raise 'Unexpected error'
193
198
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.43.0
4
+ version: 1.44.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-18 00:00:00.000000000 Z
11
+ date: 2020-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -367,6 +367,7 @@ files:
367
367
  - bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb
368
368
  - bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb
369
369
  - bolt-modules/file/lib/puppet/functions/file/exists.rb
370
+ - bolt-modules/file/lib/puppet/functions/file/join.rb
370
371
  - bolt-modules/file/lib/puppet/functions/file/read.rb
371
372
  - bolt-modules/file/lib/puppet/functions/file/readable.rb
372
373
  - bolt-modules/file/lib/puppet/functions/file/write.rb
@@ -485,12 +486,6 @@ files:
485
486
  - lib/bolt_spec/plans/publish_stub.rb
486
487
  - lib/bolt_spec/run.rb
487
488
  - lib/logging_extensions/logging.rb
488
- - lib/plan_executor/app.rb
489
- - lib/plan_executor/applicator.rb
490
- - lib/plan_executor/config.rb
491
- - lib/plan_executor/executor.rb
492
- - lib/plan_executor/orch_client.rb
493
- - lib/plan_executor/schemas/run_plan.json
494
489
  - libexec/apply_catalog.rb
495
490
  - libexec/bolt_catalog
496
491
  - libexec/custom_facts.rb
@@ -1,141 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sinatra'
4
- require 'bolt'
5
- require 'bolt/error'
6
- require 'bolt/inventory'
7
- require 'bolt/pal'
8
- require 'bolt/puppetdb'
9
- require 'bolt/version'
10
- require 'plan_executor/applicator'
11
- require 'plan_executor/executor'
12
- require 'json'
13
- require 'json-schema'
14
-
15
- module PlanExecutor
16
- class App < Sinatra::Base
17
- # This disables Sinatra's error page generation
18
- set :show_exceptions, false
19
- # Global var to capture output for testing
20
- result = nil
21
-
22
- helpers do
23
- def puppetdb_client
24
- return @puppetdb_client if @puppetdb_client
25
- @puppetdb_client = Bolt::PuppetDB::Client.new({})
26
- end
27
- end
28
-
29
- def initialize(config)
30
- # lazy-load expensive gem code
31
- require 'concurrent'
32
-
33
- @http_client = create_http(config)
34
-
35
- # Use an empty inventory until we figure out where this data comes from.
36
- @inventory = Bolt::Inventory.new(nil)
37
-
38
- # PAL is not threadsafe. Part of the work of making the plan executor
39
- # functional will be making changes to Puppet that remove the need for
40
- # global Puppet state.
41
- # https://github.com/puppetlabs/bolt/blob/master/lib/bolt/pal.rb#L166
42
- @pal = Bolt::PAL.new(config['modulepath'], nil, nil)
43
-
44
- @schema = JSON.parse(File.read(File.join(__dir__, 'schemas', 'run_plan.json')))
45
- @worker = Concurrent::SingleThreadExecutor.new
46
- @modulepath = config['modulepath']
47
-
48
- super(nil)
49
- end
50
-
51
- def create_http(config)
52
- base_url = config['orchestrator-url'].chomp('/') + '/orchestrator/v1/'
53
- agent_name = "Bolt/#{Bolt::VERSION}"
54
- http = JSONClient.new(base_url: base_url, agent_name: agent_name)
55
- http.ssl_config.set_client_cert_file(config['ssl-cert'], config['ssl-key'])
56
- http.ssl_config.add_trust_ca(config['ssl-ca-cert'])
57
- http
58
- end
59
-
60
- def validate_schema(schema, body)
61
- schema_error = JSON::Validator.fully_validate(schema, body)
62
- if schema_error.any?
63
- Bolt::Error.new("There was an error validating the request body.",
64
- 'boltserver/schema-error',
65
- schema_error)
66
- end
67
- end
68
-
69
- get '/' do
70
- 200
71
- end
72
-
73
- if ENV['RACK_ENV'] == 'dev'
74
- get '/admin/gc' do
75
- GC.start
76
- 200
77
- end
78
-
79
- get '/admin/gc_stat' do
80
- [200, GC.stat.to_json]
81
- end
82
- end
83
-
84
- get '/500_error' do
85
- raise 'Unexpected error'
86
- end
87
-
88
- post '/plan/run' do
89
- content_type :json
90
-
91
- body = JSON.parse(request.body.read)
92
- error = validate_schema(@schema, body)
93
- return [400, error.to_json] unless error.nil?
94
- name = body['plan_name']
95
- # We need to wrap all calls to @pal (not just plan_run) in a future
96
- # to ensure that the process always uses the SingleThreadExecutor
97
- # worker and forces one call to @pal at a time regardless of the number
98
- # of concurrent calls to POST /plan_run
99
- result = Concurrent::Future.execute(executor: @worker) do
100
- @pal.get_plan_info(name)
101
- end
102
- # .value! will fail if the internal process of the thread fails
103
- result.value!
104
- executor = PlanExecutor::Executor.new(body['job_id'], @http_client)
105
- applicator = PlanExecutor::Applicator.new(@inventory, executor, nil)
106
- params = body['params']
107
- # This provides a wait function, which promise doesn't
108
- result = Concurrent::Future.execute(executor: @worker) do
109
- pal_result = @pal.run_plan(name, params, executor, @inventory, puppetdb_client, applicator)
110
- executor.finish_plan(pal_result)
111
- pal_result
112
- end
113
- [200, { status: 'running' }.to_json]
114
- end
115
-
116
- # Provided for testing
117
- get '/plan/result' do
118
- result.wait_or_cancel(20)
119
- if result.fulfilled?
120
- return [200, result.value.to_json]
121
- elsif result.rejected?
122
- raise result.reason.to_s
123
- else
124
- return [200, result.state.to_s]
125
- end
126
- end
127
-
128
- error 404 do
129
- err = Bolt::Error.new("Could not find route #{request.path}",
130
- 'boltserver/not-found')
131
- [404, err.to_json]
132
- end
133
-
134
- error 500 do
135
- e = env['sinatra.error']
136
- err = Bolt::Error.new("500: Unknown error: #{e.message}",
137
- 'boltserver/server-error')
138
- [500, err.to_json]
139
- end
140
- end
141
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bolt/error'
4
-
5
- module PlanExecutor
6
- class Applicator
7
- def initialize(inventory, executor, config)
8
- @inventory = inventory
9
- @executor = executor
10
- @config = config
11
- end
12
-
13
- def raise_not_implemented(feature)
14
- raise Bolt::Error.new("#{feature} not implemented for plan executor service.",
15
- 'bolt.plan-executor/not-implemented')
16
- end
17
-
18
- def apply(_args, _apply_body, _scope)
19
- raise_not_implemented("apply")
20
- end
21
-
22
- def build_plugin_tarball
23
- raise_not_implemented("build_plugin_tarball")
24
- end
25
-
26
- def custom_facts_task
27
- raise_not_implemented('custom_facts_task')
28
- end
29
- end
30
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'hocon'
4
- require 'bolt_server/base_config'
5
- require 'bolt/error'
6
-
7
- module PlanExecutor
8
- class Config < BoltServer::BaseConfig
9
- def config_keys
10
- super + %w[modulepath workers orchestrator-url]
11
- end
12
-
13
- def defaults
14
- super.merge(
15
- 'port' => 62659,
16
- 'workers' => 1
17
- )
18
- end
19
-
20
- def service_name
21
- 'plan-executor'
22
- end
23
-
24
- def required_keys
25
- super + ['orchestrator-url']
26
- end
27
-
28
- def load_env_config
29
- env_keys.each do |key|
30
- transformed_key = "BOLT_#{key.tr('-', '_').upcase}"
31
- next unless ENV.key?(transformed_key)
32
- @data[key] = ENV[transformed_key]
33
- end
34
- end
35
-
36
- def validate
37
- super
38
- unless natural?(@data['workers'])
39
- raise Bolt::ValidationError, "Configured 'workers' must be a positive integer"
40
- end
41
- end
42
- end
43
- end
@@ -1,167 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Used for $ERROR_INFO. This *must* be capitalized!
4
- require 'English'
5
- require 'json'
6
- require 'logging'
7
- require 'set'
8
- require 'bolt/result'
9
- require 'bolt/config'
10
- require 'bolt/result_set'
11
- require 'bolt/puppetdb'
12
- require 'plan_executor/orch_client'
13
-
14
- module PlanExecutor
15
- class Executor
16
- attr_reader :noop, :logger
17
- attr_accessor :orch_client
18
-
19
- def initialize(job_id, http_client, noop = nil)
20
- @logger = Logging.logger[self]
21
- @plan_logging = false
22
- @noop = noop
23
- @logger.debug { "Started" }
24
- @orch_client = PlanExecutor::OrchClient.new(job_id, http_client, @logger)
25
- end
26
-
27
- # This handles running the job, catching errors, and turning the result
28
- # into a result set
29
- def as_resultset(targets)
30
- result_array = begin
31
- yield
32
- rescue StandardError => e
33
- @logger.warn(e)
34
- # CODEREVIEW how should we fail if there's an error?
35
- Array(Bolt::Result.from_exception(targets[0], e))
36
- end
37
- Bolt::ResultSet.new(result_array)
38
- end
39
-
40
- # BOLT-1098
41
- def log_action(description, targets)
42
- # When running a plan, info messages like starting a task are promoted to notice.
43
- log_method = @plan_logging ? :notice : :info
44
- target_str = if targets.length > 5
45
- "#{targets.count} targets"
46
- else
47
- targets.map(&:uri).join(', ')
48
- end
49
-
50
- @logger.send(log_method, "Starting: #{description} on #{target_str}")
51
-
52
- start_time = Time.now
53
- results = yield
54
- duration = Time.now - start_time
55
-
56
- failures = results.error_set.length
57
- plural = failures == 1 ? '' : 's'
58
-
59
- @logger.send(log_method, "Finished: #{description} with #{failures} failure#{plural} in #{duration.round(2)} sec")
60
-
61
- results
62
- end
63
-
64
- # BOLT-1098
65
- def log_plan(plan_name)
66
- log_method = @plan_logging ? :notice : :info
67
- @logger.send(log_method, "Starting: plan #{plan_name}")
68
- start_time = Time.now
69
-
70
- results = nil
71
- begin
72
- results = yield
73
- ensure
74
- duration = Time.now - start_time
75
- @logger.send(log_method, "Finished: plan #{plan_name} in #{duration.round(2)} sec")
76
- end
77
-
78
- results
79
- end
80
-
81
- def run_command(targets, command, options = {})
82
- description = options.fetch(:description, "command '#{command}'")
83
- log_action(description, targets) do
84
- results = as_resultset(targets) do
85
- @orch_client.run_command(targets, command, options)
86
- end
87
-
88
- results
89
- end
90
- end
91
-
92
- def run_script(targets, script, arguments, options = {})
93
- description = options.fetch(:description, "script #{script}")
94
- log_action(description, targets) do
95
- results = as_resultset(targets) do
96
- @orch_client.run_script(targets, script, arguments, options)
97
- end
98
-
99
- results
100
- end
101
- end
102
-
103
- def run_task(targets, task, arguments, options = {})
104
- description = options.fetch(:description, "task #{task.name}")
105
- log_action(description, targets) do
106
- arguments['_task'] = task.name
107
-
108
- results = as_resultset(targets) do
109
- @orch_client.run_task(targets, task, arguments, options)
110
- end
111
-
112
- results
113
- end
114
- end
115
-
116
- def upload_file(targets, source, destination, options = {})
117
- description = options.fetch(:description, "file upload from #{source} to #{destination}")
118
- log_action(description, targets) do
119
- results = as_resultset(targets) do
120
- @orch_client.file_upload(targets, source, destination, options)
121
- end
122
-
123
- results
124
- end
125
- end
126
-
127
- class TimeoutError < RuntimeError; end
128
-
129
- def wait_until_available(targets,
130
- description: 'wait until available',
131
- wait_time: 120,
132
- retry_interval: 1)
133
- log_action(description, targets) do
134
- begin
135
- wait_until(wait_time, retry_interval) { @orch_client.connected?(targets) }
136
- targets.map { |target| Bolt::Result.new(target) }
137
- rescue TimeoutError => e
138
- targets.map { |target| Bolt::Result.from_exception(target, e) }
139
- end
140
- end
141
- end
142
-
143
- def wait_until(timeout, retry_interval)
144
- start = wait_now
145
- until yield
146
- raise(TimeoutError, 'Timed out waiting for target') if (wait_now - start).to_i >= timeout
147
- sleep(retry_interval)
148
- end
149
- end
150
-
151
- def finish_plan(plan_result)
152
- @orch_client.finish_plan(plan_result)
153
- end
154
-
155
- def without_default_logging
156
- old_log = @plan_logging
157
- @plan_logging = false
158
- yield
159
- ensure
160
- @plan_logging = old_log
161
- end
162
-
163
- def report_bundled_content(mode, name); end
164
-
165
- def report_function_call(function); end
166
- end
167
- end
@@ -1,241 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'jsonclient'
4
-
5
- module PlanExecutor
6
- class OrchClient
7
- attr_reader :plan_job, :http
8
-
9
- BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
10
- BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
11
- BOLT_UPLOAD_TASK = Struct.new(:name).new('bolt_shim::upload').freeze
12
-
13
- def initialize(plan_job, http_client, logger)
14
- @plan_job = plan_job
15
- @logger = logger
16
- @http = http_client
17
- @environment = 'production'
18
- end
19
-
20
- def finish_plan(plan_result)
21
- body = {
22
- plan_job: @plan_job,
23
- result: plan_result.value || '',
24
- status: plan_result.status
25
- }
26
- post_command('internal/plan_finish', body)
27
- rescue StandardError => e
28
- @logger.error("Failed to finish plan #{plan_job}: #{e.message}")
29
- end
30
-
31
- def run_task_job(targets, task, arguments, options)
32
- # unpack any Sensitive data
33
- arguments = unwrap_sensitive_args(arguments)
34
- results = send_request(targets, task, arguments, options)
35
-
36
- process_run_results(targets, results)
37
- rescue OrchestratorClient::ApiError => e
38
- targets.map do |target|
39
- Bolt::Result.new(target, error: e.data)
40
- end
41
- rescue StandardError => e
42
- targets.map do |target|
43
- Bolt::Result.from_exception(target, e)
44
- end
45
- end
46
-
47
- def get(url)
48
- response = @http.get(url)
49
- if response.status != 200
50
- raise Bolt::Error.new(response.body['msg'], response.body['kind'], response.body['details'])
51
- end
52
- response.body
53
- end
54
-
55
- def post_command(url, body)
56
- response = @http.post(url, body)
57
- if response.status != 202
58
- raise Bolt::Error.new(response.body['msg'], response.body['kind'], response.body['details'])
59
- end
60
- response.body
61
- end
62
-
63
- def send_request(targets, task, arguments, options = {})
64
- description = options[:description]
65
- body = { task: task.name,
66
- environment: @environment,
67
- noop: arguments['_noop'],
68
- params: arguments.reject { |k, _| k.start_with?('_') },
69
- scope: {
70
- nodes: targets.map(&:host)
71
- } }
72
- body[:description] = description if description
73
- body[:plan_job] = @plan_job if @plan_job
74
-
75
- url = post_command('internal/plan_task', body).dig('job', 'id')
76
-
77
- job = get(url)
78
- until %w[stopped finished failed].include?(job['state'])
79
- sleep 1
80
- job = get(url)
81
- end
82
-
83
- get(job.dig('nodes', 'id'))['items']
84
- end
85
-
86
- def process_run_results(targets, results)
87
- targets_by_name = Hash[targets.map(&:host).zip(targets)]
88
- results.map do |node_result|
89
- target = targets_by_name[node_result['name']]
90
- state = node_result['state']
91
- result = node_result['result']
92
-
93
- # If it's finished or already has a proper error simply pass it to the
94
- # the result otherwise make sure an error is generated
95
- if state == 'finished' || (result && result['_error'])
96
- Bolt::Result.new(target, value: result)
97
- elsif state == 'skipped'
98
- Bolt::Result.new(
99
- target,
100
- value: { '_error' => {
101
- 'kind' => 'puppetlabs.tasks/skipped-node',
102
- 'msg' => "Node #{target.host} was skipped",
103
- 'details' => {}
104
- } }
105
- )
106
- else
107
- # Make a generic error with a unkown exit_code
108
- Bolt::Result.for_task(target, result.to_json, '', 'unknown')
109
- end
110
- end
111
- end
112
-
113
- def run_task(targets, task, arguments, options = {})
114
- run_task_job(targets, task, arguments, options)
115
- end
116
-
117
- def run_command(targets, command, options = {})
118
- params = { 'command' => command }
119
- run_task_job(targets, BOLT_COMMAND_TASK, params, options)
120
- end
121
-
122
- def run_script(targets, script, arguments, options = {})
123
- content = File.open(script, &:read)
124
- content = Base64.encode64(content)
125
- params = {
126
- 'content' => content,
127
- 'arguments' => arguments,
128
- 'name' => Pathname(script).basename.to_s
129
- }
130
- callback ||= proc {}
131
- results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
132
- results.map! { |result| unwrap_bolt_result(result.target, result) }
133
- results.each do |result|
134
- callback.call(type: :node_result, result: result)
135
- end
136
- end
137
-
138
- def pack(directory)
139
- # lazy-load expensive gem code
140
- require 'minitar'
141
- require 'zlib'
142
-
143
- start_time = Time.now
144
- io = StringIO.new
145
- output = Minitar::Output.new(Zlib::GzipWriter.new(io))
146
- Find.find(directory) do |file|
147
- next unless File.file?(file)
148
-
149
- tar_path = Pathname.new(file).relative_path_from(Pathname.new(directory))
150
- @logger.debug("Packing #{file} to #{tar_path}")
151
- stat = File.stat(file)
152
- content = File.binread(file)
153
- output.tar.add_file_simple(
154
- tar_path.to_s,
155
- data: content,
156
- size: content.size,
157
- mode: stat.mode & 0o777,
158
- mtime: stat.mtime
159
- )
160
- end
161
-
162
- duration = Time.now - start_time
163
- @logger.debug("Packed upload in #{duration * 1000} ms")
164
-
165
- output.close
166
- io.string
167
- ensure
168
- # Closes both tar and sgz.
169
- output&.close
170
- end
171
-
172
- def file_upload(targets, source, destination, options = {})
173
- stat = File.stat(source)
174
- content = if stat.directory?
175
- pack(source)
176
- else
177
- File.open(source, &:read)
178
- end
179
- content = Base64.encode64(content)
180
- mode = File.stat(source).mode
181
- params = {
182
- 'path' => destination,
183
- 'content' => content,
184
- 'mode' => mode,
185
- 'directory' => stat.directory?
186
- }
187
- results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options)
188
- results.map! do |result|
189
- if result.error_hash
190
- result
191
- else
192
- Bolt::Result.for_upload(result.target, source, destination)
193
- end
194
- end
195
- end
196
-
197
- # Unwraps any Sensitive data in an arguments Hash, so the plain-text is passed
198
- # to the Task/Script.
199
- #
200
- # This works on deeply nested data structures composed of Hashes, Arrays, and
201
- # and plain-old data types (int, string, etc).
202
- def unwrap_sensitive_args(arguments)
203
- # Skip this if Puppet isn't loaded
204
- return arguments unless defined?(Puppet::Pops::Types::PSensitiveType::Sensitive)
205
-
206
- case arguments
207
- when Array
208
- # iterate over the array, unwrapping all elements
209
- arguments.map { |x| unwrap_sensitive_args(x) }
210
- when Hash
211
- # iterate over the arguments hash and unwrap all keys and values
212
- arguments.each_with_object({}) { |(k, v), h|
213
- h[unwrap_sensitive_args(k)] = unwrap_sensitive_args(v)
214
- }
215
- when Puppet::Pops::Types::PSensitiveType::Sensitive
216
- # this value is Sensitive, unwrap it
217
- unwrap_sensitive_args(arguments.unwrap)
218
- else
219
- # unknown data type, just return it
220
- arguments
221
- end
222
- end
223
-
224
- # run_task generates a result that makes sense for a generic task which
225
- # needs to be unwrapped to extract stdout/stderr/exitcode.
226
- #
227
- def unwrap_bolt_result(target, result)
228
- if result.error_hash
229
- # something went wrong return the failure
230
- return result
231
- end
232
-
233
- Bolt::Result.for_command(target, result.value['stdout'], result.value['stderr'], result.value['exit_code'])
234
- end
235
-
236
- def connected?(targets)
237
- response = @http.post('inventory', nodes: targets.map(&:host))
238
- response.body['items'].all? { |node| node['connected'] }
239
- end
240
- end
241
- end
@@ -1,29 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-04/schema#",
3
- "title": "run_plan request",
4
- "description": "POST plan/run request schema",
5
- "type": "object",
6
- "properties": {
7
- "plan_name": {
8
- "type": "string",
9
- "description": "Name of the plan"
10
- },
11
- "job_id": {
12
- "type": "string",
13
- "description": "The job ID initialized in Orchestrator"
14
- },
15
- "environment": {
16
- "type": "string",
17
- "description": "Environment used for plan execution"
18
- },
19
- "description": {
20
- "type": "string",
21
- "description": "Describes this execution of the plan"
22
- },
23
- "params": {
24
- "type": "object",
25
- "description": "JSON formatted parameters to be provided to plan"
26
- }
27
- },
28
- "required": ["plan_name", "job_id", "params"]
29
- }