bolt 1.20.0 → 1.21.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: 12222d708d8694206992e7cb2e89271c4b94ee9a56c40800f723d4f88899ba2f
4
- data.tar.gz: 8206b12db4fdd4fc2503252b1671f97f34e4870533baa244accf8b8bb94214e6
3
+ metadata.gz: e5ccf0b9b2f554bd10a8f4b25eedf4049c6b2820925d614be61a20b29ce64f5e
4
+ data.tar.gz: 814f77cef6785b3c5978c5b1336f0e8eb6cced7e3e1eb8917c2cbc7c96955deb
5
5
  SHA512:
6
- metadata.gz: eb78a8b55b5490ca241c513a4be0be6b1ab062109c1895fcdcd11bf802c9540da4cfee2625b8b080821e20d8902795e3845244669cf2b9efe2e15be102bd8662
7
- data.tar.gz: ad3321dc6ab500619ddb65083e0af52ff73949fea3e0330da15d0f7c3641d08a0a7a8fd44fca7705063952a654f2ed32893520b3cdcff7bc73dcecc3e07c5d6c
6
+ metadata.gz: '058a15c710a702ea63e4f989b0f96efa3c2c54a074650e0609dd1157f4fe6818fcaa8f2aaadbf145dbf588411cecf0d686e07821d9ad66994cab59de7ec5a9b5'
7
+ data.tar.gz: 5292df40ecd9c3db055603aa279db60b2b80ce8ac416756d0c3b5e4fef7f072b7b766e78dd236f2630f00c71d3b27fc59b97681546eb680057e34df18da7dd36
@@ -9,6 +9,7 @@ Puppet::DataTypes.create_type('ResultSet') do
9
9
  count => Callable[[], Integer],
10
10
  empty => Callable[[], Boolean],
11
11
  error_set => Callable[[], ResultSet],
12
+ filter_set => Callable[[Callable], ResultSet],
12
13
  find => Callable[[String[1]], Optional[Variant[Result, ApplyResult]]],
13
14
  first => Callable[[], Optional[Variant[Result, ApplyResult]]],
14
15
  names => Callable[[], Array[String[1]]],
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # check if a file exists
4
+ Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFunction) do
5
+ # @param filename Absolute path or Puppet file path.
6
+ # @example Check a file on disk
7
+ # file::exists('/tmp/i_dumped_this_here')
8
+ # @example check a file from the modulepath
9
+ # file::exists('example/files/VERSION')
10
+ dispatch :exists do
11
+ scope_param
12
+ required_param 'String', :filename
13
+ return_type 'Boolean'
14
+ end
15
+
16
+ def exists(scope, filename)
17
+ found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
18
+ found && Puppet::FileSystem.exist?(found)
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # check if a file is readable
4
+ Puppet::Functions.create_function(:'file::readable', Puppet::Functions::InternalFunction) do
5
+ # @param filename Absolute path or Puppet file path.
6
+ # @example Check a file on disk
7
+ # file::readable('/tmp/i_dumped_this_here')
8
+ # @example check a file from the modulepath
9
+ # file::readable('example/files/VERSION')
10
+ dispatch :readable do
11
+ scope_param
12
+ required_param 'String', :filename
13
+ return_type 'Boolean'
14
+ end
15
+
16
+ def readable(scope, filename)
17
+ found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
18
+ found && File.readable?(found)
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Output a message for the user.
4
+ #
5
+ # This will print a message to stdout when using the human output format.
6
+ #
7
+ # **NOTE:** Not available in apply block
8
+ Puppet::Functions.create_function(:'out::message') do
9
+ # Output a message.
10
+ # @param message The message to output.
11
+ # @example Print a message
12
+ # out::message('Something went wrong')
13
+ dispatch :output_message do
14
+ param 'String', :message
15
+ return_type 'Undef'
16
+ end
17
+
18
+ def output_message(message)
19
+ unless Puppet[:tasks]
20
+ raise Puppet::ParseErrorWithIssue
21
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'out::message')
22
+ end
23
+
24
+ executor = Puppet.lookup(:bolt_executor)
25
+ executor.publish_event(type: :message, message: message)
26
+
27
+ nil
28
+ end
29
+ end
data/lib/bolt/cli.rb CHANGED
@@ -491,25 +491,7 @@ module Bolt
491
491
  raise Bolt::CLIError, "A #{type} must be specified"
492
492
  end
493
493
 
494
- stat = file_stat(path)
495
-
496
- if !stat.readable?
497
- raise Bolt::FileError.new("The #{type} '#{path}' is unreadable", path)
498
- elsif !stat.file? && (!allow_dir || !stat.directory?)
499
- expected = allow_dir ? 'file or directory' : 'file'
500
- raise Bolt::FileError.new("The #{type} '#{path}' is not a #{expected}", path)
501
- elsif stat.directory?
502
- Dir.foreach(path) do |file|
503
- next if %w[. ..].include?(file)
504
- validate_file(type, File.join(path, file), allow_dir)
505
- end
506
- end
507
- rescue Errno::ENOENT
508
- raise Bolt::FileError.new("The #{type} '#{path}' does not exist", path)
509
- end
510
-
511
- def file_stat(path)
512
- File.stat(path)
494
+ Bolt::Util.validate_file(type, path, allow_dir)
513
495
  end
