bolt 1.10.0 → 1.11.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: 7cdc9ce6823fb077d899fce7bfa0154f5de9a0a7e994116f879b4395f8f65f59
4
- data.tar.gz: 68ab9662c1613e93b1e8c51a6d5ce036de0e51e2961e759c25b67c37bb85c48b
3
+ metadata.gz: 29c062ea69471f56b51b9cef6ea2a3ba820773161753a13145879b7b941d2a32
4
+ data.tar.gz: 0f237d5aa389dee5e8a908f4fd52268d9fd397af25894a25adeeff0bc96d2ccc
5
5
  SHA512:
6
- metadata.gz: be80c448bea05fbfe6a210093fc94fe90ca2e9ad3f8c8deeba4431732fa6e5e5f10f969fa976e91be0c49b9bb56117d38a74ab2e1e4435128b7a6124b7cac913
7
- data.tar.gz: 6770076579d8c73e13536c2c9d2ec1c415b532b11d02f278a4f829a6f0c74455e98512717a06f9608afa258c309d3941e6d772fb99522a19e9e9c9416442ded2
6
+ metadata.gz: 79a0c7e11a5998175df643ed639f197f3e05c78895713562b220338b8c0df5f2fa1a506d5d4a7e063b38e314d4c046816cf7d4fd084296f313b3193b04b91c12
7
+ data.tar.gz: b92c3bb6f4e0fcc95895d22907c7c98e7cf5b4fef4385354dfd6a53c09817306d6d99aed1266beb1c0a65cbfb804567599c29c69a08790c3ba1d04ac5e3c7392
data/lib/bolt/cli.rb CHANGED
@@ -343,9 +343,7 @@ module Bolt
343
343
  end
344
344
 
345
345
  def list_tasks
346
- outputter.print_table(pal.list_tasks)
347
- outputter.print_message("\nUse `bolt task show <task-name>` to view "\
348
- "details and parameters for a specific task.")
346
+ outputter.print_tasks(pal.list_tasks, pal.list_modulepath)
349
347
  end
350
348
 
351
349
  def show_plan(plan_name)
@@ -353,9 +351,7 @@ module Bolt
353
351
  end
354
352
 
355
353
  def list_plans
356
- outputter.print_table(pal.list_plans)
357
- outputter.print_message("\nUse `bolt plan show <plan-name>` to view "\
358
- "details and parameters for a specific plan.")
354
+ outputter.print_plans(pal.list_plans, pal.list_modulepath)
359
355
  end
360
356
 
361
357
  def run_plan(plan_name, plan_arguments, nodes, options)
@@ -115,6 +115,13 @@ module Bolt
115
115
  )
116
116
  end
117
117
 
118
+ def print_tasks(tasks, modulepath)
119
+ print_table(tasks)
120
+ print_message("\nMODULEPATH:\n#{modulepath.join(':')}\n"\
121
+ "\nUse `bolt task show <task-name>` to view "\
122
+ "details and parameters for a specific task.")
123
+ end
124
+
118
125
  # @param [Hash] task A hash representing the task
119
126
  def print_task_info(task)
120
127
  # Building lots of strings...
@@ -177,6 +184,13 @@ module Bolt
177
184
  @stream.puts(plan_info)
178
185
  end
179
186
 
187
+ def print_plans(plans, modulepath)
188
+ print_table(plans)
189
+ print_message("\nMODULEPATH:\n#{modulepath.join(':')}\n"\
190
+ "\nUse `bolt plan show <plan-name>` to view "\
191
+ "details and parameters for a specific plan.")
192
+ end
193
+
180
194
  # @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
181
195
  def print_apply_result(apply_result)
182
196
  apply_result.each { |result| print_result(result) }
@@ -53,6 +53,10 @@ module Bolt
53
53
  @stream.puts task.to_json
54
54
  end
55
55
 
56
+ def print_tasks(tasks, modulepath)
57
+ print_table('tasks' => tasks, 'modulepath' => modulepath)
58
+ end
59
+
56
60
  def print_plan_info(plan)
