Class: Bolt::Result

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

Direct Known Subclasses

ApplyResult

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target, error: nil, message: nil, value: nil, action: 'action', object: nil) ⇒ Result

Returns a new instance of Result.



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/bolt/result.rb', line 138

def initialize(target, error: nil, message: nil, value: nil, action: 'action', object: nil)
  @target = target
  @value = value || {}
  @action = action
  @object = object
  if error && !error.is_a?(Hash)
    raise "TODO: how did we get a string error"
  end
  @value['_error'] = error if error
  @value['_output'] = message if message
end

Instance Attribute Details

#actionObject (readonly)

Returns the value of attribute action.



8
9
10
# File 'lib/bolt/result.rb', line 8

def action
  @action
end

#objectObject (readonly)

Returns the value of attribute object.



8
9
10
# File 'lib/bolt/result.rb', line 8

def object
  @object
end

#targetObject (readonly)

Returns the value of attribute target.



8
9
10
# File 'lib/bolt/result.rb', line 8

def target
  @target
end

#valueObject (readonly)

Returns the value of attribute value.



8
9
10
# File 'lib/bolt/result.rb', line 8

def value
  @value
end

Class Method Details

._pcore_init_from_hashObject



120
121
122
# File 'lib/bolt/result.rb', line 120

def self._pcore_init_from_hash
  raise "Result shouldn't be instantiated from a pcore_init class method. How did this get called?"
end

.create_details(position) ⇒ Object



27
28
29
# File 'lib/bolt/result.rb', line 27

def self.create_details(position)
  %w[file line].zip(position).to_h.compact
end

.for_command(target, value, action, command, position) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/bolt/result.rb', line 31

def self.for_command(target, value, action, command, position)
  details = create_details(position)
  unless value['exit_code'] == 0
    details['exit_code'] = value['exit_code']
    value['_error'] = {
      'kind' => 'puppetlabs.tasks/command-error',
      'issue_code' => 'COMMAND_ERROR',
      'msg' => "The command failed with exit code #{value['exit_code']}",
      'details' => details
    }
  end
  new(target, value: value, action: action, object: command)
end

.for_download(target, source, destination, download) ⇒ Object



108
109
110
111
112
113
# File 'lib/bolt/result.rb', line 108

def self.for_download(target, source, destination, download)
  msg   = "Downloaded '#{target.host}:#{source}' to '#{destination}'"
  value = { 'path' => download }

  new(target, value: value, message: msg, action: 'download', object: source)
end

.for_task(target, stdout, stderr, exit_code, task, position) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
94
95
# File 'lib/bolt/result.rb', line 45

