bolt 2.7.0 → 2.8.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.

@@ -150,8 +150,14 @@ module Bolt
150
150
  end
151
151
  elsif err =~ /^#{@sudo_id}/
152
152
  if sudo_stdin
153
- stdin.write("#{sudo_stdin}\n")
154
- stdin.close
153
+ begin
154
+ stdin.write("#{sudo_stdin}\n")
155
+ stdin.close
156
+ # If a task has stdin as an input_method but doesn't actually read
157
+ # from stdin, the task may return and close the input stream before
158
+ # we finish writing
159
+ rescue Errno::EPIPE
160
+ end
155
161
  end
156
162
  ''
157
163
  else
@@ -399,8 +405,9 @@ module Bolt
399
405
  write_stream = []
400
406
  end
401
407
  end
402
- # If a task has stdin as an input_method but doesn't actually
403
- # read from stdin, the task may return and close the input stream
408
+ # If a task has stdin as an input_method but doesn't actually read
409
+ # from stdin, the task may return and close the input stream before
410
+ # we finish writing
404
411
  rescue Errno::EPIPE
405
412
  write_stream = []
406
413
  end
@@ -155,5 +155,9 @@ module Bolt
155
155
  self.class.equal?(other.class) && @name == other.name
156
156
  end
157
157
  alias == eql?
158
+
159
+ def hash
160
+ @name.hash
161
+ end
158
162
  end
159
163
  end
@@ -32,7 +32,7 @@ module Bolt
32
32
  # Transports that need their own batching, like the Orch transport, can
33
33
  # instead override the batches() method to split Targets into sets that can
34
34
  # be executed together, and override the batch_task() and related methods
35
- # to execute a batch of nodes. In that case, those Transports should accept
35
+ # to execute a batch of targets. In that case, those Transports should accept
36
36
  # a block argument and call it with a :node_start event for each Target
37
37
  # before executing, and a :node_result event for each Target after
38
38
  # execution.
@@ -90,12 +90,12 @@ module Bolt
90
90
  # case and raises an error if it's not.
91
91
  def assert_batch_size_one(method, targets)
92
92
  if targets.length > 1
93
- message = "#{self.class.name} must implement #{method} to support batches (got #{targets.length} nodes)"
93
+ message = "#{self.class.name} must implement #{method} to support batches (got #{targets.length} targets)"
94
94
  raise NotImplementedError, message
95
95
  end
96
96
  end
97
97
 
98
- # Runs the given task on a batch of nodes.
98
+ # Runs the given task on a batch of targets.
99
99
  #
100
100
  # The default implementation only supports batches of size 1 and will fail otherwise.
101
101
  #
@@ -104,12 +104,28 @@ module Bolt
104
104
  assert_batch_size_one("batch_task()", targets)
105
105
  target = targets.first
106
106
  with_events(target, callback, 'task') do
107
- @logger.debug { "Running task run '#{task}' on #{target.safe_name}" }
107
+ @logger.debug { "Running task '#{task.name}' on #{target.safe_name}" }
108
108
  run_task(target, task, arguments, options)
109
109
  end
110
110
  end
111
111
 
112
- # Runs the given command on a batch of nodes.
112
+ # Runs the given task on a batch of targets with variable parameters.
113
+ #
114
+ # The default implementation only supports batches of size 1 and will fail otherwise.
115
+ #
116
+ # Transports may override this method to implment their own batch processing.
117
+ def batch_task_with(targets, task, target_mapping, options = {}, &callback)
118
+ assert_batch_size_one("batch_task_with()", targets)
119
+ target = targets.first
120
+ arguments = target_mapping[target]
121
+
122
+ with_events(target, callback, 'task') do
123
+ @logger.debug { "Running task '#{task.name}' on #{target.safe_name} with '#{arguments.to_json}'" }
124
+ run_task(target, task, arguments, options)
125
+ end
126
+ end
127
+
128
+ # Runs the given command on a batch of targets.
113
129
  #
114
130
  # The default implementation only supports batches of size 1 and will fail otherwise.
115
131
  #
@@ -123,7 +139,7 @@ module Bolt
123
139
  end
124
140
  end
125
141
 
126
- # Runs the given script on a batch of nodes.
142
+ # Runs the given script on a batch of targets.
127
143
  #
128
144
  # The default implementation only supports batches of size 1 and will fail otherwise.
129
145
  #
@@ -137,7 +153,7 @@ module Bolt
137
153
  end
138
154
  end
139
155
 
140
- # Uploads the given source file to the destination location on a batch of nodes.
156
+ # Uploads the given source file to the destination location on a batch of targets.
141
157
  #
