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.



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

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



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

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

.for_command(target, stdout, stderr, exit_code, action, command) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/bolt/result.rb', line 25

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

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



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

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) ⇒ Object



42
43
44
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
# File 'lib/bolt/result.rb', line 42

def self.for_task(target, stdout, stderr, exit_code, task)
  stdout.force_encoding('utf-8') unless stdout.encoding == Encoding::UTF_8
  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' => {} } }
          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
    value['_error'] = { 'kind' => 'puppetlabs.tasks/task-error',
                        'issue_code' => 'TASK_ERROR',
                        'msg' => msg,
                        'details' => { 'exit_code' => exit_code } }
  end

  if value.key?('_error')
    unless value['_error'].is_a?(Hash) && value['_error'].key?('msg')
      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' => { 'original_error' => value['_error'] }
      }
    end

    value['_error']['kind']    ||= 'bolt/error'
    value['_error']['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



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

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



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

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

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



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

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

.parse_hash(string) ⇒ Object



90
91
92
93
94
95
# File 'lib/bolt/result.rb', line 90

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



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

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

#[](key) ⇒ Object



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

def [](key)
  value[key]
end

#_pcore_init_from_hash(init_hash) ⇒ Object



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

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



122
123
124
125
126
127
128
129
# File 'lib/bolt/result.rb', line 122

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

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


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

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?



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

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



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

def error_hash
  value['_error']
end

#generic_valueObject



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

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

#messageObject



143
144
145
# File 'lib/bolt/result.rb', line 143

def message
  @value['_output']
end

#message?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/bolt/result.rb', line 147

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

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

Returns:

  • (Boolean)


206
207
208
# File 'lib/bolt/result.rb', line 206

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.



181
182
183
184
185
186
187
188
189
190
# File 'lib/bolt/result.rb', line 181

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



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

def sensitive
  value['_sensitive']
end

#statusObject



202
203
204
# File 'lib/bolt/result.rb', line 202

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

#to_dataObject



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

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

#to_json(opts = nil) ⇒ Object



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

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

#to_sObject



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

def to_s
  to_json
end