bolt 1.24.0 → 1.25.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: cc4db252a26ca2d1f05bddfa9b4affaece04baf3d2324e37b29e7f3579301640
4
- data.tar.gz: 963c29e99b6389113a6ed9e8bc1bbe5209d5b7f9a1d0492f0d56af5a88d25f17
3
+ metadata.gz: 22679235d7475d2cc80d765770e6b81b0ed0852d4563a889c00043fe61f359f1
4
+ data.tar.gz: a04c075cb1551ab90f6e6e9f0e6e02670b6081475a9d8f0d49fd9e7f046d0992
5
5
  SHA512:
6
- metadata.gz: 11d6586b4ee6b03471749c629fc1ac2c342025b707b5bf443044f7f01ee930d1ab1c678655e827452c800df149e998d0b256e4f11e5bae194cd88eba6eb5847b
7
- data.tar.gz: 9775e66c5a65810e72e129787976f98d7725f64a293ccf80237372c90b8db149ca94d991afbc0779d26dab228e3c9a72ac884ad32b1d180c33a03582c57aeab0
6
+ metadata.gz: 4633d131919822b23981f8533b580e727817c95aa78a1afc05a4ca66bd14e08f3fd8f9c116f89e8726e171616c5cee1138d27260972dfc5e0718a9cb7a6096fb
7
+ data.tar.gz: 6a1f67bf10a65e26cd56cc9a82ad050a9023d5738b5d129cf43e203a50c1403c7cef4dd67085774dfba1d853f10174983b8e09716cc0a9d8441a24c69c62fe8b
@@ -8,8 +8,8 @@
8
8
  Puppet::Functions.create_function(:catch_errors) do
9
9
  # @param error_types An array of error types to catch
10
10
  # @param block The block of steps to catch errors on
11
- # @return Undef If an error is raised in the block then the error will be
12
- # returned, otherwise the result will be returned
11
+ # @return If an error is raised in the block then the error will be returned,
12
+ # otherwise the result will be returned
13
13
  # @example Catch errors for a block
14
14
  # catch_errors() || {
15
15
  # run_command("whoami", $nodes)
@@ -26,6 +26,7 @@ Puppet::Functions.create_function(:catch_errors) do
26
26
  dispatch :catch_errors do
27
27
  optional_param 'Array[String[1]]', :error_types
28
28
  block_param 'Callable[0, 0]', :block
29
+ return_type 'Any'
29
30
  end
30
31
 
31
32
  def catch_errors(error_types = nil)
@@ -13,7 +13,7 @@ module Bolt
13
13
 
14
14
  DATA_KEYS = %w[name config facts vars features].freeze
15
15
  NODE_KEYS = DATA_KEYS + %w[alias uri]
16
- GROUP_KEYS = DATA_KEYS + %w[groups targets target-lookups]
16
+ GROUP_KEYS = DATA_KEYS + %w[groups targets]
17
17
  CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
18
18
 
19
19
  def initialize(data, plugins)
@@ -31,6 +31,12 @@ module Bolt
31
31
  raise ValidationError.new("Group name must be a String, not #{@name.inspect}", nil) unless @name.is_a?(String)
32
32
  raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ NAME_REGEX
33
33
 
34
+ # DEPRECATION : remove this before finalization
35
+ if data.key?('target-lookups')
36
+ msg = "'target-lookups' are no longer a separate key. Merge 'target-lookups' and 'targets' lists and replace 'plugin' with '_plugin'" # rubocop:disable Metrics/LineLength
37
+ raise ValidationError.new(msg, @name)
38
+ end
39
+
34
40
  unless (unexpected_keys = data.keys - GROUP_KEYS).empty?
35
41
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
36
42
  @logger.warn(msg)
@@ -42,8 +48,6 @@ module Bolt
42
48
 
43
49
  @config = config_only_plugin(fetch_value(data, 'config', Hash))
44
50
 
45
- @target_lookups = fetch_value(data, 'target-lookups', Array)
46
-
47
51
  unless (unexpected_keys = @config.keys - CONFIG_KEYS).empty?
48
52
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for group #{@name}"
49
53
  @logger.warn(msg)
@@ -61,8 +65,15 @@ module Bolt
61
65
  # resolved, and requires a depth-first traversal to categorize them.
62
66
  if target.is_a?(String)
63
67
  @name_or_alias << target
