leeroy_app 0.1.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +40 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +103 -0
  7. data/LICENSE +21 -0
  8. data/README.md +40 -0
  9. data/Rakefile +5 -0
  10. data/bin/leeroy +18 -0
  11. data/hierarchy.md +39 -0
  12. data/leeroy.gemspec +42 -0
  13. data/lib/leeroy/app.rb +132 -0
  14. data/lib/leeroy/env.rb +116 -0
  15. data/lib/leeroy/helpers/aws.rb +403 -0
  16. data/lib/leeroy/helpers/dumpable.rb +72 -0
  17. data/lib/leeroy/helpers/env.rb +42 -0
  18. data/lib/leeroy/helpers/logging.rb +36 -0
  19. data/lib/leeroy/helpers/polling.rb +67 -0
  20. data/lib/leeroy/helpers/state.rb +59 -0
  21. data/lib/leeroy/helpers.rb +9 -0
  22. data/lib/leeroy/state.rb +70 -0
  23. data/lib/leeroy/task/base.rb +61 -0
  24. data/lib/leeroy/task/image.rb +126 -0
  25. data/lib/leeroy/task/instantiate.rb +200 -0
  26. data/lib/leeroy/task/sleep.rb +29 -0
  27. data/lib/leeroy/task/stub.rb +40 -0
  28. data/lib/leeroy/task/terminate.rb +49 -0
  29. data/lib/leeroy/task.rb +17 -0
  30. data/lib/leeroy/types/dash.rb +17 -0
  31. data/lib/leeroy/types/image.rb +53 -0
  32. data/lib/leeroy/types/instance.rb +67 -0
  33. data/lib/leeroy/types/mash.rb +17 -0
  34. data/lib/leeroy/types/packedstring.rb +25 -0
  35. data/lib/leeroy/types/phase.rb +20 -0
  36. data/lib/leeroy/types/semaphore.rb +30 -0
  37. data/lib/leeroy/types/statedata.rb +44 -0
  38. data/lib/leeroy/types/statemetadata.rb +29 -0
  39. data/lib/leeroy/types/userdata.rb +13 -0
  40. data/lib/leeroy/version.rb +3 -0
  41. data/lib/leeroy.rb +7 -0
  42. data/spec/spec_helper.rb +105 -0
  43. data/spec/support/aruba.rb +1 -0
  44. data/spec/use_aruba_with_rspec_spec.rb +11 -0
  45. metadata +333 -0
