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.



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

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



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

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, stdout, stderr, exit_code, action, command, position) ⇒ Object



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

def self.for_command(target, stdout, stderr, exit_code, action, command, position)
  value = {
    'stdout' => stdout,
    'stderr' => stderr,
    'exit_code' => exit_code
  }

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

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



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

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



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
101
# File 'lib/bolt/result.rb', line 51

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



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

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



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

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



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

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

Instance Method Details

#==(other) ⇒ Object



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

def ==(other)
  eql?(other)
end

#[](key) ⇒ Object



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

def [](key)
  value[key]
end

#_pcore_init_from_hash(init_hash) ⇒ Object



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

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



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

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

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


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

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?



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

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



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

def error_hash
  value['_error']
end

#generic_valueObject



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

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

#messageObject



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

def message
  @value['_output']
end

#message?Boolean

Returns:

  • (Boolean)


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

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

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

Returns:

  • (Boolean)


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

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.



194
195
196
197
198
199
200
201
202
203
# File 'lib/bolt/result.rb', line 194

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



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

def sensitive
  value['_sensitive']
end

#statusObject



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

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

#to_dataObject



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

def to_data
  {
    "target" => @target.name,
    "action" => action,
    "object" => object,
    "status" => status,
    "value" => safe_value
  }
end

#to_json(opts = nil) ⇒ Object



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

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

#to_sObject



186
187
188
# File 'lib/bolt/result.rb', line 186

def to_s
  to_json
end