bolt 0.14.0 → 0.15.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.

data/lib/bolt/executor.rb CHANGED
@@ -1,9 +1,11 @@
1
- require 'bolt/logger'
2
1
  require 'json'
3
2
  require 'concurrent'
3
+ require 'logging'
4
4
  require 'bolt/result'
5
5
  require 'bolt/config'
6
6
  require 'bolt/notifier'
7
+ require 'bolt/node'
8
+ require 'bolt/result_set'
7
9
 
8
10
  module Bolt
9
11
  class Executor
@@ -11,20 +13,26 @@ module Bolt
11
13
 
12
14
  def initialize(config = Bolt::Config.new, noop = nil, plan_logging = false)
13
15
  @config = config
14
- @logger = Logger.get_logger(progname: 'executor')
16
+ @logger = Logging.logger[self]
17
+
18
+ # If a specific elevated log level has been requested, honor that.
19
+ # Otherwise, escalate the log level to "info" if running in plan mode, so
20
+ # that certain progress messages will be visible.
21
+ default_log_level = plan_logging ? :info : :notice
22
+ @logger.level = @config[:log_level] || default_log_level
15
23
  @noop = noop
16
24
  @notifier = Bolt::Notifier.new
17
- @plan_logging = plan_logging
18
25
  end
19
26
 
20
- def from_uris(nodes)
21
- nodes.map do |node|
22
- Bolt::Node.from_uri(node, config: @config)
27
+ def from_targets(targets)
28
+ targets.map do |target|
29
+ Bolt::Node.from_target(target, config: @config)
23
30
  end
24
31
  end
32
+ private :from_targets
25
33
 
26
34
  def on(nodes, callback = nil)
27
- results = Concurrent::Map.new
35
+ results = Concurrent::Array.new
28
36
 
29
37
  poolsize = [nodes.length, @config[:concurrency]].min
30
38
  pool = Concurrent::FixedThreadPool.new(poolsize)
@@ -38,11 +46,11 @@ module Bolt
38
46
  pool.post do
39
47
  result =
40
48
  begin
41
- @notifier.notify(callback, node, type: :node_start) if callback
49
+ @notifier.notify(callback, type: :node_start, target: node.target) if callback
42
50
  node.connect
43
51
  yield node
44
52
  rescue StandardError => ex
45
- Bolt::Result.from_exception(ex)
53
+ Bolt::Result.from_exception(node.target, ex)
46
54
  ensure
47
55
  begin
48
56
  node.disconnect
@@ -50,11 +58,8 @@ module Bolt
50
58
  @logger.info("Failed to close connection to #{node.uri} : #{ex.message}")
51
59
  end
52
60
  end
53
- results[node] = result
54
- if callback
55
- @notifier.notify(callback, node, type: :node_result, result: result)
56
- end
57
- result
61
+ results.concat([result])
62
+ @notifier.notify(callback, type: :node_result, result: result) if callback
58
63
  end
59
64
  }
60
65
  pool.shutdown
@@ -62,84 +67,78 @@ module Bolt
62
67
 
63
68
  @notifier.shutdown
64
69
 
65
- results_to_hash(results)
70
+ Bolt::ResultSet.new(results)
66
71
  end
72
+ private :on
67
73
 
68
74
  def summary(action, object, result)
69
- fc = result.select { |_, r| r.error }.length
75
+ fc = result.error_set.length
70
76
  npl = result.length == 1 ? '' : 's'
71
77
  fpl = fc == 1 ? '' : 's'
72
78
  "Ran #{action} '#{object}' on #{result.length} node#{npl} with #{fc} failure#{fpl}"
73
79
  end
80
+ private :summary
74
81
 
75
- def run_command(nodes, command)
76
- level = @plan_logging ? Logger::NOTICE : Logger::INFO
77
- @logger.log(level, "Starting command run '#{command}' on #{nodes.map(&:uri)}")
82
+ def run_command(targets, command)
83
+ nodes = from_targets(targets)
84
+ @logger.info("Starting command run '#{command}' on #{nodes.map(&:uri)}")
78
85
  callback = block_given? ? Proc.new : nil
79
86
 
80
87
  r = on(nodes, callback) do |node|
81
88
  @logger.debug("Running command '#{command}' on #{node.uri}")
82
89
  node_result = node.run_command(command)
83
- @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.to_result)}")
90
+ @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.value)}")
84
91
  node_result
85
92
  end
86
- @logger.log(level, summary('command', command, r))
93
+ @logger.info(summary('command', command, r))
87
94
  r
88
95
  end
89
96
 
