-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFozHelpers.rb
More file actions
779 lines (622 loc) · 18 KB
/
FozHelpers.rb
File metadata and controls
779 lines (622 loc) · 18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
# This is a collection of useful hacks, monkeypatches, class extensions, overrides, etc.
require 'rubygems'
require 'enumerator'
require 'rss'
require 'open-uri'
require 'net/http'
require 'etc'
require 'fileutils'
require 'term/ansicolor'
####
#### Useful constants
####
EMAIL_REGEXP = Regexp.new(/[^ @]+@[^ @]+\.[^ @]+/)
#####
##### Generic monkeypatches to extend existing base classes
#####
class String
# Give us the ability to do things like puts 'foo'.red.bold
include Term::ANSIColor
def shift
return nil if self.empty?
item=self[0]
self.sub!(/^./,"")
return nil if item.nil?
item.chr
end
def unshift(other)
newself = other.to_s.dup.pop.to_s + self
self.replace(newself)
end
def pop
return nil if self.empty?
item=self[-1]
self.chop!
return nil if item.nil?
item.chr
end
def push(other)
newself = self + other.to_s.dup.shift.to_s
self.replace(newself)
end
def rotate_left(n=1)
n=1 unless n.kind_of? Integer
n.times do
char = self.shift
self.push(char)
end
self
end
def rotate_right(n=1)
n=1 unless n.kind_of? Integer
n.times do
char = self.pop
self.unshift(char)
end
self
end
@@first_word_re = /^(\w+\W*)/
@@last_word_re = /(\w+\W*)$/
def shift_word
# shifts first word off of self
# and returns; changes self
return nil if self.empty?
self=~@@first_word_re
newself = $' || "" # $' is POSTMATCH
self.replace(newself) unless $`.nil?
$1.strip
end
def acronym(thresh=0)
self.split.find_all {|w| w.length > thresh }.
collect {|w| w[0,1].upcase}.join
end
def unshift_word(other)
# Adds provided string to front of self
newself = other.to_s + " " + self
self.replace(newself)
end
def pop_word
# pops and returns last word off self
# changes self
return nil if self.empty?
self=~@@last_word_re
newself=$` || "" # $` is PREMATCH
newself.rstrip!
self.replace(newself) unless $`.nil?
$1
end
def push_word(other)
# pushes provided string onto end of self
newself = self + " " + other.to_s
self.replace(newself)
end
def rotate_word_left
word = self.shift_word
self.push_word(word)
end
def rotate_word_right
word = self.pop_word
self.unshift_word(word)
end
# Patch the string class to give a rightstr function
# Call with String.rightstr(length)
def rightstr(length)
if length < 0
raise ArgumentError, "length cannot be a negative value"
end
self.reverse[0,length].reverse
end
# Create an each_char iterator that will return 'n' characters each yield
def each_char(n=1)
if n < 1
raise ArgumentError, "length must be > 0"
end
if block_given?
split(//).each_slice(n) do |x|
yield x.join
end
else
results = Array.new
split(//).each_slice(n) {|x| results.push(x.join)}
results
end
end
def blank?
!(self =~ /\S/)
end
# add a coerce function to the String class so we can get automatic type conversion for arithmetic
# which makes stuff like 1 + "2" work without having to cast
def coerce(other)
case other
when Integer
begin
return other, Integer(self)
rescue
return Float(other), Float(self)
end
when Float
return other, Float(self)
else
super
end
end
end
class Fixnum
def odd?
(self % 2 == 1) ? true : false
end
end
class Array
# Snag a random element from an array
def rand
self[Kernel::rand(self.size)]
end
# Shuffle the array
def randomize
sort {|a,b| Kernel::rand <=> 0.5}
end
def randomize!
replace randomize
end
end
class Numeric
# Time calculations
# i.e. Numeric.minutes, etc
# These are ripped out of ActiveSupport and give us things like 2.hours.since(4.hours.ago) and 5.minutes.from_now
def seconds
self
end
def minutes
self * 60
end
alias :minute :minutes
def hours
self * 60.minutes
end
alias :hour :hours
def days
self * 24.hours
end
alias :day :days
def weeks
self * 7.days
end
alias :week :weeks
def ago(time = Time.now)
time - self
end
alias :until :ago
def since(time = Time.now)
time + self
end
alias :from_now :since
end
class Time
# Monkeypatch the time class to include other conversions
def internet_time
t = self.gmtime + 3600 # Biel, Switzerland
midnight = Time.gm(t.year, t.month, t.day)
secs = t - midnight
beats = (secs/86.4).to_i
end
def timestamp
self.strftime("%D %T")
end
def iso8601
# Generate an ISO-8601 formatted date string in UTC, offsetDays from now yyyy-mm-dd'T'HH:MM:SS.fff'Z'.
self.w3cdtf[/^.+\.\d+/] + "Z"
end
end
#####
##### Various helpful classes
#####
module Helpers
## basic doubly-linked list
class LinkedList
include Enumerable
ListElem = Struct.new(:obj, :prev, :next)
attr_accessor :head, :tail
def initialize(*enum)
@head = @tail = ListElem.new
@head.next = @head
@head.prev = @head
(enum.size == 1 && Enumerable === enum[0] ?
enum[0] :
enum).each {|e| append e}
end
def append(e)
tmp = ListElem.new(e, @tail.prev, @tail)
tmp.prev.next = tmp
tmp.next.prev = tmp
self
end
def prepend(e)
tmp = ListElem.new(e, @head, @head.next)
tmp.prev.next = tmp
tmp.next.prev = tmp
self
end
# return the head and remove it from the list
def pop
val = @head.next.obj
@head.next = @head.next.next
@head.next.prev = @head
val
end
# return the tail and remove it from the list
def shift
val = @tail.prev.obj
@tail.prev = @tail.prev.prev
@tail.prev.next = @tail
val
end
def length
self.collect.length
end
def [](index)
if index >= self.length or index < 0
raise RangeError
end
node = @head.next
index.times do
node = node.next
end
node.obj
end
def []=(index, value)
if index >= self.length or index < 0
raise RangeError
end
node = @head.next
index.times do
node = node.next
end
node.obj = value
value
end
def to_a
self.collect
end
alias :<< :append
alias :count :length
alias :size :length
alias :push :prepend
def each
node = @head.next
while node != @tail
yield node.obj
node = node.next
end
end
def reverse
node = @head.next
# walk through swapping the next and previous pointers for each node
while node != @tail
next_node = node.next
node.next, node.prev = node.prev, node.next
node = next_node
end
# and now repoint the head and tail
@head.next, @tail.prev = @tail.prev, @head.next
self
end
def clear
while self.length > 0
self.pop
end
nil
end
end
class Task < Object
attr_accessor :name, :status, :percent, :start_time, :end_time
def initialize(name=nil, *options)
if options.empty?
options = {}
else
options = options[0]
end
@name = name
@status = options[:status] || 'pending'
@percent = options[:percent] || 0
@start_time = options[:start_time] || ''
@end_time = options[:end_time] || ''
end
def to_s
"#{@name}\t#{@status}\t#{@percent}\t#{@start_time}\t#{@end_time}"
end
def start
# Mark the current task as active"
@status = 'active'
@start_time = Time.now.iso8601
@end_time = ''
@percent = 0
self
end
def finish
# Mark the task as complete
@status = 'complete'
@percent = 100
now = Time.now.iso8601
if @start_time.blank?
@start_time = now
end
@end_time = now
return self
end
def fail
# Mark the task as failed
@status = 'failed'
@end_time = Time.now.iso8601
self
end
def clear
# Clear the task and set it to pending
@status = 'pending'
@percent = 0
@start_time = ''
@end_time = ''
self
end
end
class Tasklist
protected
attr_writer :current_task
public
attr_accessor :filename
# getters
def current_task
@tasks[@current_task]
end
def status
@status
end
# constructors, iterators, etc.
def initialize(filename=nil)
@tasks = []
@current_task = -1
@filename = filename
@status = 0
self
end
def each
@tasks.each { |task| yield task }
end
def each_with_index
@tasks.each_with_index {|task, index| yield [task, index] }
end
alias :with_index :each_with_index
def to_s
@tasks * "\n"
end
# class methods
def add(task)
if task.instance_of? String
t = Task.new(task)
elsif task.instance_of? Task
t = task
else
raise TypeError, "Can only add tasks to tasklist"
end
@tasks << t
self
end
def delete(task_number=@current_task)
if @tasks.empty?
# Should throw an exception here
raise ArgumentError, "attempt to delete from an empty tasklist"
end
if task_number > @tasks.length - 1 or task_number < 0
# throw an exception
raise ArgumentError, "attempt to delete an invalid task"
end
if task_number == @current_task
self.next
@current_task -= 1
end
@tasks.delete_at(task_number)
self.save
@status = (@current_task + 1.0 / @tasks.length * 100).to_i
self
end
def next
if @current_task != -1
@tasks[@current_task].finish
@status = (@current_task + 1.0 / @tasks.length * 100).to_i
end
@current_task += 1
if @current_task < @tasks.length
@tasks[@current_task].start
else
@current_task = -1
end
self.save
self
end
def abort
# mark the current task as failed and reset the task pointer
if @current_task == -1
self
end
@tasks[@current_task].fail
@current_task = -1
self.save
self
end
def finish
# mark all outstanding jobs as finished by incrementing through the task list
true while self.next
self.save
self
end
def start
# Reset all the tasks in the queue to pending and move the task pointer to the beginning
@tasks.collect! {|x| x.clear}
@current_task = 0
@status = 0
@tasks[@current_task].start
self.save
self
end
def clear
@tasks = []
@current_task = -1
self
end
def save(filename=nil)
# Persist ourself to a filename
filename = @filename if filename.nil?
if not filename.nil?
marshal = @current_task.to_s + "\n"
marshal << self.to_s
File.open(filename, "w") do |fd|
fd.write(marshal)
end
end
self
end
def load(filename=nil)
filename = @filename if filename.nil?
if not filename.nil?
rep = self.class.new
File.open(filename, "r") do |fd|
rep.current_task = fd.gets.to_i
while line = fd.gets
line.chomp!
(name, status, percent, start_time, end_time) = line.split("\t")
rep.add(Task.new(name, :status=>status, :percent=>percent, :start_time=>start_time, :end_time=>end_time))
end
end
rep
end
end
def load!(filename=nil)
filename = @filename if filename.nil?
if not filename.nil?
File.open(filename, "r") do |fd|
clear
@current_task = fd.gets.to_i
while line = fd.gets
line.chomp!
(name, status, percent, start_time, end_time) = line.split("\t")
self.add(Task.new(name, :status=>status, :percent=>percent, :start_time=>start_time, :end_time=>end_time))
end
end
self
end
end
end
#####
##### Generic (non class) methods attached directly to Helpers
#####
def self.fetch_remote_file(url, *options)
if options.empty?
options = {}
else
options = options[0]
end
local_file = options[:local_file] || nil
permissions = options[:permissions] || nil
owner = options[:owner] || nil
group = options[:group] || nil
# Fetch a remote file and either write it to a local file or return it
response = URI::parse(url)
ifd = response.open()
if local_file.nil?
filecontents = ifd.read()
ifd.close()
filecontents
else
ofd = open(local_file, "w")
bytes = File::copy_stream(ifd, ofd)
ifd.close()
ofd.chmod(permissions)
ofd.close()
self.chown(local_file, owner, group)
end
bytes
end
def self.get_ec2_public_hostname
self.fetch_remote_file("http://169.254.169.254/2009-04-04/meta-data/public-hostname")
end
def self.write_file(filename, contents, *options)
if options.empty?
options = {}
else
options = options[0]
end
owner = options[:owner] || nil
group = options[:group] || nil
permissions = options[:permissions] || nil
close = false
if filename.instance_of?(String)
# we got passed a filename to create
fd = File.new(filename, "w")
# if we create the file, we close it, otherwise we leave it open
close = true
elsif filename.instance_of?(File)
# we got passed an open file descriptor
fd = filename
else
raise TypeError, "filename must be a file object or a filename (string)"
end
fd << contents
if permissions
fd.chmod(permissions)
end
unless owner.nil? and group.nil?
self.chown(fd, owner, group)
end
if close
fd.close()
end
return true
end
def self.chown(filename, owner, group)
uid = Etc.getpwnam(owner).uid unless owner.nil?
gid = Etc.getgrnam(group).gid unless group.nil?
if filename.instance_of? String
FileUtils.chown(uid, gid, filename)
elsif filename.instance_of? File
filename.chown(uid, gid)
else
raise TypeError, "invalid filetype (must be open fd or string)"
end
return true
end
def self.run_cmd(cmd)
results = %x[cmd]
$?
end
def self.get_progress_log_name
me = File::basename($0)[/^.*?(?=\.template)/]
"/opt/nodeagent/handlers/state/#{me}.status"
end
def self.add_user(username, *options)
if options.empty?
options = {}
else
options = options[0]
end
shell = options[:shell] || nil
home = options[:home] || nil
create_home = options[:create_home] || false
cmd = "useradd "
cmd += "-s #{shell} " unless shell.nil?
cmd += "-d #{homeDir} " unless home.nil?
cmd += (create_home) ? "-m " : "-M "
cmd += username
self.run_cmd(cmd)
end
def self.install_packages(packages)
if packages.instance_of? String
packages = [packages]
for pkg in packages
rc = self.run_cmd("! /usr/bin/yum install -y --nogpgcheck %s | egrep '^No package .+ available\.' 2>&1 >/dev/null " % (pkg))
if rc
return rc
end
end
true
end
end
end