68
+ # Handle plugins at this level so that lookups cannot trigger recursive lookups
69
+ elsif target.is_a?(Hash)
70
+ if target.include?('_plugin')
71
+ lookup_targets(target)
72
+ else
73
+ add_target(target)
74
+ end
64
75
  else
65
- add_target(target)
76
+ raise ValidationError.new("Node entry must be a String or Hash, not #{target.class}", @name)
66
77
  end
67
78
  end
68
79
 
@@ -77,12 +88,9 @@ module Bolt
77
88
  raise ValidationError.new("Cannot set group #{key.inspect} with plugin", nil)
78
89
  end
79
90
  end
80
- if data.is_a? Hash
81
- data.each do |_k, v|
82
- validate_config_plugin(v, key, group_name)
83
- end
84
- elsif data.is_a? Array
85
- data.map { |v| validate_config_plugin(v, key, group_name) }
91
+
92
+ Bolt::Util.walk_vals(data, true) do |v|
93
+ validate_config_plugin(v, key, group_name)
86
94
  end
87
95
  end
88
96
  private :validate_config_plugin
@@ -91,11 +99,14 @@ module Bolt
91
99
  Bolt::Util.walk_vals(data) do |value|
92
100
  if value.is_a?(Hash) && value.include?('_plugin')
93
101
  unless (plugin = @plugins.by_name(value['_plugin']))
94
- raise ValidationError.new("unkown plugin: #{value['_plugin'].inspect}", nil)
102
+ raise ValidationError.new("Config lookup specifies an unknown plugin: #{value['_plugin'].inspect}", @name)
95
103
  end
96
- plugin.validate_inventory_config_lookup(value) if plugin.respond_to?(:validate_inventory_config_lookup)
104
+ unless plugin.hooks.include?('inventory_config')
105
+ raise ValidationError.new("#{plugin.name} does not support inventory_config.", @name)
106
+ end
107
+ plugin.validate_inventory_config(value) if plugin.respond_to?(:validate_inventory_config)
97
108
  Concurrent::Delay.new do
98
- plugin.inventory_config_lookup(value)
109
+ plugin.inventory_config(value)
99
110
  end
100
111
  else
101
112
  value
@@ -119,11 +130,13 @@ module Bolt
119
130
  end
120
131
 
121
132
  def add_target(target)
122
- # TODO: Do we want to accept strings from lookup_targets plugins? How should
123
- # they be handled?
133
+ # This check ensures target lookup plugins do not returns bare strings.
134
+ # Remove it if we decide to allows task plugins to return string node
135
+ # names.
124
136
  unless target.is_a?(Hash)
125
- raise ValidationError.new("Node entry must be a String or Hash, not #{target.class}", @name)
137
+ raise ValidationError.new("Node entry must be a Hash, not #{target.class}", @name)
126
138
  end
139
+ # This check prevents plugins from returning plugins
127
140
  raise ValidationError.new("Cannot set target with plugin", @name) if target.key?('_plugin')
128
141
  target.each do |k, v|
129
142
  next if k == 'config'
@@ -160,6 +173,7 @@ module Bolt
160
173
  end
161
174
 
162
175
  target['config'] = config_only_plugin(target['config'])
176
+
163
177
  unless target.include?('alias')
164
178
  return
165
179
  end
@@ -181,24 +195,16 @@ module Bolt
181
195
  end
182
196
  end
183
197
 
184
- def lookup_targets(plugins)
185
- @target_lookups.each do |lookup|
186
- unless lookup.is_a?(Hash)
187
- raise ValidationError.new("target-lookup is not a hash: #{lookup}", @name)
188
- end
189
- unless lookup['plugin']
190
- raise ValidationError.new("target-lookup does not specify a plugin: #{lookup}", @name)
191
- end
192
-
193
- unless (plugin = plugins.by_name(lookup['plugin']))
194
- raise ValidationError.new("target-lookup specifies an unkown plugin: '#{lookup['plugin']}'", @name)
195
- end
196
-
197
- targets = plugin.lookup_targets(lookup)
198
- targets.each { |target| add_target(target) }
198
+ def lookup_targets(lookup)
199
+ unless (plugin = @plugins.by_name(lookup['_plugin']))
200
+ raise ValidationError.new("Target lookup specifies an unknown plugin: '#{lookup['plugin']}'", @name)
201
+ end
202
+ unless plugin.hooks.include?('inventory_targets')
203
+ raise ValidationError.new("#{plugin.name} does not support inventory_targets.", @name)
199
204
  end
200
205
 