90
- def run_script(nodes, script, arguments)
91
- level = @plan_logging ? Logger::NOTICE : Logger::INFO
92
- @logger.log(level, "Starting script run #{script} on #{nodes.map(&:uri)}")
97
+ def run_script(targets, script, arguments)
98
+ nodes = from_targets(targets)
99
+ @logger.info("Starting script run #{script} on #{nodes.map(&:uri)}")
93
100
  @logger.debug("Arguments: #{arguments}")
94
101
  callback = block_given? ? Proc.new : nil
95
102
 
96
103
  r = on(nodes, callback) do |node|
97
104
  @logger.debug { "Running script '#{script}' on #{node.uri}" }
98
105
  node_result = node.run_script(script, arguments)
99
- @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.to_result)}")
106
+ @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.value)}")
100
107
  node_result
101
108
  end
102
- @logger.log(level, summary('script', script, r))
109
+ @logger.info(summary('script', script, r))
103
110
  r
104
111
  end
105
112
 
106
- def run_task(nodes, task, input_method, arguments)
107
- level = @plan_logging ? Logger::NOTICE : Logger::INFO
108
- @logger.log(level, "Starting task #{task} on #{nodes.map(&:uri)}")
113
+ def run_task(targets, task, input_method, arguments)
114
+ nodes = from_targets(targets)
115
+ @logger.info("Starting task #{task} on #{nodes.map(&:uri)}")
109
116
  @logger.debug("Arguments: #{arguments} Input method: #{input_method}")
110
117
  callback = block_given? ? Proc.new : nil
111
118
 
112
119
  r = on(nodes, callback) do |node|
113
120
  @logger.debug { "Running task run '#{task}' on #{node.uri}" }
114
121
  node_result = node.run_task(task, input_method, arguments)
115
- @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.to_result)}")
122
+ @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.value)}")
116
123
  node_result
117
124
  end
118
- @logger.log(level, summary('task', task, r))
125
+ @logger.info(summary('task', task, r))
119
126
  r
120
127
  end
121
128
 
122
- def file_upload(nodes, source, destination)
123
- level = @plan_logging ? Logger::NOTICE : Logger::INFO
124
- @logger.log(level, "Starting file upload from #{source} to #{destination} on #{nodes.map(&:uri)}")
129
+ def file_upload(targets, source, destination)
130
+ nodes = from_targets(targets)
131
+ @logger.info("Starting file upload from #{source} to #{destination} on #{nodes.map(&:uri)}")
125
132
  callback = block_given? ? Proc.new : nil
126
133
 
127
134
  r = on(nodes, callback) do |node|
128
- @logger.debug { "Uploading: '#{source}' to #{node.uri}" }
135
+ @logger.debug { "Uploading: '#{source}' to #{destination} on #{node.uri}" }
129
136
  node_result = node.upload(source, destination)
130
- @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.to_result)}")
137
+ @logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.value)}")
131
138
  node_result
132
139
  end
133
- @logger.log(level, summary('upload', source, r))
140
+ @logger.info(summary('upload', source, r))
134
141
  r
135
142
  end
136
-
137
- private
138
-
139
- def results_to_hash(results)
140
- result_hash = {}
141
- results.each_pair { |k, v| result_hash[k] = v }
142
- result_hash
143
- end
144
143
  end
145
144
  end
data/lib/bolt/logger.rb CHANGED
@@ -1,56 +1,32 @@
1
- require 'logger'
2
- require 'bolt/formatter'
3
-
4
- class Logger
5
- send :remove_const, 'SEV_LABEL'
6
-
7
- SEV_LABEL = {
8
- 0 => 'DEBUG',
9
- 1 => 'INFO',
10
- 2 => 'NOTICE',
11
- 3 => 'WARN',
12
- 4 => 'ERROR',
13
- 5 => 'FATAL',
14
- 6 => 'ANY'
15
- }.freeze
16
-
17
- module Severity
18
- levels = %w[WARN ERROR FATAL ANY]
19
- levels.each do |level|
20
- send(:remove_const, level) if const_defined?(level)
1
+ require 'logging'
2
+
3
+ module Bolt
4
+ module Logger
5
+ # This method provides a single point-of-entry to setup logging for both
6
+ # the CLI and for tests. This is necessary because we define custom log
7
+ # levels which create corresponding methods on the logger instances;
8
+ # without first initializing the Logging system, calls to those methods
9
+ # will fail.
10
+ def self.initialize_logging
11
+ # Initialization isn't idempotent and will result in warnings about const
12
+ # redefs, so skip it if it's already been initialized
13
+ return if Logging.initialized?
14
+
15
+ Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
16
+ Logging.appenders.stderr(
17
+ 'stderr',
18
+ layout: Logging.layouts.pattern(
19
+ pattern: '%d %-6l %c: %m\n',
20
+ date_pattern: '%Y-%m-%dT%H:%M:%S.%6N'
21
+ )
22
+ )
23
+ root_logger = Logging.logger[:root]
24
+ root_logger.add_appenders :stderr
25
+ root_logger.level = :notice
21
26
  end