@@ -0,0 +1,403 @@
1
+ require 'aws-sdk'
2
+ require 'base64'
3
+
4
+ require 'leeroy/helpers'
5
+ require 'leeroy/helpers/env'
6
+
7
+ require 'leeroy/types/instance'
8
+ require 'leeroy/types/mash'
9
+ require 'leeroy/types/semaphore'
10
+
11
+ module Leeroy
12
+ module Helpers
13
+ module AWS
14
+ include Leeroy::Helpers
15
+
16
+ attr :ec2, :s3
17
+
18
+ def initialize(*args, &block)
19
+ super(*args, &block)
20
+
21
+ logger.debug "initializing AWS helpers"
22
+
23
+ @ec2 = Aws::EC2::Client.new
24
+ @s3 = Aws::S3::Client.new
25
+
26
+ logger.debug "AWS helpers initialized"
27
+ end
28
+
29
+ # EC2
30
+
31
+ def ec2Request(method, params = {}, ec2 = self.ec2, options = self.options, global_options = self.global_options)
32
+ begin
33
+ logger.debug "constructing EC2 request for '#{method}'"
34
+ logger.debug "params: #{params.inspect}"
35
+
36
+ params_mash = Leeroy::Types::Mash.new(params)
37
+ params = params_mash
38
+
39
+ dry_run = global_options[:op] ? false : true
40
+
41
+ params.dry_run = dry_run
42
+
43
+ logger.debug "params: #{params.inspect}"
44
+
45
+ resp = ec2.send(method.to_sym, params)
46
+
47
+ logger.debug "resp: #{resp.inspect}"
48
+
49
+ resp
50
+
51
+ rescue StandardError => e
52
+ raise e
53
+ end
54
+ end
55
+
56
+ def getVpcId(vpcname, ec2 = self.ec2)
57
+ begin
58
+ logger.debug "getting VPC ID for '#{vpcname}'"
59
+
60
+ resp = ec2Request(:describe_vpcs, {:filters => [{name: 'tag:Name', values: [vpcname]}]})
61
+ vpcs = resp.vpcs
62
+ logger.debug "vpcs: #{vpcs.inspect}"
63
+
64
+ if vpcs.length < 1
65
+ raise "No VPC found with the name '#{vpcname}'."
66
+ elsif vpcs.length > 1
67
+ raise "Multiple VPCs found with the name '#{vpcname}'."
68
+ else
69
+ vpcid = vpcs[0].vpc_id
70
+ end
71
+
72
+ logger.debug "vpcid: #{vpcid}"
73
+ vpcid
74
+
75
+ rescue Aws::EC2::Errors::DryRunOperation => e
76
+ logger.info e.message
77
+ "DRYRUN_DUMMY_VALUE: #{self.class.to_s}"
78
+
79
+ rescue StandardError => e
80
+ raise e
81
+ end
82
+ end
83
+
84
+ def getSgId(sgname, vpcname, vpcid, ec2 = self.ec2)
85
+ begin
86
+ logger.debug "getting SG ID for '#{sgname}'"
87
+
88
+ resp = ec2Request(:describe_security_groups, {:filters => [{name: 'vpc-id', values: [vpcid]}]})
89
+ security_groups = resp.security_groups
90
+
91
+ # now filter by sgname
92
+ sgmatcher = %r{#{vpcname}-#{sgname}-.*}
93
+ security_group = security_groups.select { |sg| sg.group_name =~ sgmatcher}
94
+ logger.debug "security_group: #{security_group.inspect}"
95
+
96
+ if security_group.length < 1
97
+ raise "No SG found with the name '#{sgname}'."
98
+ elsif security_group.length > 1
99
+ raise "Multiple SGs found with the name '#{sgname}'."
100
+ else
101
+ sgid = security_group[0].group_id
102
+ end
103
+
104
+ logger.debug "sgid: #{sgid}"
105
+ sgid
106
+
107
+ rescue Aws::EC2::Errors::DryRunOperation => e
108
+ logger.info e.message
109
+ "DRYRUN_DUMMY_VALUE: #{self.class.to_s}"
110
+
111
+ rescue StandardError => e
112
+ raise e
113
+ end
114
+ end
115
+
116
+ def getSubnetId(subnetname, vpcid, ec2 = self.ec2)
117
+ begin
118
+ logger.debug "getting Subnet ID for '#{subnetname}'"
119
+
120
+ resp = ec2Request(:describe_subnets, {:filters => [{name: 'vpc-id', values: [vpcid]}, {name: 'tag:Name', values: [subnetname]}]})
121
+ subnets = resp.subnets
122
+ logger.debug "subnets: #{subnets.inspect}"
123
+
124
+ if subnets.length < 1
125
+ raise "No Subnet found with the name '#{subnetname}'."
126
+ elsif subnets.length > 1
127
+ raise "Multiple Subnets found with the name '#{subnetname}'."
128
+ else
129
+ subnetid = subnets[0].subnet_id
130
+ end
131
+
132
+ logger.debug "subnetid: #{subnetid}"
133
+ subnetid
134
+
135
+ rescue Aws::EC2::Errors::DryRunOperation => e
136
+ logger.info e.message
137
+ "DRYRUN_DUMMY_VALUE: #{self.class.to_s}"
138
+
139
+ rescue StandardError => e
140
+ raise e
141
+ end
142
+ end
143
+
144
+ def destroyInstance(state = self.state, env = self.env, ec2 = self.ec2, options = self.options)
145
+ begin
146
+ # did we get instance ID(s)?
147
+ instanceids = options.fetch(:instance, nil)
148
+ if instanceids.nil?
149
+ instanceids = Array(state.instanceid)
150
+ end
151
+
152
+ logger.debug "instanceids: #{instanceids}"
153
+
154
+ run_params = Leeroy::Types::Mash.new
155
+ run_params.instance_ids = instanceids
156
+
157
+ resp = ec2Request(:terminate_instances, run_params)
158
+
159
+ resp.terminating_instances.collect { |i| i.instance_id }.sort
160
+
161
+ rescue Aws::EC2::Errors::DryRunOperation => e
162
+ logger.info e.message
163
+ "DRYRUN_DUMMY_VALUE: #{self.class.to_s}"
164
+
165
+ rescue StandardError => e
166
+ raise e
167
+ end
168
+ end
169
+
170
+
171
+ def createTags(tags = {}, resourceids = [], state = self.state, env = self.env, ec2 = self.ec2, options = self.options)
172
+ begin
173
+ if resourceids.length == 0
174
+ if state.instanceid?
175
+ logger.debug "no resourceids provided for tagging, defaulting to instanceid #{state.instanceid} from state"
176
+ resourceids.push(state.instanceid.to_s)
177
+ end
178
+ end
179
+
180
+ run_params = Leeroy::Types::Mash.new
181
+
182
+ logger.debug "resourceids: #{resourceids}"
183
+ run_params.resources = resourceids
184
+
185
+ tag_array = tags.collect {|key,value| {'key' => key, 'value' => value}}
186
+
187
+ logger.debug "tags: #{tags}"
188
+ logger.debug "tag_array: #{tag_array}"
189
+ run_params.tags = tag_array
190
+
191
+ resp = ec2Request(:create_tags, run_params)
192
+
193
+ rescue Aws::EC2::Errors::DryRunOperation => e
194
+ logger.info e.message
195
+ "DRYRUN_DUMMY_VALUE: #{self.class.to_s}"
196
+
197
+ rescue StandardError => e
198
+ raise e
199
+ end
200
+ end
201
+
202
+ def filterImages(selector, collector = lambda { |x| x }, state = self.state, env = self.env, ec2 = self.ec2, options = self.options)
203
+ begin
204
+ run_params = Leeroy::Types::Mash.new
205
+
206
+ run_params.owners = ['self']
207
+
208
+ resp = ec2Request(:describe_images, run_params)
209
+
210
+ # now filter based on callback
211
+ resp.images.select {|x| selector.call(x)}.collect {|x| collector.call(x)}
212
+
213
+ rescue Aws::EC2::Errors::DryRunOperation => e
214
+ logger.info e.message
215
+ "DRYRUN_DUMMY_VALUE: #{self.class.to_s}"
216
+
217
+ rescue StandardError => e
218
+ raise e
219
+ end
220
+ end
221
+
222
+ def getGoldMasterInstanceName(env_name = 'LEEROY_GOLD_MASTER_NAME')
223
+ checkEnv(env_name)
224
+ end
225
+
226
+ def getApplicationInstanceName(index, env_app = 'LEEROY_APP_NAME', env_name = 'LEEROY_BUILD_TARGET')
227
+ name_prefix = [checkEnv(env_app), checkEnv(env_name), index].join('-')
228
+ logger.debug "name_prefix: #{name_prefix}"
229
+
230
+ if index.nil?
231
+ # determine the index by looking at existing images
232
+ selector = lambda {|image| image.name =~ /^#{name_prefix}/}
233
+ # and extract the names
234
+ collector = lambda {|image| image.name}
235
+
236
+ image_names = filterImages(selector, collector)
237
+ logger.debug image_names.awesome_inspect
238
+ end
239
+
240
+ end
241
+
242
+ def getGoldMasterImageIndex(env_prefix = 'LEEROY_GOLD_MASTER_IMAGE_PREFIX')
243
+ name_prefix = checkEnv(env_prefix)
244
+ logger.debug "name_prefix: #{name_prefix}"
245
+
246
+ # determine the index by looking at existing images
247
+ selector = lambda {|image| image.name =~ /^#{name_prefix}/}
248
+ # and extract the names
249
+ collector = lambda {|image| image.name}
250
+
251
+ image_names = filterImages(selector, collector)
252
+ image_numbers = image_names.collect do |name|
253
+ if name =~ /(\d+)$/
254
+ image_number = $1.to_i
255
+ end
256
+ end
257
+
258
+ latest_image = image_numbers.sort.compact.uniq.pop
259
+ logger.debug "latest_image: #{latest_image}"
260
+
261
+ latest_image
262
+
263
+ end
264
+
265
+ # S3
266
+
267
+ def s3Request(method, params = {}, s3 = self.s3, options = self.options, global_options = self.global_options)
268
+ begin
269
+ logger.debug "constructing S3 request for '#{method}'"
270
+
271
+ params_mash = Leeroy::Types::Mash.new(params)
272
+ params = params_mash
273
+
274
+ logger.debug "params: #{params.inspect}"
275
+
276
+ resp = s3.send(method.to_sym, params)
277
+
278
+ logger.debug "resp: #{resp.inspect}"
279
+
280
+ resp
281
+
282
+ rescue StandardError => e
283
+ raise e
284
+ end
285
+ end
286
+
287
+ def buildS3ObjectName(key, type, prefixes = Leeroy::Env::S3_PREFIXES)
288
+ begin
289
+ logger.debug "building S3 prefix (key: #{key}, type: #{type})"
290
+ pfx = Leeroy::Types::Mash.new(prefixes)
291
+ root = pfx.jenkins
292
+ prefix = pfx.fetch(type,type)
293
+
294
+ # FIXME i should do this with URI
295
+ [root, prefix, key].join('/')
296
+
297
+ rescue StandardError => e
298
+ raise e
299
+ end
300
+ end
301
+
302
+ def genSemaphore(object, payload = '', bucket = checkEnv('LEEROY_S3_BUCKET'))
303
+ begin
304
+ logger.debug "creating a semaphore"
305
+
306
+ semaphore = Leeroy::Types::Semaphore.new(bucket: bucket, object: object, payload: payload)
307
+ logger.debug "semaphore: #{semaphore}"
308
+
309
+ semaphore
310
+
311
+ rescue StandardError => e
312
+ raise e
313
+ end
314
+ end
315
+
316
+ def setSemaphore(semaphore)
317
+ begin
318
+ unless semaphore.kind_of?(Leeroy::Types::Semaphore)
319
+ semaphore = Leeroy::Types::Semaphore.new(semaphore)
320
+ end
321
+
322
+ logger.debug "setting a semaphore"
323
+
324
+ run_params = Leeroy::Types::Mash.new
325
+
326
+ run_params.body = semaphore.payload
327
+ run_params.bucket = semaphore.bucket
328
+ run_params.key = semaphore.object
329
+
330
+ resp = s3Request(:put_object, run_params)
331
+
332
+ semaphore
333
+
334
+ rescue StandardError => e
335
+ raise e
336
+ end
337
+ end
338
+
339
+ def clearSemaphore(semaphore)
340
+ begin
341
+ logger.debug "semaphore.class: #{semaphore.class}"
342
+
343
+ if semaphore.kind_of?(Leeroy::Types::Semaphore)
344
+ logger.debug "received a semaphore, continuing"
345
+ else
346
+ logger.debug "did not receive a semaphore, initializing"
347
+ semaphore = Leeroy::Types::Semaphore.new(semaphore)
348
+ end
349
+
350
+ run_params = Leeroy::Types::Mash.new
351
+ run_params.bucket = semaphore.bucket
352
+ run_params.key = semaphore.object
353
+
354
+ # is the object present in S3?
355
+ resp = checkSemaphore(semaphore)
356
+
357
+ if checkSemaphore(semaphore)
358
+ logger.debug "#{semaphore} present, deleting"
359
+ resp = s3Request(:delete_object, run_params)
360
+ else
361
+ logger.debug "#{semaphore} not present, continuing"
362
+ end
363
+
364
+ semaphore
365
+
366
+ rescue StandardError => e
367
+ raise e
368
+ end
369
+ end
370
+
371
+ def checkSemaphore(semaphore)
372
+ begin
373
+ unless semaphore.kind_of?(Leeroy::Types::Semaphore)
374
+ semaphore = Leeroy::Types::Semaphore.new(semaphore)
375
+ end
376
+
377
+ run_params = Leeroy::Types::Mash.new
378
+ run_params.bucket = semaphore.bucket
379
+ run_params.key = semaphore.object
380
+
381
+ # is the object present in S3?
382
+ logger.debug "checking for presence of #{semaphore}"
383
+ resp = s3Request(:head_object, run_params)
384
+
385
+ if resp.delete_marker.nil?
386
+ resp
387
+ else
388
+ logger.debug "#{semaphore} already deleted"
389
+ nil
390
+ end
391
+
392
+ rescue Aws::S3::Errors::NotFound => e
393
+ logger.debug "#{semaphore} not found"
394
+ nil
395
+
396
+ rescue StandardError => e
397
+ raise e
398
+ end
399
+ end
400
+
401
+ end
402
+ end
403
+ end
@@ -0,0 +1,72 @@
1
+ require 'multi_json'
2
+
3
+ require 'leeroy/types/mash'
4
+ require 'leeroy/helpers/logging'
5
+
6
+ module Leeroy
7
+ module Helpers
8
+ module Dumpable
9
+
10
+ attr_accessor :dump_properties
11
+
12
+ def dump
13
+ begin
14
+ logger.debug "beginning dump"
15
+
16
+ dump_mash = Leeroy::Types::Mash.new
17
+
18
+ dump_properties = self.dump_properties
19
+ logger.debug "dump_properties: #{dump_properties.inspect}"
20
+
21
+ if dump_properties.length == 0
22
+ logger.warn "dumping an object with no dump_properties set, this is unlikely to end well"
23
+ end
24
+
25
+ self.dump_properties.each do |property|
26
+ logger.debug "dumping property '#{property.to_s}'"
27
+ if self.respond_to?(property.to_sym)
28
+ begin
29
+ raw = self.fetch(property.to_s)
30
+
31
+ # logger.debug "raw: #{raw.inspect}"
32
+
33
+ cooked = raw.respond_to?(:dumper) ? raw.dumper : raw
34
+
35
+ # logger.debug "cooked: #{cooked.inspect}"
36
+
37
+ dump_mash.store(property.to_s, cooked)
38
+
39
+ rescue KeyError => e
40
+ logger.debug e.message
41
+ end
42
+ end
43
+ end
44
+
45
+ logger.debug "dump_mash: #{dump_mash.inspect}"
46
+
47
+ MultiJson.dump(dump_mash.to_hash)
48
+
49
+ rescue StandardError => e
50
+ raise e
51
+ end
52
+ end
53
+
54
+ def dumper
55
+ begin
56
+ dump_hash = {}
57
+
58
+ self.dump_properties.each do |property|
59
+ the_prop = self.send(property.to_sym)
60
+ dump_hash[property] = the_prop.respond_to?(:dumper) ? the_prop.dumper : the_prop
61
+ end
62
+
63
+ dump_hash
64
+
65
+ rescue StandardError => e
66
+ raise e
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,42 @@
1
+ require 'awesome_print'
2
+
3
+ require 'leeroy/env'
4
+ require 'leeroy/helpers'
5
+
6
+ module Leeroy
7
+ module Helpers
8
+ module Env
9
+ include Leeroy::Helpers
10
+
11
+ attr_reader :env
12
+
13
+ def checkEnv(param, check = lambda { |x| ! x.nil? }, errmsg = "You must provide #{param} in the environment.", env = self.env)
14
+ begin
15
+ logger.debug "checking for '#{param}' in environment"
16
+
17
+ # get param from env
18
+ candidate = env.fetch(param, nil)
19
+ logger.debug "candidate: #{candidate}"
20
+
21
+ # check it against the check
22
+ check_passed = check.call(candidate)
23
+ logger.debug "check_passed: #{check_passed}"
24
+
25
+ if check_passed
26
+ candidate
27
+ else
28
+ raise errmsg
29
+ end
30
+
31
+ rescue NoMethodError => e
32
+ logger.error "unable to read environment! env: #{env.inspect}"
33
+ raise e
34
+
35
+ rescue StandardError => e
36
+ raise e
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ require 'yell'
2
+ require 'yell-adapters-syslog'
3
+
4
+ require 'leeroy/helpers'
5
+ require 'leeroy/helpers/env'
6
+
7
+ module Leeroy
8
+ module Helpers
9
+ module Logging
10
+ include Leeroy::Helpers
11
+
12
+ # constants
13
+ TRUNCATE_THRESHOLD = 60
14
+
15
+ TRACE_FORMAT = "%d [%5L] %p (%M): %m"
16
+ TRACE_LEVELS = [:debug]
17
+
18
+ # define a logger
19
+ # Yell.new :stderr, name: self.class.to_s, format: TRACE_FORMAT, trace: TRACE_LEVELS, level: :debug
20
+ if ENV['ENVIRONMENT'] == 'production'
21
+ Yell.new :syslog, name: self.class.to_s, format: TRACE_FORMAT, trace: TRACE_LEVELS, level: :info, facility: :user
22
+ else
23
+ Yell.new :stderr, name: self.class.to_s, format: TRACE_FORMAT, trace: TRACE_LEVELS, level: :debug
24
+ end
25
+
26
+ # make this class loggable
27
+ self.class.send :include, Yell::Loggable
28
+
29
+ # make logger an instance method
30
+ def logger
31
+ self.class.logger
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,67 @@
1
+ require 'smart_polling'
2
+
3
+ require 'leeroy/helpers'
4
+ require 'leeroy/helpers/env'
5
+ require 'leeroy/types/mash'
6
+
7
+ module Leeroy
8
+ module Helpers
9
+ module Polling
10
+ include Leeroy::Helpers
11
+ include Leeroy::Helpers::Env
12
+
13
+ POLL_CALLBACK = lambda {|x| raise 'this is the default callback, did you forget to set the poll_callback attribute?'}
14
+ POLL_TIMEOUT = 600 # seconds
15
+ POLL_INTERVAL = 10 # seconds
16
+
17
+ attr_accessor :poll_callback, :poll_timeout, :poll_interval, :poll_response
18
+
19
+ def poll(*args)
20
+ begin
21
+ logger.debug "beginning to poll"
22
+
23
+ callback = self.poll_callback
24
+ raise "callback must be a Proc" unless callback.kind_of?(Proc)
25
+
26
+ timeout = self.poll_timeout
27
+ interval = self.poll_interval
28
+
29
+ logger.debug "callback: #{callback.inspect}"
30
+ logger.debug "polling every #{interval} seconds for #{timeout} seconds"
31
+
32
+ SmartPolling.poll(timeout: timeout, interval: interval) do
33
+ poll_arg = args[0]
34
+ logger.debug "poll_arg: #{poll_arg.inspect}"
35
+ self.poll_response = callback.call(poll_arg)
36
+ end
37
+
38
+ response = self.poll_response
39
+ logger.debug "response: #{response.inspect}"
40
+
41
+ response
42
+
43
+ rescue Interrupt => e
44
+ logger.fatal "Keyboard interrupt"
45
+ raise e
46
+
47
+ rescue StandardError => e
48
+ raise e
49
+ end
50
+ end
51
+
52
+ def initialize(*args, &block)
53
+ begin
54
+ super
55
+
56
+ @poll_callback = POLL_CALLBACK
57
+ @poll_timeout = POLL_TIMEOUT
58
+ @poll_interval = POLL_INTERVAL
59
+
60
+ rescue StandardError => e
61
+ raise e
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,59 @@
1
+ require 'fcntl'
2
+ require 'multi_json'
3
+
4
+ require 'leeroy/helpers'
5
+
6
+ module Leeroy
7
+ module Helpers
8
+ module State
9
+ include Leeroy::Helpers
10
+
11
+ attr_accessor :state
12
+
13
+ def state_from_pipe(state = {}, global_options = self.global_options)
14
+ begin
15
+ state.merge(load_state)
16
+
17
+ rescue StandardError => e
18
+ raise e
19
+ end
20
+ end
21
+
22
+ def load_state
23
+ begin
24
+ logger.debug "loading state from stdin if available"
25
+
26
+ _stdin? ? MultiJson.load($stdin.read, :symbolize_keys => true) : {}
27
+
28
+ rescue StandardError => e
29
+ raise e
30
+ end
31
+ end
32
+
33
+ def dump_state
34
+ logger.debug "dumping state to stdout"
35
+ $stdout.puts self.state.dump
36
+ end
37
+
38
+ def rotate_task_metadata
39
+ logger.debug "rotating task metadata"
40
+ if self.state.metadata.task?
41
+ self.state.metadata.previous = self.state.metadata.task
42
+ end
43
+ self.state.metadata.task = self.class.to_s
44
+ end
45
+
46
+ def to_s
47
+ "#{self.metadata},#{self.data}"
48
+ end
49
+
50
+ private
51
+
52
+ # this is preposterous BS and doubtless not portable to Windows
53
+ def _stdin?
54
+ $stdin.fcntl(Fcntl::F_GETFL, 0) == 0
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,9 @@
1
+ require 'leeroy'
2
+
3
+ module Leeroy
4
+ module Helpers
5
+
6
+ private
7
+
8
+ end
9
+ end