57
61
  path = plan.delete('module')
58
62
  plan['module_dir'] = if path.start_with?(Bolt::PAL::MODULES_PATH)
@@ -63,6 +67,10 @@ module Bolt
63
67
  @stream.puts plan.to_json
64
68
  end
65
69
 
70
+ def print_plans(plans, modulepath)
71
+ print_table('plans' => plans, 'modulepath' => modulepath)
72
+ end
73
+
66
74
  def print_apply_result(apply_result)
67
75
  @stream.puts apply_result.to_json
68
76
  end
data/lib/bolt/pal.rb CHANGED
@@ -197,6 +197,10 @@ module Bolt
197
197
  end
198
198
  end
199
199
 
200
+ def list_modulepath
201
+ @modulepath - [BOLTLIB_PATH, MODULES_PATH]
202
+ end
203
+
200
204
  def parse_params(type, object_name, params)
201
205
  in_bolt_compiler do |compiler|
202
206
  if type == 'task'
@@ -110,7 +110,8 @@ module Bolt
110
110
  content = Base64.encode64(content)
111
111
  params = {
112
112
  'content' => content,
113
- 'arguments' => arguments
113
+ 'arguments' => arguments,
114
+ 'name' => Pathname(script).basename.to_s
114
115
  }
115
116
  callback ||= proc {}
116
117
  results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.10.0'
4
+ VERSION = '1.11.0'
5
5
  end
@@ -76,7 +76,12 @@ module BoltServer
76
76
 
77
77
  def validate
78
78
  required_keys.each do |k|
79
- next unless @data[k].nil?
79
+ # Handled nested config
80
+ if k.is_a?(Array)
81
+ next unless @data.dig(*k).nil?
82
+ else
83
+ next unless @data[k].nil?
84
+ end
80
85
  raise Bolt::ValidationError, "You must configure #{k} in #{@config_path}"
81
86
  end
82
87
 
@@ -3,10 +3,10 @@
3
3
  require 'sinatra'
4
4
  require 'bolt'
5
5
  require 'bolt/error'
6
- require 'bolt/executor'
7
6
  require 'bolt/inventory'
8
7
  require 'bolt/pal'
9
8
  require 'bolt/puppetdb'
9
+ require 'bolt/version'
10
10
  require 'plan_executor/applicator'
11
11
  require 'plan_executor/executor'
12
12
  require 'concurrent'
@@ -27,18 +27,24 @@ module PlanExecutor
27
27
  end
28
28
  end
29
29
 
30
- def initialize(modulepath, executor = nil)
31
- @schema = JSON.parse(File.read(File.join(__dir__, 'schemas', 'run_plan.json')))
32
- @worker = Concurrent::SingleThreadExecutor.new
30
+ def initialize(config)
31
+ conn_opts = { 'service-url' => config['orchestrator-url'],
32
+ 'cacert' => config['ssl-ca-cert'],
33
+ 'User-Agent' => "Bolt/#{Bolt::VERSION}" }
34
+ @client = OrchestratorClient.new(conn_opts, false)
33
35
 
34
- # Create a basic executor, leave concurrency up to Orchestrator.
35
- @executor = executor || PlanExecutor::Executor.new(0)
36
36
  # Use an empty inventory until we figure out where this data comes from.
37
37
  @inventory = Bolt::Inventory.new(nil)
38
- # TODO: what should max compiles be set to for apply?
39
- @pal = Bolt::PAL.new(modulepath, nil)
40
38
 
41
- @applicator = PlanExecutor::Applicator.new(@inventory, @executor, nil)
39
+ # PAL is not threadsafe. Part of the work of making the plan executor
40
+ # functional will be making changes to Puppet that remove the need for
41
+ # global Puppet state.
42
+ # https://github.com/puppetlabs/bolt/blob/master/lib/bolt/pal.rb#L166
43
+ @pal = Bolt::PAL.new(config['modulepath'], nil)
44
+
45
+ @schema = JSON.parse(File.read(File.join(__dir__, 'schemas', 'run_plan.json')))
46
+ @worker = Concurrent::SingleThreadExecutor.new
47
+ @modulepath = config['modulepath']
42
48
 
