Class: Bolt::ApplyResult

Inherits:
Result
  • Object
show all
Defined in:
lib/bolt/apply_result.rb

Instance Attribute Summary

Attributes inherited from Result

#action, #object, #target, #value

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Result

#[], #_pcore_init_from_hash, _pcore_init_from_hash, create_details, #eql?, #error, #error_hash, for_command, for_download, for_lookup, for_task, for_upload, from_asserted_args, from_exception, #message, #message?, #ok?, parse_hash, #safe_value, #sensitive, #status, #to_data, #to_json, #to_s

Constructor Details

#initialize(target, error: nil, report: nil, catalog: nil) ⇒ ApplyResult

Returns a new instance of ApplyResult.



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/bolt/apply_result.rb', line 103

def initialize(target, error: nil, report: nil, catalog: nil)
  @target = target
  @value = {}
  @action = 'apply'
  @value['report'] = report if report
  @value['_error'] = error if error
  @value['_output'] = metrics_message if metrics_message

  if catalog
    @value['_sensitive'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new({ 'catalog' => catalog })
  end
end

Class Method Details

.from_task_result(result, catalog = nil) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/bolt/apply_result.rb', line 68

def self.from_task_result(result, catalog = nil)
  if (puppet_missing = puppet_missing_error(result))
    new(result.target,
        error: puppet_missing,
        report: result.value.reject { |k| k == '_error' },
        catalog: catalog)
  elsif !result.ok?
    new(result.target,
        error: result.error_hash,
        catalog: catalog)
  elsif (invalid_report = invalid_report_error(result))
    new(result.target,
        error: invalid_report,
        report: result.value.reject { |k| %w[_error _output].include?(k) },
        catalog: catalog)
  elsif (resource_error = resource_error(result))
    new(result.target,
        error: resource_error,
        report: result.value.reject { |k| k == '_error' },
        catalog: catalog)
  else
    new(result.target,
        report: result.value,
        catalog: catalog)
  end
end

.invalid_report_error(result) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/bolt/apply_result.rb', line 49

def self.invalid_report_error(result)
  # These are the keys ApplyResult methods rely on.
  expected_report_keys = %w[metrics resource_statuses status]
  missing_keys = expected_report_keys.reject { |k| result.value.include?(k) }

  unless missing_keys.empty?
    if result['_output']
      # rubocop:disable Layout/LineLength
      msg = "Report result contains an '_output' key. Catalog application might have printed extraneous output to stdout: #{result['_output']}"
      # rubocop:enable Layout/LineLength
    else
      msg = "Report did not contain all expected keys missing: #{missing_keys.join(', ')}"
    end

    { 'msg' => msg,
      'kind' => 'bolt/invalid-report' }
  end
end

.puppet_missing_error(result) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/bolt/apply_result.rb', line 9

def self.puppet_missing_error(result)
  error_hash = result.error_hash
  exit_code = error_hash['details']['exit_code'] if error_hash && error_hash['details']
  # If we get exit code 126 or 127 back, it means the shebang command wasn't found; Puppet isn't present
  if [126, 127].include?(exit_code)
    {
      'msg' => "Puppet is not installed on the target, please install it to enable 'apply'",
      'kind' => 'bolt/apply-error'
    }
  elsif exit_code == 1 &&
        (error_hash['msg'] =~ /Could not find executable 'ruby.exe'/ ||
         error_hash['msg'] =~ /The term 'ruby.exe' is not recognized as the name of a cmdlet/)
    # Windows does not have Ruby present
    {
      'msg' => "Puppet was not found on the target or in $env:ProgramFiles, please install it to enable 'apply'",
      'kind' => 'bolt/apply-error'
    }
  elsif exit_code == 1 && error_hash['msg'] =~ /cannot load such file -- puppet \(LoadError\)/
    # Windows uses a Ruby that doesn't have Puppet installed
    # TODO: fix so we don't find other Rubies, or point to a known issues URL for more info
    { 'msg' => 'Found a Ruby without Puppet present, please install Puppet ' \
               "or remove Ruby from $env:Path to enable 'apply'",
      'kind' => 'bolt/apply-error' }
  end
end

.resource_error(result) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/bolt/apply_result.rb', line 35

def self.resource_error(result)
  if result.value['status'] == 'failed'
    resources = result.value['resource_statuses']
    failed = resources.select { |_, r| r['failed'] }.flat_map do |key, resource|
      resource['events'].select { |e| e['status'] == 'failure' }.map do |event|
        "\n  #{key}: #{event['message']}"
      end
    end

    { 'msg' => "Resources failed to apply for #{result.target.name}#{failed.join}",
      'kind' => 'bolt/resource-failure' }
  end
end

Instance Method Details

#_pcore_init_hashObject

Other pcore methods are inherited from Result



96
97
98
99
100
101
# File 'lib/bolt/apply_result.rb', line 96

def _pcore_init_hash
  { 'target' => @target,
    'error' => value['_error'],
    'report' => value['report'],
    'catalog' => catalog }
end

#catalogObject



146
147
148
# File 'lib/bolt/apply_result.rb', line 146

def catalog
  sensitive.unwrap['catalog'] if sensitive
end

#event_metricsObject



116
117
118
119
120
# File 'lib/bolt/apply_result.rb', line 116

def event_metrics
  if (events = value.dig('report', 'metrics', 'resources', 'values'))
    events.each_with_object({}) { |ev, h| h[ev[0]] = ev[2] }
  end
end

#generic_valueObject



150
151
152
# File 'lib/bolt/apply_result.rb', line 150

def generic_value
  {}
end

#logsObject



122
123
124
# File 'lib/bolt/apply_result.rb', line 122

def logs
  value.dig('report', 'logs') || []
end

#metrics_messageObject



131
132
133
134
135
136
137
138
139
140
# File 'lib/bolt/apply_result.rb', line 131

def metrics_message
  if (metrics = event_metrics)
    changed = metrics['changed']
    failed = metrics['failed']
    skipped = metrics['skipped']
    unchanged = metrics['total'] - changed - failed - skipped
    noop = metrics['out_of_sync'] - changed - failed
    "changed: #{changed}, failed: #{failed}, unchanged: #{unchanged} skipped: #{skipped}, noop: #{noop}"
  end
end

#reportObject



142
143
144
# File 'lib/bolt/apply_result.rb', line 142

def report
  @value['report']
end

#resource_logsObject

Return only log messages associated with resources



127
128
129
# File 'lib/bolt/apply_result.rb', line 127

def resource_logs
  logs.reject { |log| log['source'] == 'Puppet' }
end