514
496
 
515
497
  def rerun
@@ -56,6 +56,8 @@ module Bolt
56
56
  @disable_depth -= 1
57
57
  when :disable_default_output
58
58
  @disable_depth += 1
59
+ when :message
60
+ print_message_event(event)
59
61
  end
60
62
  end
61
63
 
@@ -334,6 +336,10 @@ module Bolt
334
336
  end
335
337
  end
336
338
 
339
+ def print_message_event(event)
340
+ print_message(event[:message])
341
+ end
342
+
337
343
  def fatal_error(err)
338
344
  @stream.puts(colorize(:red, err.message))
339
345
  if err.is_a? Bolt::RunFailure
@@ -346,10 +352,10 @@ module Bolt
346
352
  end
347
353
  end
348
354
  end
349
- end
350
355
 
351
- def print_message(message)
352
- @stream.puts(message)
356
+ def print_message(message)
357
+ @stream.puts(message)
358
+ end
353
359
  end
354
360
  end
355
361
  end
@@ -3,8 +3,19 @@
3
3
  module Bolt
4
4
  class Plugin
5
5
  class Puppetdb
6
+ class FactLookupError < Bolt::Error
7
+ def initialize(fact, err = nil)
8
+ m = String.new("Fact lookup '#{fact}' contains an invalid factname")
9
+ m << ": #{err}" unless err.nil?
10
+ super(m, 'bolt.plugin/fact-lookup-error')
11
+ end
12
+ end
13
+
14
+ TARGET_OPTS = %w[uri name config].freeze
15
+
6
16
  def initialize(pdb_client)
7
17
  @puppetdb_client = pdb_client
18
+ @logger = Logging.logger[self]
8
19
  end
9
20
 
10
21
  def name
@@ -15,9 +26,61 @@ module Bolt
15
26
  ['lookup_targets']
16
27
  end
17
28
 
29
+ def warn_missing_fact(certname, fact)
30
+ @logger.warn("Could not find fact #{fact} for node #{certname}")
31
+ end
32
+
33
+ def fact_path(raw_fact)
34
+ fact_path = raw_fact.split(".")
35
+ if fact_path[0] == 'facts'
36
+ fact_path.drop(1)
37
+ elsif fact_path == ['certname']
38
+ fact_path
39
+ else
40
+ raise FactLookupError.new(raw_fact, "fact lookups must start with 'facts.'")
41
+ end
42
+ end
43
+
18
44
  def lookup_targets(opts)
19
- nodes = @puppetdb_client.query_certnames(opts['query'])
20
- nodes.map { |certname| { 'uri' => certname } }
45
+ targets = @puppetdb_client.query_certnames(opts['query'])
46
+ facts = []
47
+
48
+ target_opts = opts.select { |k, _| TARGET_OPTS.include?(k) }
49
+ Bolt::Util.walk_vals(target_opts) do |value|
50
+ # This is done in parts instead of in place so that we only need to
51
+ # make one puppetDB query
52
+ if value.is_a?(String)
53
+ facts << fact_path(value)
54
+ end
55
+ value
56
+ end
57
+
58
+ facts.uniq!
59
+ # Returns {'mycertname' => [{'path' => ['nested', 'fact'], 'value' => val'}], ... }
60
+ fact_values = @puppetdb_client.fact_values(targets, facts)
61
+
62
+ targets.map do |certname|
63
+ target_data = fact_values[certname]
64
+ target = resolve_facts(target_opts, certname, target_data) || {}
65
+ target['uri'] = certname unless target['uri'] || target['name']
66
+
67
+ target
68
+ end
69
+ end
70
+
71
+ def resolve_facts(config, certname, target_data)
72
+ Bolt::Util.walk_vals(config) do |value|
73
+ if value.is_a?(String)
74
+ data = target_data&.detect { |d| d['path'] == fact_path(value) }
75
+ warn_missing_fact(certname, value) if data.nil?
76
+ # If there's no fact data this will be nil
77
+ data&.fetch('value', nil)
78
+ elsif value.is_a?(Array) || value.is_a?(Hash)
79
+ value
80
+ else
81
+ raise FactLookupError.new(value, "fact lookups must be a string")
82
+ end
83
+ end
21
84
  end
22
85
  end
23
86
  end
@@ -42,6 +42,22 @@ module Bolt
42
42
  end
43
43
  end
44
44
 