22
- NOTICE = 2
23
- WARN = 3
24
- ERROR = 4
25
- FATAL = 5
26
- ANY = 6
27
- end
28
-
29
- def notice(progname = nil, &block)
30
- add(NOTICE, nil, progname, &block)
31
- end
32
27
 
33
- def notice?
34
- @level <= NOTICE
35
- end
36
-
37
- # rubocop:disable Style/ClassVars
38
- @@config = {
39
- log_destination: STDERR,
40
- log_level: NOTICE
41
- }
42
-
43
- # Not thread safe call only during startup
44
- def self.configure(config)
45
- @@config[:log_level] = config[:log_level] if config[:log_level]
46
- end
47
-
48
- def self.get_logger(**conf)
49
- conf = @@config.merge(conf)
50
- logger = new(conf[:log_destination])
51
- logger.level = conf[:log_level]
52
- logger.progname = conf[:progname] if conf[:progname]
53
- logger.formatter = Bolt::Formatter.new
54
- logger
28
+ def self.reset_logging
29
+ Logging.reset
30
+ end
55
31
  end
56
32
  end
data/lib/bolt/node.rb CHANGED
@@ -1,17 +1,15 @@
1
- require 'bolt/logger'
2
- require 'bolt/node_uri'
3
1
  require 'bolt/result'
4
2
  require 'bolt/config'
5
3
  require 'bolt/target'
4
+ require 'logging'
6
5
 
7
6
  module Bolt
8
7
  class Node
9
8
  STDIN_METHODS = %w[both stdin].freeze
10
9
  ENVIRONMENT_METHODS = %w[both environment].freeze
11
10
 
12
- def self.from_uri(uri_string, **kwargs)
13
- uri = NodeURI.new(uri_string, kwargs[:config][:transport])
14
- klass = case uri.scheme
11
+ def self.from_target(target, **kwargs)
12
+ klass = case target.protocol || kwargs[:config][:transport]
15
13
  when 'winrm'
16
14
  Bolt::WinRM
17
15
  when 'pcp'
@@ -19,27 +17,19 @@ module Bolt
19
17
  else
20
18
  Bolt::SSH
21
19
  end
22
- klass.new(uri.hostname,
23
- uri.port,
24
- uri.user,
25
- uri.password,
26
- uri: uri_string,
27
- **kwargs)
20
+ klass.new(target, **kwargs)
28
21
  end
29
22
 
30
23
  def self.initialize_transport(_logger); end
31
24
 
32
- attr_reader :logger, :host, :port, :uri, :user, :password, :connect_timeout
25
+ attr_reader :logger, :user, :password, :connect_timeout, :target
33
26
 
34
- def initialize(host, port = nil, user = nil, password = nil, uri: nil,
35
- config: Bolt::Config.new)
36
- @host = host
37
- @port = port
38
- @uri = uri
27
+ def initialize(target, config: Bolt::Config.new)
28
+ @target = target
39
29
 
40
30
  transport_conf = config[:transports][protocol.to_sym]
41
- @user = user || transport_conf[:user]
42
- @password = password || transport_conf[:password]
31
+ @user = @target.user || transport_conf[:user]
32
+ @password = @target.password || transport_conf[:password]
43
33
  @key = transport_conf[:key]
44
34
  @cacert = transport_conf[:cacert]
45
35
  @tty = transport_conf[:tty]
@@ -53,30 +43,11 @@ module Bolt
53
43
  @orch_task_environment = transport_conf[:orch_task_environment]
54
44
  @extensions = transport_conf[:extensions]
55
45
 
56
- @logger = Logger.get_logger(progname: @host)
57
- @transport_logger = Logger.get_logger(progname: @host, log_level: Logger::WARN)
46
+ @logger = Logging.logger[@target.host]
58
47
  end
59
48
 
60
- def upload(source, destination)
61
- @logger.debug { "Uploading #{source} to #{destination}" }
62
- result = _upload(source, destination)
63
- if result.success?
64
- Bolt::Result.new(nil, "Uploaded '#{source}' to '#{host}:#{destination}'")
65
- else
66
- result
67
- end
68
- end
69
-
70
- def run_command(command)
71
- _run_command(command)
72
- end
73
-
74
- def run_script(script, arguments)
75
- _run_script(script, arguments)
76
- end
77
-
78
- def run_task(task, input_method, arguments)
79
- _run_task(task, input_method, arguments)
49
+ def uri
50
+ @target.uri
80
51
  end
