cloud_powers 0.2.7.23 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.test.env.example +6 -6
- data/.travis.yml +1 -1
- data/README +190 -0
- data/cloud_powers.gemspec +4 -4
- data/lib/cloud_powers.rb +3 -13
- data/lib/cloud_powers/aws_resources.rb +21 -4
- data/lib/cloud_powers/creatable.rb +122 -0
- data/lib/cloud_powers/helpers.rb +58 -0
- data/lib/cloud_powers/helpers/lang_help.rb +288 -0
- data/lib/cloud_powers/helpers/logic_help.rb +152 -0
- data/lib/cloud_powers/helpers/path_help.rb +90 -0
- data/lib/cloud_powers/node.rb +69 -68
- data/lib/cloud_powers/node/instance.rb +52 -0
- data/lib/cloud_powers/resource.rb +44 -0
- data/lib/cloud_powers/storage.rb +27 -14
- data/lib/{stubs → cloud_powers/stubs}/aws_stubs.rb +37 -14
- data/lib/cloud_powers/synapse/broadcast.rb +117 -0
- data/lib/cloud_powers/synapse/broadcast/channel.rb +44 -0
- data/lib/cloud_powers/synapse/pipe.rb +211 -0
- data/lib/cloud_powers/synapse/pipe/stream.rb +41 -0
- data/lib/cloud_powers/synapse/queue.rb +357 -0
- data/lib/cloud_powers/synapse/queue/board.rb +61 -95
- data/lib/cloud_powers/synapse/queue/poller.rb +29 -0
- data/lib/cloud_powers/synapse/synapse.rb +10 -12
- data/lib/cloud_powers/synapse/web_soc.rb +13 -0
- data/lib/cloud_powers/synapse/web_soc/soc_client.rb +52 -0
- data/lib/cloud_powers/synapse/web_soc/soc_server.rb +48 -0
- data/lib/cloud_powers/version.rb +1 -1
- data/lib/cloud_powers/zenv.rb +13 -12
- metadata +24 -13
- data/lib/cloud_powers/context.rb +0 -275
- data/lib/cloud_powers/delegator.rb +0 -113
- data/lib/cloud_powers/helper.rb +0 -453
- data/lib/cloud_powers/synapse/websocket/websocclient.rb +0 -53
- data/lib/cloud_powers/synapse/websocket/websocserver.rb +0 -46
- data/lib/cloud_powers/workflow_factory.rb +0 -160
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'syslog/logger'
|
3
|
+
require 'cloud_powers/helpers/lang_help'
|
4
|
+
require 'cloud_powers/helpers/logic_help'
|
5
|
+
require 'cloud_powers/helpers/path_help'
|
6
|
+
|
7
|
+
module Smash
|
8
|
+
module CloudPowers
|
9
|
+
module Helpers
|
10
|
+
# methods to help change convert between different cases, like the
|
11
|
+
# <tt>from_json</tt> and <tt>to_camel</tt> and other help with Ruby
|
12
|
+
include Smash::CloudPowers::LangHelp
|
13
|
+
# methods to help awareness, dynamic code and other such fun
|
14
|
+
include Smash::CloudPowers::LogicHelp
|
15
|
+
# methods to help find locations of files and directories. This provides
|
16
|
+
# common locations for code to reference.
|
17
|
+
include Smash::CloudPowers::PathHelp
|
18
|
+
|
19
|
+
# creates a default logger
|
20
|
+
#
|
21
|
+
# Parameters
|
22
|
+
# * log_to +String+ (optional) - location to send logging information to; default is STDOUT
|
23
|
+
#
|
24
|
+
# Returns
|
25
|
+
# +Logger+
|
26
|
+
#
|
27
|
+
# Notes
|
28
|
+
# * TODO: at least make this have overridable defaults
|
29
|
+
def create_logger(log_to = STDOUT)
|
30
|
+
logger = Logger.new(log_to)
|
31
|
+
logger.datetime_format = '%Y-%m-%d %H:%M:%S'
|
32
|
+
logger
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets the path from the environment and sets @log_file using the path
|
36
|
+
#
|
37
|
+
# Returns
|
38
|
+
# @log_file +String+
|
39
|
+
#
|
40
|
+
# Notes
|
41
|
+
# * See +#zfind()+
|
42
|
+
def log_file
|
43
|
+
@log_file ||= zfind('LOG_FILE')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns An instance of Logger, cached as @logger@log_file path <String>
|
47
|
+
#
|
48
|
+
# Returns
|
49
|
+
# +Logger+
|
50
|
+
#
|
51
|
+
# Notes
|
52
|
+
# * See +#create_logger+
|
53
|
+
def logger
|
54
|
+
@logger ||= create_logger
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
module Smash
|
2
|
+
module CloudPowers
|
3
|
+
module LangHelp
|
4
|
+
|
5
|
+
# Allows you to modify all keys, including nested, with a block that you pass.
|
6
|
+
# If no block is passed, a copy is returned.
|
7
|
+
#
|
8
|
+
# Parameters
|
9
|
+
# * params +Hash+|+Array+ - hash to be modified
|
10
|
+
# * +block+ (optional) - a block to be used to modify each key should
|
11
|
+
# modify the key and return that value so it can be used in the copy
|
12
|
+
#
|
13
|
+
# Returns
|
14
|
+
# +Hash+|+Array+ - a copy of the given Array or Hash, with all Hash keys modified
|
15
|
+
#
|
16
|
+
# Example
|
17
|
+
# hash = { 'foo' => 'v1', 'bar' => { fleep: { 'florp' => 'yo' } } }
|
18
|
+
# modify_keys_with(hash) { |key| key.to_sym }
|
19
|
+
# # => { foo: 'v1', bar: { fleep: { florp: 'yo' } } }
|
20
|
+
#
|
21
|
+
# Notes
|
22
|
+
# * see `#modify_keys_with()` for handling first-level keys
|
23
|
+
# * see `#pass_the_buck()` for the way nested structures are handled
|
24
|
+
# * case for different types taken from _MultiXML_ (multi_xml.rb)
|
25
|
+
# * TODO: look at optimization
|
26
|
+
def deep_modify_keys_with(params)
|
27
|
+
case params
|
28
|
+
when Hash
|
29
|
+
params.inject({}) do |carry, (k, v)|
|
30
|
+
carry.tap do |h|
|
31
|
+
if block_given?
|
32
|
+
key = yield k
|
33
|
+
|
34
|
+
value = if v.kind_of?(Hash)
|
35
|
+
deep_modify_keys_with(v) { |new_key| Proc.new.call(new_key) }
|
36
|
+
else
|
37
|
+
v
|
38
|
+
end
|
39
|
+
|
40
|
+
h[key] = value
|
41
|
+
else
|
42
|
+
h[k] = v
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
when Array
|
47
|
+
params.map{ |value| symbolize_keys(value) }
|
48
|
+
else
|
49
|
+
params
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Search through a +Hash+ without knowing if the key is a +String+ or
|
54
|
+
# +Symbol+. A +String+ modification that _normalizes_ each value to compare
|
55
|
+
# is used that is case insensitive. It only leaves word characters, not
|
56
|
+
# including underscore. After the value is found, if it exists and can
|
57
|
+
# be found, the +Hash+, minus that value, is returns in an +Array+ with
|
58
|
+
# the element you were searching for
|
59
|
+
#
|
60
|
+
# Parameters
|
61
|
+
# * key +String+|+Symbol+ - the key you are searching for
|
62
|
+
# * hash +Hash+ - the +Hash+ to search through and return a modified copy
|
63
|
+
# from
|
64
|
+
def find_and_remove(key, hash)
|
65
|
+
candidate_keys = hash.select do |k,v|
|
66
|
+
to_pascal(key).casecmp(to_pascal(k)) == 0
|
67
|
+
end.keys
|
68
|
+
|
69
|
+
interesting_value = hash.delete(candidate_keys.first)
|
70
|
+
[interesting_value, hash]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Join the message and backtrace into a String with line breaks
|
74
|
+
#
|
75
|
+
# Parameters
|
76
|
+
# * error +Exception+
|
77
|
+
#
|
78
|
+
# Returns
|
79
|
+
# +String+
|
80
|
+
def format_error_message(error)
|
81
|
+
begin
|
82
|
+
[error.message, error.backtrace.join("\n")].join("\n")
|
83
|
+
rescue Exception
|
84
|
+
# if the formatting won't work, return the original exception
|
85
|
+
error
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Change valid JSON into a hash
|
90
|
+
#
|
91
|
+
# Parameter
|
92
|
+
# * var +String+
|
93
|
+
#
|
94
|
+
# Returns
|
95
|
+
# +Hash+ or +nil+ - +nil+ is returned if the JSON is invalid
|
96
|
+
def from_json(var)
|
97
|
+
begin
|
98
|
+
JSON.parse(var)
|
99
|
+
rescue JSON::ParserError, TypeError
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Allows you to modify all first-level keys with a block that you pass.
|
105
|
+
# If no block is passed, a copy is returned.
|
106
|
+
#
|
107
|
+
# Parameters
|
108
|
+
# * params +Hash+|+Array+
|
109
|
+
# * block (optional) - should modify the key and return that value so it can be used in the copy
|
110
|
+
#
|
111
|
+
# Returns
|
112
|
+
# +Hash+|+Array+ - a copy of the given Array or Hash, with all Hash keys modified
|
113
|
+
#
|
114
|
+
# Example
|
115
|
+
# hash = { 'foo' => 'v1', 'bar' => { fleep: { 'florp' => 'yo' } } }
|
116
|
+
# modify_keys_with(hash) { |k| k.to_sym }
|
117
|
+
# # => { :foo => 'v1', :bar => { fleep: { 'florp' => 'yo' } } }
|
118
|
+
#
|
119
|
+
# Notes
|
120
|
+
# * see +#deep_modify_keys_with()+ for handling nested keys
|
121
|
+
# * case for different types taken from _MultiXML_ (multi_xml.rb)
|
122
|
+
def modify_keys_with(params)
|
123
|
+
params.inject({}) do |carry, (k, v)|
|
124
|
+
carry.tap do |h|
|
125
|
+
key = block_given? ? (yield k) : k
|
126
|
+
h[key] = v
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Change strings into camelCase
|
132
|
+
#
|
133
|
+
# Parameters
|
134
|
+
# * var +String+
|
135
|
+
#
|
136
|
+
# Returns
|
137
|
+
# +String+
|
138
|
+
def to_camel(var)
|
139
|
+
var = var.to_s unless var.kind_of? String
|
140
|
+
step_one = to_snake(var)
|
141
|
+
step_two = to_pascal(step_one)
|
142
|
+
step_two[0, 1].downcase + step_two[1..-1]
|
143
|
+
end
|
144
|
+
|
145
|
+
# Change strings into a hyphen delimited phrase
|
146
|
+
#
|
147
|
+
# Parameters
|
148
|
+
# * var +String+
|
149
|
+
#
|
150
|
+
# Returns
|
151
|
+
# +String+
|
152
|
+
def to_hyph(var)
|
153
|
+
var = var.to_s unless var.kind_of? String
|
154
|
+
|
155
|
+
var.gsub(/:{2}|\//, '-').
|
156
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
157
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
158
|
+
gsub(/\s+/, '-').
|
159
|
+
tr("_", "-").
|
160
|
+
gsub(/^\W/, '').
|
161
|
+
downcase
|
162
|
+
end
|
163
|
+
|
164
|
+
# Assure your arguments are a +Hash+
|
165
|
+
#
|
166
|
+
# Parameters
|
167
|
+
# * start_point +Object+ - Best to start with a +Hash+, then a 2-D Array,
|
168
|
+
# then something +Enumerable+ that is at least Ordered
|
169
|
+
#
|
170
|
+
# Returns
|
171
|
+
# +Hash+
|
172
|
+
#
|
173
|
+
# Notes
|
174
|
+
# * If a +Hash+ is given, a copy is returned
|
175
|
+
# * If an +Array+ is given,
|
176
|
+
# * And if the +Array+ is a properly formatted, 2-D +Array+, <tt>to_h</tt>
|
177
|
+
# is called
|
178
|
+
# * Else <tt>Hash[<default_key argument>, <the thing we're trying to turn into a hash>]</tt>
|
179
|
+
def to_basic_hash(start_point, default_key: 'key')
|
180
|
+
case start_point
|
181
|
+
when Hash
|
182
|
+
start_point
|
183
|
+
when Enumerable
|
184
|
+
two_dimensional_elements = start_point.select do |value|
|
185
|
+
value.respond_to? :each
|
186
|
+
end
|
187
|
+
|
188
|
+
if two_dimensional_elements.count - start_point.count
|
189
|
+
start_point.to_h
|
190
|
+
else
|
191
|
+
Hash[default_key, start_point]
|
192
|
+
end
|
193
|
+
else
|
194
|
+
Hash[default_key, start_point]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Change strings into an i-var format
|
199
|
+
#
|
200
|
+
# Parameters
|
201
|
+
# * var +String+
|
202
|
+
#
|
203
|
+
# Returns
|
204
|
+
# +String+
|
205
|
+
def to_i_var(var)
|
206
|
+
var = var.to_s unless var.kind_of? String
|
207
|
+
/^\W*@\w+/ =~ var ? to_snake(var) : "@#{to_snake(var)}"
|
208
|
+
end
|
209
|
+
|
210
|
+
# Change strings into PascalCase
|
211
|
+
#
|
212
|
+
# Parameters
|
213
|
+
# * var +String+
|
214
|
+
#
|
215
|
+
# Returns
|
216
|
+
# +String+
|
217
|
+
def to_pascal(var)
|
218
|
+
var = var.to_s unless var.kind_of? String
|
219
|
+
var.gsub(/^(.{1})|\W.{1}|\_.{1}/) { |s| s.gsub(/[^a-z0-9]+/i, '').capitalize }
|
220
|
+
end
|
221
|
+
|
222
|
+
# Change strings into a ruby_file_name with extension
|
223
|
+
#
|
224
|
+
# Parameters
|
225
|
+
# * var +String+
|
226
|
+
#
|
227
|
+
# Returns
|
228
|
+
# +String+
|
229
|
+
#
|
230
|
+
# Notes
|
231
|
+
# * given_string.rb
|
232
|
+
# * includes ruby file extension
|
233
|
+
# * see #to_snake()
|
234
|
+
def to_ruby_file_name(name)
|
235
|
+
return name if /\w+\.rb$/ =~ name
|
236
|
+
"#{to_snake(name)}.rb"
|
237
|
+
end
|
238
|
+
|
239
|
+
# Change strings into PascalCase
|
240
|
+
#
|
241
|
+
# Parameters
|
242
|
+
# * var +String+
|
243
|
+
#
|
244
|
+
# Returns
|
245
|
+
# +String+
|
246
|
+
#
|
247
|
+
# Notes
|
248
|
+
# * given_string
|
249
|
+
# * will not have file extensions
|
250
|
+
# * see #to_ruby_file_name()
|
251
|
+
def to_snake(var)
|
252
|
+
var = var.to_s unless var.kind_of? String
|
253
|
+
|
254
|
+
var.gsub(/:{2}|\//, '_').
|
255
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
256
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
257
|
+
gsub(/\s+/, '_').
|
258
|
+
tr("-", "_").
|
259
|
+
downcase
|
260
|
+
end
|
261
|
+
|
262
|
+
# Predicate method to check if a String is parsable, as JSON
|
263
|
+
#
|
264
|
+
# Parameters
|
265
|
+
# * json +String+
|
266
|
+
#
|
267
|
+
# Returns
|
268
|
+
# +Boolean+
|
269
|
+
#
|
270
|
+
# Notes
|
271
|
+
# * See <tt>from_json</tt>
|
272
|
+
def valid_json?(json)
|
273
|
+
!!from_json(json)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Predicate method to check if a String is a valid URL
|
277
|
+
#
|
278
|
+
# Parameters
|
279
|
+
# * url +String+
|
280
|
+
#
|
281
|
+
# Returns
|
282
|
+
# +Boolean+
|
283
|
+
def valid_url?(url)
|
284
|
+
url =~ /\A#{URI::regexp}\z/
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Smash
|
2
|
+
module CloudPowers
|
3
|
+
module LogicHelp
|
4
|
+
# Sets an Array of instance variables, individually to a value that a
|
5
|
+
# user given block returns.
|
6
|
+
#
|
7
|
+
# Parameters
|
8
|
+
#
|
9
|
+
# * keys +Array+
|
10
|
+
# * * each object will be used as the name for the instance variable that
|
11
|
+
# your block returns
|
12
|
+
# +block+ (optional)
|
13
|
+
# * this block is called for each object in the Array and is used as the value
|
14
|
+
# for the instance variable that is being named and created for each key
|
15
|
+
# Returns +Array+ - each object will either be the result of
|
16
|
+
# <tt>#instance_variable_set(key, value)</tt> => +value+
|
17
|
+
# or instance_variable_get(key)
|
18
|
+
# Example
|
19
|
+
# keys = ['foo', 'bar', 'yo']
|
20
|
+
#
|
21
|
+
# attr_map!(keys) { |key| sleep 1; "#{key}:#{Time.now.to_i}" }
|
22
|
+
# # => ['foo:1475434058', 'bar:1475434059', 'yo:1475434060']
|
23
|
+
#
|
24
|
+
# puts @bar
|
25
|
+
# # => 'bar:1475434059'
|
26
|
+
def attr_map(attributes)
|
27
|
+
attributes = [attributes, nil] unless attributes.respond_to? :map
|
28
|
+
|
29
|
+
attributes.inject(self) do |this, (attribute, before_value)|
|
30
|
+
first_place, second_place = yield attribute, before_value if block_given?
|
31
|
+
|
32
|
+
results = if second_place.nil?
|
33
|
+
[attribute, first_place]
|
34
|
+
else
|
35
|
+
[first_place, second_place]
|
36
|
+
end
|
37
|
+
|
38
|
+
this.instance_variable_set(to_i_var(results.first), results.last)
|
39
|
+
this
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Does its best job at guessing where this method was called from, in terms
|
44
|
+
# of where it is located on the file system. It helps track down where a
|
45
|
+
# project root is etc.
|
46
|
+
#
|
47
|
+
# Returns
|
48
|
+
# +String+
|
49
|
+
#
|
50
|
+
# Notes
|
51
|
+
# * Uses +$0+ to figure out what the current file is
|
52
|
+
def called_from
|
53
|
+
File.expand_path(File.dirname($0))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create an <tt>attr_accessor</tt> feeling getter and setter for an instance
|
57
|
+
# variable. The method doesn't create a getter or setter if it is already
|
58
|
+
# defined.
|
59
|
+
#
|
60
|
+
# Parameters
|
61
|
+
# * base_name +String+ - the name, without the '@' symbol
|
62
|
+
# # ok
|
63
|
+
# add_instance_attr_accessor('my_variable_name', my_value)
|
64
|
+
# => <your_instance @my_variable_name=my_value, ...>
|
65
|
+
# # not ok
|
66
|
+
# add_instance_attr_accessor('@#!!)', my_value)
|
67
|
+
# => <your_instance> <i>no new instance variable found</i>
|
68
|
+
# * value +Object+ - the actual instance variable that matches the +base_name+
|
69
|
+
#
|
70
|
+
# Returns
|
71
|
+
# * the value of the instance variable that matches the +base_name+ (first) argument
|
72
|
+
#
|
73
|
+
# Notes
|
74
|
+
# * if a matching getter or setter method can be found, this method won't
|
75
|
+
# stomp on it. nothing happens, in that case
|
76
|
+
# * if an appropriately named instance variable can't be found, the getter
|
77
|
+
# method will return nil until you set it again.
|
78
|
+
# * <b>it is the responsibility of you and me to make sure our variable names
|
79
|
+
# are valid, i.e. proper Ruby instance variable names
|
80
|
+
def instance_attr_accessor(base_name)
|
81
|
+
i_var_name = to_i_var(base_name)
|
82
|
+
getter_signature = to_snake(base_name)
|
83
|
+
setter_signature = "#{getter_signature}="
|
84
|
+
|
85
|
+
unless respond_to? getter_signature
|
86
|
+
define_singleton_method(getter_signature) do
|
87
|
+
instance_variable_get(i_var_name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
unless respond_to? setter_signature
|
92
|
+
define_singleton_method(setter_signature) do |argument|
|
93
|
+
instance_variable_set(i_var_name, argument)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Lets you retry a piece of logic with 1 second sleep in between attempts
|
99
|
+
# until another bit of logic does what it's supposed to, kind of like
|
100
|
+
# continuing to poll something and doing something when a package is ready
|
101
|
+
# to be taken and processed.
|
102
|
+
#
|
103
|
+
# Parameters
|
104
|
+
# * allowed_attempts +Number+|+Infinity(default)+ - The number of times
|
105
|
+
# the loop should be allowed to...well, loop, before a failed retry occurs.
|
106
|
+
# * &test +Block+ - A predicate method or block of code that is callable
|
107
|
+
# is used to test if the block being retried is successful yet.
|
108
|
+
# * []
|
109
|
+
#
|
110
|
+
# Example
|
111
|
+
# check_stuff = lambda { |params| return true }
|
112
|
+
# smart_retry(3, check_stuff(params)) { do_stuff_that_needs_to_be_checked }
|
113
|
+
def smart_retry(test, allowed_attempts = Float::INFINITY)
|
114
|
+
result = yield if block_given?
|
115
|
+
tries = 1
|
116
|
+
until test.call(result) || tries >= allowed_attempts
|
117
|
+
result = yield if block_given?
|
118
|
+
tries += 1
|
119
|
+
sleep 1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# This method provides a default overrideable message body for things like
|
124
|
+
# basic status updates.
|
125
|
+
#
|
126
|
+
# Parameters
|
127
|
+
# * instanceId +Hash+
|
128
|
+
#
|
129
|
+
# Notes
|
130
|
+
# * camel casing is used on the keys because most other languages prefer
|
131
|
+
# that and it's not a huge problem in ruby. Besides, there's some other
|
132
|
+
# handy methods in this module to get you through those issues, like
|
133
|
+
# +#to_snake()+ and or +#modify_keys_with()+
|
134
|
+
def update_message_body(opts = {})
|
135
|
+
# TODO: Better implementation of merging message bodies and config needed
|
136
|
+
unless opts.kind_of? Hash
|
137
|
+
update = opts.to_s
|
138
|
+
opts = {}
|
139
|
+
opts[:extraInfo] = { message: update }
|
140
|
+
end
|
141
|
+
updated_extra_info = opts.delete(:extraInfo) || {}
|
142
|
+
|
143
|
+
{
|
144
|
+
instanceId: @instance_id || 'none-aquired',
|
145
|
+
type: 'status-update',
|
146
|
+
content: 'running',
|
147
|
+
extraInfo: updated_extra_info
|
148
|
+
}.merge(opts)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|