201
- @groups.each { |g| g.lookup_targets(plugins) }
206
+ targets = plugin.inventory_targets(lookup)
207
+ targets.each { |target| add_target(target) }
202
208
  end
203
209
 
204
210
  def data_merge(data1, data2)
@@ -22,7 +22,6 @@ module Bolt
22
22
  @target_vars = target_vars
23
23
  @target_facts = target_facts
24
24
  @target_features = target_features
25
- @groups.lookup_targets(plugins)
26
25
  @groups.resolve_aliases(@groups.target_aliases, @groups.target_names)
27
26
  collect_groups
28
27
  end
@@ -37,21 +37,7 @@ module Bolt
37
37
  end
38
38
 
39
39
  def handle_event(event)
40
- return unless enabled? || event[:type] == :enable_default_output
41
-
42
40
  case event[:type]
43
- when :node_start
44
- print_start(event[:target]) if @verbose
45
- when :node_result
46
- print_result(event[:result]) if @verbose
47
- when :step_start
48
- print_step_start(event) if plan_logging?
49
- when :step_finish
50
- print_step_finish(event) if plan_logging?
51
- when :plan_start
52
- print_plan_start(event)
53
- when :plan_finish
54
- print_plan_finish(event)
55
41
  when :enable_default_output
56
42
  @disable_depth -= 1
57
43
  when :disable_default_output
@@ -59,6 +45,23 @@ module Bolt
59
45
  when :message
60
46
  print_message_event(event)
61
47
  end
48
+
49
+ if enabled?
50
+ case event[:type]
51
+ when :node_start
52
+ print_start(event[:target]) if @verbose
53
+ when :node_result
54
+ print_result(event[:result]) if @verbose
55
+ when :step_start
56
+ print_step_start(event) if plan_logging?
57
+ when :step_finish
58
+ print_step_finish(event) if plan_logging?
59
+ when :plan_start
60
+ print_plan_start(event)
61
+ when :plan_finish
62
+ print_plan_finish(event)
63
+ end
64
+ end
62
65
  end
63
66
 
64
67
  def enabled?
@@ -7,7 +7,7 @@ module Bolt
7
7
  class Plugin
8
8
  class Pkcs7 < Bolt::Secret::Base
9
9
  def self.validate_config(config)
10
- known_keys = ['private-key', 'public-key', 'keysize']
10
+ known_keys = %w[private-key public-key keysize]
11
11
  known_keys.each do |key|
12
12
  unless key.is_a? String
13
13
  raise Bolt::ValidationError, "Invalid config for pkcs7 plugin: '#{key}' is not a String"
@@ -14,14 +14,14 @@ module Bolt
14
14
  end
15
15
 
16
16
  def hooks
17
- ['inventory_config_lookup']
17
+ ['inventory_config']
18
18
  end
19
19
 
20
- def validate_inventory_config_lookup(opts)
20
+ def validate_inventory_config(opts)
21
21
  raise Bolt::ValidationError, "Prompt requires a 'message'" unless opts['message']
22
22
  end
23
23
 
24
- def inventory_config_lookup(opts)
24
+ def inventory_config(opts)
25
25
  STDOUT.print "#{opts['message']}:"
26
26
  value = STDIN.noecho(&:gets).chomp
27
27
  STDOUT.puts
@@ -23,7 +23,7 @@ module Bolt
23
23
  end
24
24
 
25
25
  def hooks
26
- ['lookup_targets']
26
+ ['inventory_targets']
27
27
  end
28
28
 
29
29
  def warn_missing_fact(certname, fact)
@@ -41,7 +41,7 @@ module Bolt
41
41
  end
42
42
  end
43
43
 
44
- def lookup_targets(opts)
44
+ def inventory_targets(opts)
45
45
  targets = @puppetdb_client.query_certnames(opts['query'])
46
46
  facts = []
47
47
 
@@ -14,14 +14,14 @@ module Bolt
14
14
  end
15
15
 
16
16
  def hooks
17
- ['lookup_targets']
17
+ ['inventory_targets']
18
18
  end
19
19
 
20
20
  def warn_missing_property(name, property)
21
21
  @logger.warn("Could not find property #{property} of terraform resource #{name}")
22
22
  end
23
23
 
24
- def lookup_targets(opts)
24
+ def inventory_targets(opts)
25
25
  state = load_statefile(opts)
26
26
 
27
27
  resources = extract_resources(state)