142
158
  # The default implementation only supports batches of size 1 and will fail otherwise.
143
159
  #
@@ -157,7 +173,7 @@ module Bolt
157
173
  end
158
174
 
159
175
  # Split the given list of targets into a list of batches. The default
160
- # implementation returns single-node batches.
176
+ # implementation returns single-target batches.
161
177
  #
162
178
  # Transports may override this method, and the corresponding batch_*
163
179
  # methods, to implement their own batch processing.
@@ -210,6 +210,10 @@ module Bolt
210
210
  end
211
211
  end
212
212
 
213
+ def batch_task_with(_targets, _task, _target_mapping, _options = {})
214
+ raise NotImplementedError, "pcp transport does not support run_task_with()"
215
+ end
216
+
213
217
  def batch_connected?(targets)
214
218
  resp = get_connection(targets.first.options).query_inventory(targets)
215
219
  resp['items'].all? { |node| node['connected'] }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.7.0'
4
+ VERSION = '2.8.0'
5
5
  end
@@ -138,7 +138,7 @@ module BoltSpec
138
138
  # Override in your tests
139
139
  def config
140
140
  @config ||= begin
141
- conf = Bolt::Config.new(Bolt::Boltdir.new('.'), {})
141
+ conf = Bolt::Config.new(Bolt::Project.new('.'), {})
142
142
  conf.modulepath = [modulepath].flatten
143
143
  conf
144
144
  end
@@ -152,7 +152,7 @@ module BoltSpec
152
152
  end
153
153
 
154
154
  def pal
155
- @pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
155
+ @pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config, config.project.resource_types)
156
156
  end
157
157
 
158
158
  BoltSpec::Plans::MOCKED_ACTIONS.each do |action|
@@ -219,7 +219,7 @@ module BoltSpec
219
219
  end
220
220
 
221
221
  def run_plan(name, params)
222
- pal = Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
222
+ pal = Bolt::PAL.new(config.modulepath, config.hiera_config, config.project.resource_types)
223
223
  result = executor.with_plan_allowed_exec(name, params) do
224
224
  pal.run_plan(name, params, executor, inventory, puppetdb_client)
225
225
  end
@@ -126,25 +126,25 @@ module BoltSpec
126
126
  end
127
127
 
128
128
  class BoltRunner
129
- # Creates a temporary boltdir so no settings are picked up
130
- # WARNING: puppetdb config and orch config which do not use the boltdir may
129
+ # Creates a temporary project so no settings are picked up
130
+ # WARNING: puppetdb config and orch config which do not use the project may
131
131
  # still be loaded
132
132
  def self.with_runner(config_data, inventory_data)
133
- Dir.mktmpdir do |boltdir_path|
134
- runner = new(Bolt::Util.deep_clone(config_data), Bolt::Util.deep_clone(inventory_data), boltdir_path)
133
+ Dir.mktmpdir do |project_path|
134
+ runner = new(Bolt::Util.deep_clone(config_data), Bolt::Util.deep_clone(inventory_data), project_path)
135
135
  yield runner
136
136
  end
137
137
  end
138
138
 
139
- def initialize(config_data, inventory_data, boltdir_path)
139
+ def initialize(config_data, inventory_data, project_path)
140
140
  @config_data = config_data || {}
141
141
  @inventory_data = inventory_data || {}
142
- @boltdir_path = boltdir_path
142
+ @project_path = project_path
143
143
  @analytics = Bolt::Analytics::NoopClient.new
144
144
  end
145
145
 
146
146
  def config
147
- @config ||= Bolt::Config.new(Bolt::Boltdir.new(@boltdir_path), @config_data)
147
+ @config ||= Bolt::Config.new(Bolt::Project.new(@project_path), @config_data)
148
148
  end
149
149
 
150
150
  def inventory
@@ -165,7 +165,7 @@ module BoltSpec
165
165
  def pal
166
166
  @pal ||= Bolt::PAL.new(config.modulepath,
167
167
  config.hiera_config,
168
- config.boltdir.resource_types,
168
+ config.project.resource_types,
169
169
  config.compile_concurrency)
170
170
  end
171
171
 
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: 2.7.0
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-27 00:00:00.000000000 Z
11
+ date: 2020-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -184,7 +184,7 @@ dependencies:
184
184
  requirements:
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: 6.11.0
187
+ version: 6.15.0
188
188
  - - "<"
189
189
  - !ruby/object:Gem::Version
190
190
  version: '7'
@@ -194,7 +194,7 @@ dependencies:
194
194
  requirements:
195
195
  - - ">="