43
49
  super(nil)
44
50
  end
@@ -77,16 +83,18 @@ module PlanExecutor
77
83
  body = JSON.parse(request.body.read)
78
84
  error = validate_schema(@schema, body)
79
85
  return [400, error.to_json] unless error.nil?
80
-
81
86
  name = body['plan_name']
82
87
  # Errors if plan is not found
83
88
  @pal.get_plan_info(name)
84
89
 
90
+ executor = PlanExecutor::Executor.new(body['job_id'], @client)
91
+ applicator = PlanExecutor::Applicator.new(@inventory, executor, nil)
85
92
  params = body['params']
86
93
  # This provides a wait function, which promise doesn't
87
94
  result = Concurrent::Future.execute(executor: @worker) do
88
- # Stores result in result for testing
89
- @pal.run_plan(name, params, @executor, @inventory, puppetdb_client, @applicator)
95
+ pal_result = @pal.run_plan(name, params, executor, @inventory, puppetdb_client, applicator)
96
+ executor.finish_plan(pal_result)
97
+ pal_result
90
98
  end
91
99
 
92
100
  [200, { status: 'running' }.to_json]
@@ -7,7 +7,7 @@ require 'bolt/error'
7
7
  module PlanExecutor
8
8
  class Config < BoltServer::BaseConfig
9
9
  def config_keys
10
- super + %w[modulepath workers]
10
+ super + %w[modulepath workers orchestrator-url]
11
11
  end
12
12
 
13
13
  def defaults
@@ -21,6 +21,10 @@ module PlanExecutor
21
21
  'plan-executor'
22
22
  end
23
23
 
24
+ def required_keys
25
+ super + ['orchestrator-url']
26
+ end
27
+
24
28
  def load_env_config
25
29
  env_keys.each do |key|
26
30
  transformed_key = "BOLT_#{key.tr('-', '_').upcase}"
@@ -7,27 +7,26 @@ require 'logging'
7
7
  require 'set'
8
8
  require 'bolt/result'
9
9
  require 'bolt/config'
10
- require 'bolt/transport/api'
11
- require 'bolt/notifier'
12
10
  require 'bolt/result_set'
13
11
  require 'bolt/puppetdb'
12
+ require 'plan_executor/orch_client'
14
13
 
15
14
  module PlanExecutor
16
15
  class Executor
17
- attr_reader :noop, :transport
16
+ attr_reader :noop, :logger
17
+ attr_accessor :transport
18
18
 
19
- def initialize(noop = nil)
19
+ def initialize(job_id, client, noop = nil)
20
20
  @logger = Logging.logger[self]
21
21
  @plan_logging = false
22
22
  @noop = noop
23
23
  @logger.debug { "Started" }
24
- @notifier = Bolt::Notifier.new
25
- @transport = Bolt::Transport::Api.new
24
+ @transport = PlanExecutor::OrchClient.new(job_id, client, @logger)
26
25
  end
27
26
 
28
27
  # This handles running the job, catching errors, and turning the result
29
28
  # into a result set
30
- def execute(targets)
29
+ def as_resultset(targets)
31
30
  result_array = begin
32
31
  yield
33
32
  rescue StandardError => e
@@ -38,7 +37,7 @@ module PlanExecutor
38
37
  Bolt::ResultSet.new(result_array)
39
38
  end
40
39
 
41
- # TODO: Remove in favor of service logging
40
+ # BOLT-1098
42
41
  def log_action(description, targets)
43
42
  # When running a plan, info messages like starting a task are promoted to notice.
44
43
  log_method = @plan_logging ? :notice : :info
@@ -62,6 +61,7 @@ module PlanExecutor
62
61
  results
63
62
  end
64
63
 
64
+ # BOLT-1098
65
65
  def log_plan(plan_name)