@@ -4,7 +4,7 @@ module Bolt
4
4
  class Secret
5
5
  class Base
6
6
  def hooks
7
- %w[inventory_config_lookup encrypt decrypt create_keys]
7
+ %w[inventory_config secret_encrypt secret_decrypt secret_createkeys]
8
8
  end
9
9
 
10
10
  def encode(raw)
@@ -31,9 +31,9 @@ module Bolt
31
31
  raw, _plugin = decode(opts['encrypted-value'])
32
32
  decrypt_value(raw)
33
33
  end
34
- alias inventory_config_lookup secret_decrypt
34
+ alias inventory_config secret_decrypt
35
35
 
36
- def validate_inventory_config_lookup(opts)
36
+ def validate_inventory_config(opts)
37
37
  decode(opts['encrypted-value'])
38
38
  end
39
39
  end
data/lib/bolt/util.rb CHANGED
@@ -134,8 +134,8 @@ module Bolt
134
134
  # Accepts a Data object and returns a copy with all hash and array values
135
135
  # Arrays and hashes including the initial object are modified before
136
136
  # their descendants are.
137
- def walk_vals(data, &block)
138
- data = yield(data)
137
+ def walk_vals(data, skip_top = false, &block)
138
+ data = yield(data) unless skip_top
139
139
  if data.is_a? Hash
140
140
  map_vals(data) { |v| walk_vals(v, &block) }
141
141
  elsif data.is_a? Array
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.24.0'
4
+ VERSION = '1.25.0'
5
5
  end
@@ -6,31 +6,31 @@ require 'bolt/error'
6
6
  module BoltServer
7
7
  class BaseConfig
8
8
  def config_keys
9
- ['host', 'port', 'ssl-cert', 'ssl-key', 'ssl-ca-cert',
10
- 'ssl-cipher-suites', 'loglevel', 'logfile', 'whitelist']
9
+ %w[host port ssl-cert ssl-key ssl-ca-cert
10
+ ssl-cipher-suites loglevel logfile whitelist]
11
11
  end
12
12
 
13
13
  def env_keys
14
- ['ssl-cert', 'ssl-key', 'ssl-ca-cert', 'loglevel']
14
+ %w[ssl-cert ssl-key ssl-ca-cert loglevel]
15
15
  end
16
16
 
17
17
  def defaults
18
18
  { 'host' => '127.0.0.1',
19
19
  'loglevel' => 'notice',
20
- 'ssl-cipher-suites' => ['ECDHE-ECDSA-AES256-GCM-SHA384',
21
- 'ECDHE-RSA-AES256-GCM-SHA384',
22
- 'ECDHE-ECDSA-CHACHA20-POLY1305',
23
- 'ECDHE-RSA-CHACHA20-POLY1305',
24
- 'ECDHE-ECDSA-AES128-GCM-SHA256',
25
- 'ECDHE-RSA-AES128-GCM-SHA256',
26
- 'ECDHE-ECDSA-AES256-SHA384',
27
- 'ECDHE-RSA-AES256-SHA384',
28
- 'ECDHE-ECDSA-AES128-SHA256',
29
- 'ECDHE-RSA-AES128-SHA256'] }
20
+ 'ssl-cipher-suites' => %w[ECDHE-ECDSA-AES256-GCM-SHA384
21
+ ECDHE-RSA-AES256-GCM-SHA384
22
+ ECDHE-ECDSA-CHACHA20-POLY1305
23
+ ECDHE-RSA-CHACHA20-POLY1305
24
+ ECDHE-ECDSA-AES128-GCM-SHA256
25
+ ECDHE-RSA-AES128-GCM-SHA256
26
+ ECDHE-ECDSA-AES256-SHA384
27
+ ECDHE-RSA-AES256-SHA384
28
+ ECDHE-ECDSA-AES128-SHA256
29
+ ECDHE-RSA-AES128-SHA256] }
30
30
  end
31
31
 
32
32
  def ssl_keys
33
- ['ssl-cert', 'ssl-key', 'ssl-ca-cert']
33
+ %w[ssl-cert ssl-key ssl-ca-cert]
34
34
  end
35
35
 