196
196
  - !ruby/object:Gem::Version
197
- version: 6.11.0
197
+ version: 6.15.0
198
198
  - - "<"
199
199
  - !ruby/object:Gem::Version
200
200
  version: '7'
@@ -410,6 +410,7 @@ files:
410
410
  - bolt-modules/boltlib/lib/puppet/functions/run_plan.rb
411
411
  - bolt-modules/boltlib/lib/puppet/functions/run_script.rb
412
412
  - bolt-modules/boltlib/lib/puppet/functions/run_task.rb
413
+ - bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb
413
414
  - bolt-modules/boltlib/lib/puppet/functions/set_config.rb
414
415
  - bolt-modules/boltlib/lib/puppet/functions/set_feature.rb
415
416
  - bolt-modules/boltlib/lib/puppet/functions/set_var.rb
@@ -438,7 +439,6 @@ files:
438
439
  - lib/bolt/apply_result.rb
439
440
  - lib/bolt/apply_target.rb
440
441
  - lib/bolt/bolt_option_parser.rb
441
- - lib/bolt/boltdir.rb
442
442
  - lib/bolt/catalog.rb
443
443
  - lib/bolt/catalog/logging.rb
444
444
  - lib/bolt/cli.rb
@@ -484,10 +484,10 @@ files:
484
484
  - lib/bolt/plugin.rb
485
485
  - lib/bolt/plugin/env_var.rb
486
486
  - lib/bolt/plugin/module.rb
487
- - lib/bolt/plugin/pkcs7.rb
488
487
  - lib/bolt/plugin/prompt.rb
489
488
  - lib/bolt/plugin/puppetdb.rb
490
489
  - lib/bolt/plugin/task.rb
490
+ - lib/bolt/project.rb
491
491
  - lib/bolt/puppetdb.rb
492
492
  - lib/bolt/puppetdb/client.rb
493
493
  - lib/bolt/puppetdb/config.rb
@@ -496,7 +496,6 @@ files:
496
496
  - lib/bolt/result.rb
497
497
  - lib/bolt/result_set.rb
498
498
  - lib/bolt/secret.rb
499
- - lib/bolt/secret/base.rb
500
499
  - lib/bolt/shell.rb
501
500
  - lib/bolt/shell/bash.rb