66
66
  log_method = @plan_logging ? :notice : :info
67
67
  @logger.send(log_method, "Starting: plan #{plan_name}")
@@ -78,60 +78,48 @@ module PlanExecutor
78
78
  results
79
79
  end
80
80
 
81
- def run_command(targets, command, options = {}, &callback)
81
+ def run_command(targets, command, options = {})
82
82
  description = options.fetch('_description', "command '#{command}'")
83
83
  log_action(description, targets) do
84
- notify = proc { |event| @notifier.notify(callback, event) if callback }
85
-
86
- results = execute(targets) do
87
- @transport.batch_command(targets, command, options, &notify)
84
+ results = as_resultset(targets) do
85
+ @transport.run_command(targets, command, options)
88
86
  end
89
87
 
90
- @notifier.shutdown
91
88
  results
92
89
  end
93
90
  end
94
91
 
95
- def run_script(targets, script, arguments, options = {}, &callback)
92
+ def run_script(targets, script, arguments, options = {})
96
93
  description = options.fetch('_description', "script #{script}")
97
94
  log_action(description, targets) do
98
- notify = proc { |event| @notifier.notify(callback, event) if callback }
99
-
100
- results = execute(targets) do
101
- @transport.batch_script(targets, script, arguments, options, &notify)
95
+ results = as_resultset(targets) do
96
+ @transport.run_script(targets, script, arguments, options)
102
97
  end
103
98
 
104
- @notifier.shutdown
105
99
  results
106
100
  end
107
101
  end
108
102
 
109
- def run_task(targets, task, arguments, options = {}, &callback)
103
+ def run_task(targets, task, arguments, options = {})
110
104
  description = options.fetch('_description', "task #{task.name}")
111
105
  log_action(description, targets) do
112
- notify = proc { |event| @notifier.notify(callback, event) if callback }
113
-
114
106
  arguments['_task'] = task.name
115
107
 
116
- results = execute(targets) do
117
- @transport.batch_task(targets, task, arguments, options, &notify)
108
+ results = as_resultset(targets) do
109
+ @transport.run_task(targets, task, arguments, options)
118
110
  end
119
111
 
120
- @notifier.shutdown
121
112
  results
122
113
  end
123
114
  end
124
115
 
125
- def upload_file(targets, source, destination, options = {}, &callback)
116
+ def upload_file(targets, source, destination, options = {})
126
117
  description = options.fetch('_description', "file upload from #{source} to #{destination}")
127
118
  log_action(description, targets) do
128
- notify = proc { |event| @notifier.notify(callback, event) if callback }
129
-
130
- results = execute(targets) do
131
- @transport.batch_upload(targets, source, destination, options, &notify)
119
+ results = as_resultset(targets) do
120
+ @transport.file_upload(targets, source, destination, options)
132
121
  end
133
122
 
134
- @notifier.shutdown
135
123
  results
136
124
  end
137
125
  end
@@ -144,7 +132,7 @@ module PlanExecutor
144
132
  retry_interval: 1)
145
133
  log_action(description, targets) do
146
134
  begin
147
- wait_until(wait_time, retry_interval) { @transport.batch_connected?(targets) }
135
+ wait_until(wait_time, retry_interval) { @transport.connected?(targets) }
148
136
  targets.map { |target| Bolt::Result.new(target) }
149
137
  rescue TimeoutError => e
150
138
  targets.map { |target| Bolt::Result.from_exception(target, e) }
@@ -160,21 +148,6 @@ module PlanExecutor
160
148
  end
161
149
  end
162
150
 
163
- # Plan context doesn't make sense for most transports but it is tightly
164
- # coupled with the orchestrator transport since the transport behaves
165
- # differently when a plan is running. In order to limit how much this
166
- # pollutes the transport API we only handle the orchestrator transport here.
167
- # Since we callt this function without resolving targets this will result
168
- # in the orchestrator transport always being initialized during plan runs.
169
- # For now that's ok.
170
- #
171
- # In the future if other transports need this or if we want a plan stack
172
- # we'll need to refactor.
173
- def start_plan(plan_context)
174
- @transport.plan_context = plan_context
175
- @plan_logging = true
176
- end
177
-
178
151
  def finish_plan(plan_result)