36
36
  def required_keys
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "run_task request",
4
+ "description": "POST <transport>/run_task request schema",
5
+ "type": "object",
6
+ "properties": {
7
+ "task": { "$ref": "partial:task" },
8
+ "parameters": {
9
+ "type": "object",
10
+ "description": "JSON formatted parameters to be provided to task"
11
+ },
12
+ "target": { "$ref": "partial:target-any" }
13
+ },
14
+ "required": ["target", "task"],
15
+ "additionalProperties": false
16
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "partial:target-any",
3
+ "$schema": "http://json-schema.org/draft-04/schema",
4
+ "title": "Target information about where to run a bolt action, either over SSH or WinRM",
5
+ "type": "object",
6
+ "anyOf": [
7
+ { "$ref": "partial:target-ssh" },
8
+ { "$ref": "partial:target-winrm" }
9
+ ]
10
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "id": "partial:target-ssh",
3
+ "$schema": "http://json-schema.org/draft-04/schema",
4
+ "title": "Target information about where to run a bolt action over SSH",
5
+ "type": "object",
6
+ "properties": {
7
+ "hostname": {
8
+ "type": "string",
9
+ "description": "Target identifier"
10
+ },
11
+ "user": {
12
+ "type": "string",
13
+ "description": "Login user"
14
+ },
15
+ "password": {
16
+ "type": "string",
17
+ "description": "Password for SSH transport authentication"
18
+ },
19
+ "private-key-content": {
20
+ "type": "string",
21
+ "description": "Contents of private key for SSH"
22
+ },
23
+ "port": {
24
+ "type": "integer",
25
+ "description": "Connection port"
26
+ },
27
+ "connect-timeout": {
28
+ "type": "integer",
29
+ "description": "How long Bolt should wait when establishing connections"
30
+ },
31
+ "run-as-command": {
32
+ "type": "array",
33
+ "description": "Command elevate permissions",
34
+ "items": {
35
+ "type": "string"
36
+ }
37
+ },
38
+ "run-as": {
39
+ "type": "string",
40
+ "description": "A different user to run commands as after login"
41
+ },
42
+ "tmpdir": {
43
+ "type": "string",
44
+ "description": "The directory to upload and execute temporary files on the target"
45
+ },
46
+ "tty": {
47
+ "type": "boolean",
48
+ "description": "Should bolt use pseudo tty to meet sudoer restrictions"
49
+ },
50
+ "host-key-check": {
51
+ "type": "boolean",
52
+ "description": "Whether to perform host key validation when connecting over SSH"
53
+ },
54
+ "sudo-password": {
55
+ "type": "string",
56
+ "description": "Password to use when changing users via run-as"
57
+ },
58
+ "interpreters": {
59
+ "type": "object",
60
+ "description": "Map of file extensions to remote executable"
61
+ }
62
+ },
63
+ "oneOf": [
64
+ {
65
+ "required": [
66
+ "password"
67
+ ]
68
+ },
69
+ {
70
+ "required": [
71
+ "private-key-content"
72
+ ]
73
+ }
74
+ ],
75
+ "required": [
76
+ "hostname",
77
+ "user"
78
+ ],
79
+ "additionalProperties": false
80
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "id": "partial:target-winrm",
3
+ "$schema": "http://json-schema.org/draft-04/schema",
4
+ "title": "Target information about where to run a bolt action over WinRM",
5
+ "type": "object",
6
+ "properties": {
7
+ "hostname": {
8
+ "type": "string",
9
+ "description": "Target identifier"
10
+ },
11
+ "user": {
12
+ "type": "string",
13
+ "description": "Login user"
14
+ },
15
+ "password": {
16
+ "type": "string",
17
+ "description": "Password for WinRM transport authentication"
18
+ },
19
+ "port": {
20
+ "type": "integer",
21
+ "description": "Connection port"
22
+ },
23
+ "connect-timeout": {
24
+ "type": "integer",
25
+ "description": "How long Bolt should wait when establishing connections"
26
+ },
27
+ "tmpdir": {
28
+ "type": "string",
29
+ "description": "The directory to upload and execute temporary files on the target"
30
+ },
31
+ "ssl": {
32
+ "type": "boolean",
33
+ "description": "When true, Bolt will use https connections for WinRM"
34
+ },
35
+ "ssl-verify": {
36
+ "type": "boolean",
37
+ "description": "When true, verifies the targets certificate matches the cacert"
38
+ },
39
+ "cacert": {
40
+ "type": "string",
41
+ "description": "The path to the CA certificate"
42
+ },
43
+ "extensions": {
44
+ "type": "array",
45
+ "description": "List of file extensions that are accepted for scripts or tasks"
46
+ },
47
+ "interpreters": {
48
+ "type": "object",
49
+ "description": "Map of file extensions to remote executable"
50
+ },
51
+ "file-protocol": {
52
+ "type": "string",
53
+ "enum": ["winrm", "smb"],
54
+ "description": "Protocol for file transfer, WinRM or SMB"
55
+ },
56
+ "smb-port": {
57
+ "type": "integer",
58
+ "description": "Port for SMB protocol"
59
+ }
60
+ },
61
+ "required": ["hostname", "user", "password"],
62
+ "additionalProperties": false
63
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "file:task",
2
+ "id": "partial:task",
3
3
  "$schema": "http://json-schema.org/draft-04/schema#",