45
+ def fact_values(certnames = [], facts = [])
46
+ return {} if certnames.empty? || facts.empty?
47
+
48
+ certnames.uniq!
49
+ name_query = certnames.map { |c| ["=", "certname", c] }
50
+ name_query.insert(0, "or")
51
+
52
+ facts_query = facts.map { |f| ["=", "path", f] }
53
+ facts_query.insert(0, "or")
54
+
55
+ query = ['and', name_query, facts_query]
56
+ result = make_query(query, 'fact-contents')
57
+ result.map! { |h| h.delete_if { |k, _v| %w[environment name].include?(k) } }
58
+ result.group_by { |c| c['certname'] }
59
+ end
60
+
45
61
  def make_query(query, path = nil)
46
62
  body = JSON.generate(query: query)
47
63
  url = "#{uri}/pdb/query/v4"
@@ -30,6 +30,11 @@ module Bolt
30
30
  self
31
31
  end
32
32
 
33
+ def filter_set
34
+ filtered = @results.select { |r| yield r }
35
+ self.class.new(filtered)
36
+ end
37
+
33
38
  def result_hash
34
39
  @result_hash ||= @results.each_with_object({}) do |result, acc|
35
40
  acc[result.target.name] = result
@@ -8,7 +8,7 @@ module Bolt
8
8
  module Transport
9
9
  class Docker < Base
10
10
  def self.options
11
- %w[host service-url service-options tmpdir interpreters]
11
+ %w[host service-url service-options tmpdir interpreters shell-command tty]
12
12
  end
13
13
 
14
14
  def provided_features
@@ -56,9 +56,17 @@ module Bolt
56
56
  end
57
57
  end
58
58
 
59
- def run_command(target, command, _options = {})
59
+ def run_command(target, command, options = {})
60
+ if target.options['tty']
61
+ options[:Tty] = true
62
+ end
63
+ if target.options['shell-command'] && !target.options['shell-command'].empty?
64
+ # escape any double quotes in command
65
+ command = command.gsub('"', '\"')
66
+ command = "#{target.options['shell-command']} \" #{command}\""
67
+ end
60
68
  with_connection(target) do |conn|
61
- stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), {})
69
+ stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), options)
62
70
  Bolt::Result.for_command(target, stdout, stderr, exitcode, 'command', command)
63
71
  end
64
72
  end
@@ -30,6 +30,14 @@ module Bolt
30
30
 
31
31
  @logger = Logging.logger[@target.host]
32
32
  @transport_logger = transport_logger
33
+
34
+ if target.options['private-key']&.instance_of?(String)
35
+ begin
36
+ Bolt::Util.validate_file('ssh key', target.options['private-key'])
37
+ rescue Bolt::FileError => e
38
+ @logger.warn(e.msg)
39
+ end
40
+ end
33
41
  end
34
42
 
35
43
  PAGEANT_NAME = "Pageant\0".encode(Encoding::UTF_16LE)
data/lib/bolt/util.rb CHANGED
@@ -179,6 +179,29 @@ module Bolt
179
179
  end
180
180
  end
181
181
 
182
+ # This is stubbed for testing validate_file
183
+ def file_stat(path)
184
+ File.stat(File.expand_path(path))
185
+ end
186
+
187
+ def validate_file(type, path, allow_dir = false)
188
+ stat = file_stat(path)
189
+
190
+ if !stat.readable?
191
+ raise Bolt::FileError.new("The #{type} '#{path}' is unreadable", path)
192
+ elsif !stat.file? && (!allow_dir || !stat.directory?)
193
+ expected = allow_dir ? 'file or directory' : 'file'
194
+ raise Bolt::FileError.new("The #{type} '#{path}' is not a #{expected}", path)
195
+ elsif stat.directory?
196
+ Dir.foreach(path) do |file|
197
+ next if %w[. ..].include?(file)
198
+ validate_file(type, File.join(path, file), allow_dir)
199
+ end
200
+ end
201
+ rescue Errno::ENOENT
202
+ raise Bolt::FileError.new("The #{type} '#{path}' does not exist", path)
203
+ end
204
+
182
205
  # Returns true if windows false if not.
183
206
  def windows?
184
207
  !!File::ALT_SEPARATOR
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.20.0'
4
+ VERSION = '1.21.0'
5
5
  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.20.0
4
+ version: 1.21.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-05-16 00:00:00.000000000 Z
11
+ date: 2019-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -346,8 +346,11 @@ files:
346
346
  - bolt-modules/boltlib/types/targetspec.pp
347
347
  - bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb
348
348
  - bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb
349
+ - bolt-modules/file/lib/puppet/functions/file/exists.rb
349
350
  - bolt-modules/file/lib/puppet/functions/file/read.rb
351
+ - bolt-modules/file/lib/puppet/functions/file/readable.rb
350
352
  - bolt-modules/file/lib/puppet/functions/file/write.rb
353
+ - bolt-modules/out/lib/puppet/functions/out/message.rb
351
354
  - bolt-modules/system/lib/puppet/functions/system/env.rb
352
355
  - exe/bolt
353
356
  - exe/bolt-inventory-pdb