179
152
  @transport.finish_plan(plan_result)
180
153
  end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanExecutor
4
+ class OrchClient
5
+ attr_reader :plan_job, :client
6
+
7
+ BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
8
+ BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
9
+ BOLT_UPLOAD_TASK = Struct.new(:name).new('bolt_shim::upload').freeze
10
+
11
+ def initialize(plan_job, client, logger)
12
+ @plan_job = plan_job
13
+ @client = client
14
+ @logger = logger
15
+ @client_url = client.config['service-url']
16
+ logger.debug("Creating orchestrator client for #{@client_url}")
17
+ @environment = 'production'
18
+ end
19
+
20
+ def finish_plan(plan_result)
21
+ result = @client.command.plan_finish(
22
+ plan_job: @plan_job,
23
+ result: plan_result.value || '',
24
+ status: plan_result.status
25
+ )
26
+ rescue StandardError => e
27
+ @logger.debug("Failed to finish plan on #{@client_url}: #{e.message}\nResult: #{result.to_json}")
28
+ end
29
+
30
+ def run_task_job(targets, task, arguments, options)
31
+ # unpack any Sensitive data
32
+ arguments = unwrap_sensitive_args(arguments)
33
+ results = send_request(targets, task, arguments, options)
34
+
35
+ process_run_results(targets, results)
36
+ rescue OrchestratorClient::ApiError => e
37
+ targets.map do |target|
38
+ Bolt::Result.new(target, error: e.data)
39
+ end
40
+ rescue StandardError => e
41
+ targets.map do |target|
42
+ Bolt::Result.from_exception(target, e)
43
+ end
44
+ end
45
+
46
+ def send_request(targets, task, arguments, options = {})
47
+ description = options['_description']
48
+ body = { task: task.name,
49
+ environment: @environment,
50
+ noop: arguments['_noop'],
51
+ params: arguments.reject { |k, _| k.start_with?('_') },
52
+ scope: {
53
+ nodes: targets.map(&:host)
54
+ } }
55
+ body[:description] = description if description
56
+ body[:plan_job] = @plan_job if @plan_job
57
+
58
+ @client.run_task(body)
59
+ end
60
+
61
+ def process_run_results(targets, results)
62
+ targets_by_name = Hash[targets.map(&:host).zip(targets)]
63
+ results.map do |node_result|
64
+ target = targets_by_name[node_result['name']]
65
+ state = node_result['state']
66
+ result = node_result['result']
67
+
68
+ # If it's finished or already has a proper error simply pass it to the
69
+ # the result otherwise make sure an error is generated
70
+ if state == 'finished' || (result && result['_error'])
71
+ Bolt::Result.new(target, value: result)
72
+ elsif state == 'skipped'
73
+ Bolt::Result.new(
74
+ target,
75
+ value: { '_error' => {
76
+ 'kind' => 'puppetlabs.tasks/skipped-node',
77
+ 'msg' => "Node #{target.host} was skipped",
78
+ 'details' => {}
79
+ } }
80
+ )
81
+ else
82
+ # Make a generic error with a unkown exit_code
83
+ Bolt::Result.for_task(target, result.to_json, '', 'unknown')
84
+ end
85
+ end
86
+ end
87
+
88
+ def run_task(targets, task, arguments, options = {})
89
+ run_task_job(targets, task, arguments, options)
90
+ end
91
+
92
+ def run_command(targets, command, options = {})
93
+ params = { 'command' => command }
94
+ run_task_job(targets, BOLT_COMMAND_TASK, params, options)
95
+ end
96
+
97
+ def run_script(targets, script, arguments, options = {})
98
+ content = File.open(script, &:read)
99
+ content = Base64.encode64(content)
100
+ params = {
101
+ 'content' => content,
102
+ 'arguments' => arguments
103
+ }
104
+ callback ||= proc {}
105
+ results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
106
+ results.map! { |result| unwrap_bolt_result(result.target, result) }
107
+ results.each do |result|
108
+ callback.call(type: :node_result, result: result)
109
+ end
110
+ end
111
+
112
+ def pack(directory)
113
+ start_time = Time.now
114
+ io = StringIO.new
115
+ output = Minitar::Output.new(Zlib::GzipWriter.new(io))
116
+ Find.find(directory) do |file|
117
+ next unless File.file?(file)
118
+
119
+ tar_path = Pathname.new(file).relative_path_from(Pathname.new(directory))
120
+ @logger.debug("Packing #{file} to #{tar_path}")
121
+ stat = File.stat(file)
122
+ content = File.binread(file)
123
+ output.tar.add_file_simple(
124
+ tar_path.to_s,
125
+ data: content,
126
+ size: content.size,
127
+ mode: stat.mode & 0o777,
128
+ mtime: stat.mtime
129
+ )
130
+ end
131
+
132
+ duration = Time.now - start_time
133
+ @logger.debug("Packed upload in #{duration * 1000} ms")
134
+
135
+ output.close
136
+ io.string
137
+ ensure
138
+ # Closes both tar and sgz.
139
+ output&.close
140
+ end
141
+
142
+ def file_upload(targets, source, destination, options = {})
143
+ stat = File.stat(source)
144
+ content = if stat.directory?
145
+ pack(source)
146
+ else
147
+ File.open(source, &:read)
148
+ end
149
+ content = Base64.encode64(content)
150
+ mode = File.stat(source).mode
151
+ params = {
152
+ 'path' => destination,
153
+ 'content' => content,
154
+ 'mode' => mode,
155
+ 'directory' => stat.directory?
156
+ }
157
+ results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options)
158
+ results.map! do |result|
159
+ if result.error_hash
160
+ result
161
+ else
162
+ Bolt::Result.for_upload(result.target, source, destination)
163
+ end
164
+ end
165
+ end
166
+
167
+ # Unwraps any Sensitive data in an arguments Hash, so the plain-text is passed
168
+ # to the Task/Script.
169
+ #
170
+ # This works on deeply nested data structures composed of Hashes, Arrays, and
171
+ # and plain-old data types (int, string, etc).
172
+ def unwrap_sensitive_args(arguments)
173
+ # Skip this if Puppet isn't loaded
174
+ return arguments unless defined?(Puppet::Pops::Types::PSensitiveType::Sensitive)
175
+
176
+ case arguments
177
+ when Array
178
+ # iterate over the array, unwrapping all elements
179
+ arguments.map { |x| unwrap_sensitive_args(x) }
180
+ when Hash
181
+ # iterate over the arguments hash and unwrap all keys and values
182
+ arguments.each_with_object({}) { |(k, v), h|
183
+ h[unwrap_sensitive_args(k)] = unwrap_sensitive_args(v)
184
+ }
185
+ when Puppet::Pops::Types::PSensitiveType::Sensitive
186
+ # this value is Sensitive, unwrap it
187
+ unwrap_sensitive_args(arguments.unwrap)
188
+ else
189
+ # unknown data type, just return it
190
+ arguments
191
+ end
192
+ end
193
+
194
+ # run_task generates a result that makes sense for a generic task which
195
+ # needs to be unwrapped to extract stdout/stderr/exitcode.
196
+ #
197
+ def unwrap_bolt_result(target, result)
198
+ if result.error_hash
199
+ # something went wrong return the failure
200
+ return result
201
+ end
202
+
203
+ Bolt::Result.for_command(target, result.value['stdout'], result.value['stderr'], result.value['exit_code'])
204
+ end
205
+
206
+ def connected?(targets)
207
+ nodes = @client.post('inventory', nodes: targets.map(&:host))
208
+ nodes['items'].all? { |node| node['connected'] }
209
+ end
210
+ end
211
+ 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.10.0
4
+ version: 1.11.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-01-16 00:00:00.000000000 Z
11
+ date: 2019-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -357,8 +357,6 @@ files:
357
357
  - lib/bolt/task.rb