4
4
  "title": "Task",
5
5
  "description": "Task schema for bolt-server",
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema",
3
+ "title": "Action request with SSH transport",
4
+ "description": "POST /ssh/<action> request schema",
5
+ "type": "object",
6
+ "properties": {
7
+ "target": { "$ref": "partial:target-ssh" }
8
+ },
9
+ "required": ["target"]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema",
3
+ "title": "Action request with WinRM transport",
4
+ "description": "POST /winrm/<action> request schema",
5
+ "type": "object",
6
+ "properties": {
7
+ "target": { "$ref": "partial:target-winrm" }
8
+ },
9
+ "required": ["target"]
10
+ }
@@ -15,15 +15,23 @@ module BoltServer
15
15
  # This disables Sinatra's error page generation
16
16
  set :show_exceptions, false
17
17
 
18
+ # These partial schemas are reused to build multiple request schemas
19
+ PARTIAL_SCHEMAS = %w[target-any target-ssh target-winrm task].freeze
20
+
21
+ # These schemas combine shared schemas to describe client requests
22
+ REQUEST_SCHEMAS = %w[action-run_task transport-ssh transport-winrm].freeze
23
+
18
24
  def initialize(config)
19
25
  @config = config
20
- @schemas = {
21
- "ssh-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'ssh-run_task.json'))),
22
- "winrm-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'winrm-run_task.json')))
23
- }
24
- shared_schema = JSON::Schema.new(JSON.parse(File.read(File.join(__dir__, 'schemas', 'task.json'))),
25
- Addressable::URI.parse("file:task"))
26
- JSON::Validator.add_schema(shared_schema)
26
+ @schemas = Hash[REQUEST_SCHEMAS.map do |basename|
27
+ [basename, JSON.parse(File.read(File.join(__dir__, ['schemas', "#{basename}.json"])))]
28
+ end]
29
+
30
+ PARTIAL_SCHEMAS.each do |basename|
31
+ schema_content = JSON.parse(File.read(File.join(__dir__, ['schemas', 'partials', "#{basename}.json"])))
32
+ shared_schema = JSON::Schema.new(schema_content, Addressable::URI.parse("partial:#{basename}"))
33
+ JSON::Validator.add_schema(shared_schema)
34
+ end
27
35
 
28
36
  @executor = Bolt::Executor.new(0)
29
37
 
@@ -48,6 +56,19 @@ module BoltServer
48
56
  end
49
57
  end
50
58
 
59
+ def run_task(target, body)
60
+ error = validate_schema(@schemas["action-run_task"], body)
61
+ return [400, error.to_json] unless error.nil?
62
+
63
+ task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)
64
+ parameters = body['parameters'] || {}
65
+ results = @executor.run_task(target, task, parameters)
66
+
67
+ # Since this will only be on one node we can just return the first result
68
+ result = scrub_stack_trace(results.first.status_hash)
69
+ [200, result.to_json]
70
+ end
71
+
51
72
  get '/' do
52
73
  200
53
74
  end
@@ -67,14 +88,18 @@ module BoltServer
67
88
  raise 'Unexpected error'
68
89
  end
69
90
 
70
- post '/ssh/run_task' do
71
- content_type :json
91
+ ACTIONS = %w[run_task].freeze
72
92
 
93
+ post '/ssh/:action' do
94
+ not_found unless ACTIONS.include?(params[:action])
95
+
96
+ content_type :json
73
97
  body = JSON.parse(request.body.read)
74
- error = validate_schema(@schemas["ssh-run_task"], body)
98
+
99
+ error = validate_schema(@schemas["transport-ssh"], body)
75
100
  return [400, error.to_json] unless error.nil?
76
101
 
77
- opts = body['target']
102
+ opts = body['target'].clone
78
103
  if opts['private-key-content']
79
104
  opts['private-key'] = { 'key-data' => opts['private-key-content'] }
