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.


143
144
145
146
147
148
149
150
151
152
153
# File 'lib/bolt/result.rb', line 143

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


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

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


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

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


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

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_lookup(target, key, value) ⇒ Object


31
32
33
34
# File 'lib/bolt/result.rb', line 31

def self.for_lookup(target, key, value)
  val = { 'value' => value }
  new(target, value: val, action: 'lookup', object: key)
end

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


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
96
97
98
99
100
# File 'lib/bolt/result.rb', line 50

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


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

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


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

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


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

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


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

def [](key)
  value[key]
end

#_pcore_init_from_hash(init_hash) ⇒ Object


129
130
131
132
# File 'lib/bolt/result.rb', line 129

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


134
135
136
137
138
139
140
141
# File 'lib/bolt/result.rb', line 134

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)

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

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?


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

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


228
229
230
# File 'lib/bolt/result.rb', line 228

def error_hash
  value['_error']
end

#generic_valueObject


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

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

#messageObject


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

def message
  @value['_output']
end

#message?Boolean

Returns:

  • (Boolean)

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

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

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

Returns:

  • (Boolean)

220
221
222
# File 'lib/bolt/result.rb', line 220

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.


190
191
192
193
194
195
196
197
198
199
# File 'lib/bolt/result.rb', line 190

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


241
242
243
# File 'lib/bolt/result.rb', line 241

def sensitive
  value['_sensitive']
end

#statusObject


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

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

#to_dataObject


201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/bolt/result.rb', line 201

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


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

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

#to_sObject


182
183
184
# File 'lib/bolt/result.rb', line 182

def to_s
  to_json
end