def self.for_task(target, stdout, stderr, exit_code, task, position)
  stdout.force_encoding('utf-8') unless stdout.encoding == Encoding::UTF_8

  details = create_details(position)
  value = if stdout.valid_encoding?
            parse_hash(stdout) || { '_output' => stdout }
          else
            { '_error' => { 'kind' => 'puppetlabs.tasks/task-error',
                            'issue_code' => 'TASK_ERROR',
                            'msg' => 'The task result contained invalid UTF-8 on stdout',
                            'details' => details } }
          end

  if exit_code != 0 && value['_error'].nil?
    msg = if stdout.empty?
            if stderr.empty?
              "The task failed with exit code #{exit_code} and no output"
            else
              "The task failed with exit code #{exit_code} and no stdout, but stderr contained:\n#{stderr}"
            end
          else
            "The task failed with exit code #{exit_code}"
          end
    details['exit_code'] = exit_code
    value['_error'] = { 'kind' => 'puppetlabs.tasks/task-error',
                        'issue_code' => 'TASK_ERROR',
                        'msg' => msg,
                        'details' => details }
  end

  if value.key?('_error')
    unless value['_error'].is_a?(Hash) && value['_error'].key?('msg')
      details['original_error'] = value['_error']
      value['_error'] = {
        'msg'     => "Invalid error returned from task #{task}: #{value['_error'].inspect}. Error "\
                     "must be an object with a msg key.",
        'kind'    => 'bolt/invalid-task-error',
        'details' => details
      }
    end

    value['_error']['kind']    ||= 'bolt/error'
    value['_error']['details'] ||= details
  end

  if value.key?('_sensitive')
    value['_sensitive'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(value['_sensitive'])
  end

  new(target, value: value, action: 'task', object: task)
end

.for_upload(target, source, destination) ⇒ Object



104
105
106
# File 'lib/bolt/result.rb', line 104

def self.for_upload(target, source, destination)
  new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'", action: 'upload', object: source)
end

.from_asserted_args(target, value) ⇒ Object

Satisfies the Puppet datatypes API



116
117
118
# File 'lib/bolt/result.rb', line 116

def self.from_asserted_args(target, value)
  new(target, value: value)
end

.from_exception(target, exception, action: 'action', position: []) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/bolt/result.rb', line 10

def self.from_exception(target, exception, action: 'action', position: [])
  details = create_details(position)
  if exception.is_a?(Bolt::Error)
    error = Bolt::Util.deep_merge({ 'details' => details }, exception.to_h)
  else
    details['class'] = exception.class.to_s
    error = {
      'kind' => 'puppetlabs.tasks/exception-error',
      'issue_code' => 'EXCEPTION',
      'msg' => exception.message,
      'details' => details
    }
    error['details']['stack_trace'] = exception.backtrace.join('\n') if exception.backtrace
  end
  Result.new(target, error: error, action: action)
end

.parse_hash(string) ⇒ Object



97
98
99
100
101
102
# File 'lib/bolt/result.rb', line 97

def self.parse_hash(string)
  value = JSON.parse(string)
  value if value.is_a? Hash
rescue JSON::ParserError
  nil
end

Instance Method Details

#[](key) ⇒ Object



169
170
171
# File 'lib/bolt/result.rb', line 169

def [](key)
  value[key]
end

#_pcore_init_from_hash(init_hash) ⇒ Object



124
125
126
127
# File 'lib/bolt/result.rb', line 124

def _pcore_init_from_hash(init_hash)
  opts = init_hash.reject { |k, _v| k == 'target' }
  initialize(init_hash['target'], opts.transform_keys(&:to_sym))
end

#_pcore_init_hashObject



129
130
131
132
133
134
135
136
# File 'lib/bolt/result.rb', line 129

def _pcore_init_hash
  { 'target' => @target,
    'error' => @value['_error'],
    'message' => @value['_output'],
    'value' => @value,
    'action' => @action,
    'object' => @object }
end

#eql?(other) ⇒ Boolean Also known as: ==

Returns:

  • (Boolean)


162
163
164
165
166
# File 'lib/bolt/result.rb', line 162

def eql?(other)
  self.class == other.class &&
    target == other.target &&
    value == other.value
end

#errorObject

Warning: This will fail outside of a compilation. Use error_hash inside bolt. Is it crazy for this to behave differently outside a compiler?



230
231
232
233
234
# File 'lib/bolt/result.rb', line 230

def error
  if error_hash
    Puppet::DataTypes::Error.from_asserted_hash(error_hash)
  end
end

#error_hashObject

This allows access to errors outside puppet compilation it should be prefered over error in bolt code



223
224
225
# File 'lib/bolt/result.rb', line 223

def error_hash
  value['_error']
end

#generic_valueObject



158
159
160
# File 'lib/bolt/result.rb', line 158

def generic_value
  safe_value.reject { |k, _| %w[_error _output].include? k }
end

#messageObject



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

def message
  @value['_output']
end

#message?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/bolt/result.rb', line 154

def message?
  message && !message.strip.empty?
end

#ok?Boolean Also known as: ok, success?

Returns:

  • (Boolean)


215
216
217
# File 'lib/bolt/result.rb', line 215

def ok?
  error_hash.nil?
end

#safe_valueObject

This is the value with all non-UTF-8 characters removed, suitable for printing or converting to JSON. It should only be possible to have non-UTF-8 characters in stdout/stderr keys as they are not allowed from tasks but we scrub the whole thing just in case.



185
186
187
188
189
190
191
192
193
194
# File 'lib/bolt/result.rb', line 185

def safe_value
  Bolt::Util.walk_vals(value) do |val|
    if val.is_a?(String)
      # Replace invalid bytes with hex codes, ie. \xDE\xAD\xBE\xEF
      val.scrub { |c| c.bytes.map { |b| "\\x" + b.to_s(16).upcase }.join }
    else
      val
    end
  end
end

#sensitiveObject



236
237
238
# File 'lib/bolt/result.rb', line 236

def sensitive
  value['_sensitive']
end

#statusObject



211
212
213
# File 'lib/bolt/result.rb', line 211

def status
  ok? ? 'success' : 'failure'
end

#to_dataObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/bolt/result.rb', line 196

def to_data
  serialized_value = safe_value
  if serialized_value.key?('_sensitive') &&
     serialized_value['_sensitive'].is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
    serialized_value['_sensitive'] = serialized_value['_sensitive'].to_s
  end
  {
    "target" => @target.name,
    "action" => action,
    "object" => object,
    "status" => status,
    "value" => serialized_value
  }
end

#to_json(opts = nil) ⇒ Object



173
174
175
# File 'lib/bolt/result.rb', line 173

def to_json(opts = nil)
  to_data.to_json(opts)
end

#to_sObject



177
178
179
# File 'lib/bolt/result.rb', line 177

def to_s
  to_json
end