80
105
  opts.delete('private-key-content')
@@ -83,35 +108,23 @@ module BoltServer
83
108
 
84
109
  target = [Bolt::Target.new(body['target']['hostname'], opts)]
85
110
 
86
- task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)
87
-
88
- parameters = body['parameters'] || {}
89
-
90
- # Since this will only be on one node we can just return the first result
91
- results = @executor.run_task(target, task, parameters)
92
- result = scrub_stack_trace(results.first.status_hash)
93
- [200, result.to_json]
111
+ method(params[:action]).call(target, body)
94
112
  end
95
113
 
96
- post '/winrm/run_task' do
97
- content_type :json
114
+ post '/winrm/:action' do
115
+ not_found unless ACTIONS.include?(params[:action])
98
116
 
117
+ content_type :json
99
118
  body = JSON.parse(request.body.read)
100
- error = validate_schema(@schemas["winrm-run_task"], body)
119
+
120
+ error = validate_schema(@schemas["transport-winrm"], body)
101
121
  return [400, error.to_json] unless error.nil?
102
122
 
103
- opts = body['target'].merge('protocol' => 'winrm')
123
+ opts = body['target'].clone.merge('protocol' => 'winrm')
104
124
 
105
125
  target = [Bolt::Target.new(body['target']['hostname'], opts)]
106
126
 
107
- task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)
108
-
109
- parameters = body['parameters'] || {}
110
-
111
- # Since this will only be on one node we can just return the first result
112
- results = @executor.run_task(target, task, parameters)
113
- result = scrub_stack_trace(results.first.status_hash)
114
- [200, result.to_json]
127
+ method(params[:action]).call(target, body)
115
128
  end
116
129
 
117
130
  error 404 do
@@ -31,7 +31,7 @@ module BoltSpec
31
31
  end
32
32
 
33
33
  def parameters
34
- @invocation[:arguments] + @invocation[:options] if @invocation.include[:arguments]
34
+ @invocation[:params]
35
35
  end
36
36
 
37
37
  def result_for(target, stdout: '', stderr: '')
@@ -41,6 +41,7 @@ module BoltSpec
41
41
  # Public methods
42
42
 
43
43
  def with_params(params)
44
+ @invocation[:params] = params
44
45
  @invocation[:arguments] = params['arguments']
45
46
  @invocation[:options] = params.select { |k, _v| k.start_with?('_') }
46
47
  self
@@ -30,7 +30,7 @@ module BoltSpec
30
30
  end
31
31
 
32
32
  def parameters
33
- @invocation[:arguments] + @invocation[:options] if @invocation.include?(:arguments)
33
+ @invocation[:params]
34
34
  end
35
35
 
36
36
  # Allow any data.
@@ -43,6 +43,7 @@ module BoltSpec
43
43
  # Restricts the stub to only match invocations with certain parameters.
44
44
  # All parameters must match exactly.
45
45
  def with_params(params)
46
+ @invocation[:params] = params
46
47
  @invocation[:arguments] = params.reject { |k, _v| k.start_with?('_') }
47
48
  @invocation[:options] = params.select { |k, _v| k.start_with?('_') }
48
49
  self
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.24.0
4
+ version: 1.25.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-06-21 00:00:00.000000000 Z
11
+ date: 2019-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -430,9 +430,13 @@ files:
430
430
  - lib/bolt_server/base_config.rb
431
431
  - lib/bolt_server/config.rb
432
432
  - lib/bolt_server/file_cache.rb
433
- - lib/bolt_server/schemas/ssh-run_task.json
434
- - lib/bolt_server/schemas/task.json
435
- - lib/bolt_server/schemas/winrm-run_task.json
433
+ - lib/bolt_server/schemas/action-run_task.json
434
+ - lib/bolt_server/schemas/partials/target-any.json
435
+ - lib/bolt_server/schemas/partials/target-ssh.json
436
+ - lib/bolt_server/schemas/partials/target-winrm.json
437
+ - lib/bolt_server/schemas/partials/task.json
438
+ - lib/bolt_server/schemas/transport-ssh.json
439
+ - lib/bolt_server/schemas/transport-winrm.json
436
440
  - lib/bolt_server/transport_app.rb
437
441
  - lib/bolt_spec/plans.rb
438
442
  - lib/bolt_spec/plans/action_stubs.rb