358
358
  - lib/bolt/task/puppet_server.rb
359
359
  - lib/bolt/task/remote.rb
360
- - lib/bolt/transport/api.rb
361
- - lib/bolt/transport/api/connection.rb
362
360
  - lib/bolt/transport/base.rb
363
361
  - lib/bolt/transport/docker.rb
364
362
  - lib/bolt/transport/docker/connection.rb
@@ -396,6 +394,7 @@ files:
396
394
  - lib/plan_executor/applicator.rb
397
395
  - lib/plan_executor/config.rb
398
396
  - lib/plan_executor/executor.rb
397
+ - lib/plan_executor/orch_client.rb
399
398
  - lib/plan_executor/schemas/run_plan.json
400
399
  - libexec/apply_catalog.rb
401
400
  - libexec/bolt_catalog
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bolt/transport/api/connection'
4
-
5
- # A copy of the orchestrator transport which uses the api connection class
6
- # in order to bypass calling 'start_plan'
7
- module Bolt
8
- module Transport
9
- class Api < Orch
10
- def initialize(*args)
11
- super
12
- end
13
-
14
- def get_connection(conn_opts)
15
- key = Bolt::Transport::Api::Connection.get_key(conn_opts)
16
- unless (conn = @connections[key])
17
- @connections[key] = Bolt::Transport::Api::Connection.new(conn_opts, logger)
18
- conn = @connections[key]
19
- end
20
- conn
21
- end
22
-
23
- def batches(targets)
24
- targets.group_by { |target| Bolt::Transport::Api::Connection.get_key(target.options) }.values
25
- end
26
- end
27
- end
28
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This is a copy of the orchestrator transport connection, but without the
4
- # 'start_plan' call in the init function which is handled by the orchestrator
5
- # client
6
- module Bolt
7
- module Transport
8
- class Api < Orch
9
- class Connection
10
- attr_reader :logger, :key
11
-
12
- CONTEXT_KEYS = Set.new(%i[plan_name description params]).freeze
13
-
14
- def self.get_key(opts)
15
- [
16
- opts['service-url'],
17
- opts['task-environment'],
18
- opts['token-file']
19
- ].join('-')
20
- end
21
-
22
- def initialize(opts, logger)
23
- @logger = logger
24
- @key = self.class.get_key(opts)
25
- client_keys = %w[service-url token-file cacert]
26
- client_opts = client_keys.each_with_object({}) do |k, acc|
27
- acc[k] = opts[k] if opts.include?(k)
28
- end
29
- client_opts['User-Agent'] = "Bolt/#{VERSION}"
30
- logger.debug("Creating orchestrator client for #{client_opts}")
31
-
32
- @client = OrchestratorClient.new(client_opts, true)
33
- @environment = opts["task-environment"]
34
- end
35
-
36
- def finish_plan(plan_result)
37
- if @plan_job
38
- @client.command.plan_finish(
39
- plan_job: @plan_job,
40
- result: plan_result.value || '',
41
- status: plan_result.status
42
- )
43
- end
44
- end
45
-
46
- def build_request(targets, task, arguments, description = nil)
47
- body = { task: task.name,
48
- environment: @environment,
49
- noop: arguments['_noop'],
50
- params: arguments.reject { |k, _| k.start_with?('_') },
51
- scope: {
52
- nodes: targets.map(&:host)
53
- } }
54
- body[:description] = description if description
55
- body[:plan_job] = @plan_job if @plan_job
56
- body
57
- end
58
-
59
- def run_task(targets, task, arguments, options)
60
- body = build_request(targets, task, arguments, options['_description'])
61
- @client.run_task(body)
62
- end
63
-
64
- def query_inventory(targets)
65
- @client.post('inventory', nodes: targets.map(&:host))
66
- end
67
- end
68
- end
69
- end
70
- end