81
52
  end
82
53
  end
@@ -43,13 +43,13 @@ module Bolt
43
43
  end
44
44
  end
45
45
 
46
- def _run_task(task, _input_method, arguments)
46
+ def run_task(task, _input_method, arguments)
47
47
  body = { task: task_name_from_path(task),
48
48
  environment: @orch_task_environment,
49
49
  noop: arguments['_noop'],
50
50
  params: arguments.reject { |k, _| k == '_noop' },
51
51
  scope: {
52
- nodes: [@host]
52
+ nodes: [@target.host]
53
53
  } }
54
54
  # Should we handle errors here or let them propagate?
55
55
  results = client.run_task(body)
@@ -57,53 +57,47 @@ module Bolt
57
57
  state = node_result['state']
58
58
  result = node_result['result']
59
59
 
60
- if state == 'finished'
61
- exit_code = 0
60
+ # If it's finished or already has a proper error simply pass it to the
61
+ # the result otherwise make sure an error is generated
62
+ if state == 'finished' || result['_error']
63
+ Bolt::Result.new(@target, value: result)
62
64
  elsif state == 'skipped'
63
- return Bolt::TaskResult.new(
64
- JSON.dump(
65
- '_error' => {
66
- 'kind' => 'puppetlabs.tasks/skipped-node',
67
- 'msg' => "Node #{@host} was skipped",
68
- 'details' => {}
69
- }
70
- ),
71
- nil,
72
- nil
65
+ Bolt::Result.new(
66
+ @target,
67
+ value: { '_error' => {
68
+ 'kind' => 'puppetlabs.tasks/skipped-node',
69
+ 'msg' => "Node #{@target.host} was skipped",
70
+ 'details' => {}
71
+ } }
73
72
  )
74
73
  else
75
- # Try to extract the exit_code from _error
76
- begin
77
- exit_code = result['_error']['details']['exit_code'] || 'unknown'
78
- rescue NoMethodError
79
- exit_code = 'unknown'
80
- end
74
+ # Make a generic error with a unkown exit_code
75
+ Bolt::Result.for_task(@target, result.to_json, '', 'unknown')
81
76
  end
82
- Bolt::TaskResult.new(result.to_json, "", exit_code)
83
77
  end
84
78
 
85
79
  # run_task generates a result that makes sense for a generic task which
86
80
  # needs to be unwrapped to extract stdout/stderr/exitcode.
87
81
  #
88
82
  def unwrap_bolt_result(result)
89
- if result.error
83
+ if result.error_hash
90
84
  # something went wrong return the failure
91
85
  return result
92
86
  end
93
87
 
94
- Bolt::CommandResult.new(result.value['stdout'], result.value['stderr'], result.value['exit_code'])
88
+ Bolt::Result.for_command(@target, result.value['stdout'], result.value['stderr'], result.value['exit_code'])
95
89
  end
96
90
 
97
- def _run_command(command, options = {})
98
- result = _run_task(BOLT_MOCK_FILE,
99
- 'stdin',
100
- action: 'command',
101
- command: command,
102
- options: options)
91
+ def run_command(command, options = {})
92
+ result = run_task(BOLT_MOCK_FILE,
93
+ 'stdin',
94
+ action: 'command',
95
+ command: command,
96
+ options: options)
103
97
  unwrap_bolt_result(result)
104
98
  end
105
99
 
106
- def _upload(source, destination)
100
+ def upload(source, destination)
107
101
  content = File.open(source, &:read)
108
102
  content = Base64.encode64(content)
109
103
  mode = File.stat(source).mode
@@ -113,12 +107,12 @@ module Bolt
113
107
  content: content,
114
108
  mode: mode
115
109
  }
116
- result = _run_task(BOLT_MOCK_FILE, 'stdin', params)
117
- result = Bolt::Result.new unless result.error
110
+ result = run_task(BOLT_MOCK_FILE, 'stdin', params)
111
+ result = Bolt::Result.for_upload(@target, source, destination) unless result.error_hash
118
112
  result
119
113
  end
120
114
 
121
- def _run_script(script, arguments)
115
+ def run_script(script, arguments)
122
116
  content = File.open(script, &:read)
123
117
  content = Base64.encode64(content)
124
118
  params = {
@@ -126,7 +120,7 @@ module Bolt
126
120
  content: content,
127
121
  arguments: arguments
128
122
  }
129
- unwrap_bolt_result(_run_task(BOLT_MOCK_FILE, 'stdin', params))
123
+ unwrap_bolt_result(run_task(BOLT_MOCK_FILE, 'stdin', params))
130
124
  end
131
125
  end
132
126
  end