@@ -1,79 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-04/schema#",
3
- "title": "SSH run_task request",
4
- "description": "POST ssh/run_task request schema",
5
- "type": "object",
6
- "properties": {
7
- "target": {
8
- "type": "object",
9
- "description": "Target information to run task on",
10
- "properties": {
11
- "hostname": {
12
- "type": "string",
13
- "description": "Target identifier"
14
- },
15
- "user": {
16
- "type": "string",
17
- "description": "Login user"
18
- },
19
- "password": {
20
- "type": "string",
21
- "description": "Password for SSH transport authentication"
22
- },
23
- "private-key-content": {
24
- "type": "string",
25
- "description": "Contents of private key for SSH"
26
- },
27
- "port": {
28
- "type": "integer",
29
- "description": "Connection port"
30
- },
31
- "connect-timeout": {
32
- "type": "integer",
33
- "description": "How long Bolt should wait when establishing connections"
34
- },
35
- "run-as-command": {
36
- "type": "array",
37
- "description": "Command elevate permissions",
38
- "items": { "type": "string" }
39
- },
40
- "run-as": {
41
- "type": "string",
42
- "description": "A different user to run commands as after login"
43
- },
44
- "tmpdir": {
45
- "type": "string",
46
- "description": "The directory to upload and execute temporary files on the target"
47
- },
48
- "tty": {
49
- "type": "boolean",
50
- "description": "Should bolt use pseudo tty to meet sudoer restrictions"
51
- },
52
- "host-key-check": {
53
- "type": "boolean",
54
- "description": "Whether to perform host key validation when connecting over SSH"
55
- },
56
- "sudo-password": {
57
- "type": "string",
58
- "description": "Password to use when changing users via run-as"
59
- },
60
- "interpreters": {
61
- "type": "object",
62
- "description": "Map of file extensions to remote executable"
63
- }
64
- },
65
- "oneOf": [
66
- { "required": ["password"] },
67
- { "required": ["private-key-content"] }
68
- ],
69
- "required": ["hostname", "user"],
70
- "additionalProperties": false
71
- },
72
- "task": { "$ref": "file:task"},
73
- "parameters": {
74
- "type": "object",
75
- "description": "JSON formatted parameters to be provided to task"
76
- }
77
- },
78
- "required": ["target", "task"]
79
- }
@@ -1,76 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-04/schema#",
3
- "title": "WinRm run_task request",
4
- "description": "POST winrm/run_task request schema",
5
- "type": "object",
6
- "properties": {
7
- "target": {
8
- "type": "object",
9
- "description": "Target information to run task on",
10
- "properties": {
11
- "hostname": {
12
- "type": "string",
13
- "description": "Target identifier"
14
- },
15
- "user": {
16
- "type": "string",
17
- "description": "Login user"
18
- },
19
- "password": {
20
- "type": "string",
21
- "description": "Password for SSH transport authentication"
22
- },
23
- "port": {
24
- "type": "integer",
25
- "description": "Connection port"
26
- },
27
- "connect-timeout": {
28
- "type": "integer",
29
- "description": "How long Bolt should wait when establishing connections"
30
- },
31
- "tmpdir": {
32
- "type": "string",
33
- "description": "The directory to upload and execute temporary files on the target"
34
- },
35
- "ssl": {
36
- "type": "boolean",
37
- "description": "When true, Bolt will use https connections for WinRM"
38
- },
39
- "ssl-verify": {
40
- "type": "boolean",
41
- "description": "When true, verifies the targets certificate matches the cacert"
42
- },
43
- "cacert": {
44
- "type": "string",
45
- "description": "The path to the CA certificate"
46
- },
47
- "extensions": {
48
- "type": "array",
49
- "description": "List of file extensions that are accepted for scripts or tasks"
50
- },
51
- "interpreters": {
52
- "type": "object",
53
- "description": "Map of file extensions to remote executable"
54
- },
55
- "file-protocol": {
56
- "type": "string",
57
- "enum": ["winrm", "smb"],
58
- "description": "Protocol for file transfer, WinRM or SMB"
59
- },
60
- "smb-port": {
61
- "type": "integer",
62
- "description": "Port for SMB protocol"
63
- }
64
- },
65
-
66
- "required": ["hostname", "user", "password"],
67
- "additionalProperties": false
68
- },
69
- "task": { "$ref": "file:task"},
70
- "parameters": {
71
- "type": "object",
72
- "description": "JSON formatted parameters to be provided to task"
73
- }
74
- },
75
- "required": ["target", "task"]
76
- }