502
501
  - lib/bolt/shell/bash/tmpdir.rb
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'pathname'
4
-
5
- module Bolt
6
- class Boltdir
7
- BOLTDIR_NAME = 'Boltdir'
8
-
9
- attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config,
10
- :puppetfile, :rerunfile, :type, :resource_types
11
-
12
- def self.default_boltdir
13
- Boltdir.new(File.join('~', '.puppetlabs', 'bolt'), 'user')
14
- end
15
-
16
- # Search recursively up the directory hierarchy for the Boltdir. Look for a
17
- # directory called Boltdir or a file called bolt.yaml (for a control repo
18
- # type Boltdir). Otherwise, repeat the check on each directory up the
19
- # hierarchy, falling back to the default if we reach the root.
20
- def self.find_boltdir(dir)
21
- dir = Pathname.new(dir)
22
- if (dir + BOLTDIR_NAME).directory?
23
- new(dir + BOLTDIR_NAME, 'embedded')
24
- elsif (dir + 'bolt.yaml').file?
25
- new(dir, 'local')
26
- elsif dir.root?
27
- default_boltdir
28
- else
29
- find_boltdir(dir.parent)
30
- end
31
- end
32
-
33
- def initialize(path, type = 'option')
34
- @path = Pathname.new(path).expand_path
35
- @config_file = @path + 'bolt.yaml'
36
- @inventory_file = @path + 'inventory.yaml'
37
- @modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
38
- @hiera_config = @path + 'hiera.yaml'
39
- @puppetfile = @path + 'Puppetfile'
40
- @rerunfile = @path + '.rerun.json'
41
- @resource_types = @path + '.resource_types'
42
- @type = type
43
- end
44
-
45
- def to_s
46
- @path.to_s
47
- end
48
-
49
- def eql?(other)
50
- path == other.path
51
- end
52
- alias == eql?
53
- end
54
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bolt/secret/base'
4
- require 'fileutils'
5
-
6
- module Bolt
7
- class Plugin
8
- class Pkcs7 < Bolt::Secret::Base
9
- def self.validate_config(config = {})
10
- known_keys = %w[private-key public-key keysize]
11
- known_keys.each do |key|
12
- unless key.is_a? String
13
- raise Bolt::ValidationError, "Invalid config for pkcs7 plugin: '#{key}' is not a String"
14
- end
15
- end
16
-
17
- config.each_key do |key|
18
- unless known_keys.include?(key)
19
- raise Bolt::ValidationError, "Unpexpected key in pkcs7 plugin config: #{key}"
20
- end
21
- end
22
- end
23
-
24
- def name
25
- 'pkcs7'
26
- end
27
-
28
- def initialize(config:, context:, **_opts)
29
- self.class.validate_config(config)
30
- require 'openssl'
31
- @context = context
32
- @options = config || {}
33
- @logger = Logging.logger[self]
34
- end
35
-
36
- def boltdir
37
- @context.boltdir
38
- end
39
-
40
- def private_key_path
41
- path = @options['private-key'] || 'keys/private_key.pkcs7.pem'
42
- path = File.expand_path(path, boltdir)
43
- @logger.debug("Using private-key: #{path}")
44
- path
45
- end
46
-
47
- def private_key
48
- @private_key ||= OpenSSL::PKey::RSA.new(File.read(private_key_path))
49
- end
50
-
51
- def public_key_path
52
- path = @options['public-key'] || 'keys/public_key.pkcs7.pem'
53
- path = File.expand_path(path, boltdir)
54
- @logger.debug("Using public-key: #{path}")
55
- path
56
- end
57
-
58
- def public_key
59
- @public_key ||= OpenSSL::X509::Certificate.new(File.read(public_key_path))
60
- end
61
-
62
- def keysize
63
- @options['keysize'] || 2048
64
- end
65
-
66
- # The following implementations are intended to be compatible with hiera-eyaml
67
- def encrypt_value(plaintext)
68
- cipher = OpenSSL::Cipher::AES.new(256, :CBC)
69
- OpenSSL::PKCS7.encrypt([public_key], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der
70
- end
71
-
72
- def decrypt_value(ciphertext)
73
- pkcs7 = OpenSSL::PKCS7.new(ciphertext)
74
- pkcs7.decrypt(private_key, public_key)
75
- end
76
-
77
- def secret_createkeys
78
- key = OpenSSL::PKey::RSA.new(keysize)
79
-
80
- cert = OpenSSL::X509::Certificate.new
81
- cert.subject = OpenSSL::X509::Name.parse('/')
82
- cert.serial = 1
83
- cert.version = 2
84
- cert.not_before = Time.now
85
- cert.not_after = Time.now + 50 * 365 * 24 * 60 * 60
86
- cert.public_key = key.public_key
87
- cert.sign(key, OpenSSL::Digest.new('SHA512'))
88
-
89
- @logger.warn("Overwriting private-key '#{private_key_path}'") if File.exist?(private_key_path)
90
- @logger.warn("Overwriting public-key '#{public_key_path}'") if File.exist?(public_key_path)
91
-
92
- private_keydir = File.dirname(private_key_path)
93
- FileUtils.mkdir_p(private_keydir) unless File.exist?(private_keydir)
94
- FileUtils.touch(private_key_path)
95
- File.chmod(0o600, private_key_path)
96
- File.write(private_key_path, key.to_pem)
97
-
98
- public_keydir = File.dirname(public_key_path)
99
- FileUtils.mkdir_p(public_keydir) unless File.exist?(public_keydir)
100
- File.write(public_key_path, cert.to_pem)
101
- end
102
- end
103
- end
104
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Bolt
4
- class Secret
5
- class Base
6
- def hooks
7
- %i[resolve_reference secret_encrypt secret_decrypt secret_createkeys validate_resolve_reference]
8
- end
9
-
10
- def encode(raw)
11
- coded = Base64.encode64(raw).strip
12
- "ENC[#{name.upcase},#{coded}]"
13
- end
14
-
15
- def decode(code)
16
- format = %r{\AENC\[(?<plugin>\w+),(?<encoded>[\w\s+-=/]+)\]\s*\z}
17
- match = format.match(code)
18
-
19
- raise Bolt::ValidationError, "Could not parse as an encrypted value: #{code}" unless match
20
-
21
- raw = Base64.decode64(match[:encoded])
22
- [raw, match[:plugin]]
23
- end
24
-
25
- def secret_encrypt(opts)
26
- encrypted = encrypt_value(opts['plaintext_value'])
27
- encode(encrypted)
28
- end
29
-
30
- def secret_decrypt(opts)
31
- raw, _plugin = decode(opts['encrypted_value'])
32
- decrypt_value(raw)
33
- end
34
- alias resolve_reference secret_decrypt
35
-
36
- def validate_resolve_reference(opts)
37
- decode(opts['encrypted_value'])
38
- end
39
- end
40
- end
41
- end