nodectl 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +4 -0
  6. data/README.md +3 -0
  7. data/Rakefile +1 -0
  8. data/TODO.md +7 -0
  9. data/assets/javascript/application.coffee +22 -0
  10. data/assets/javascript/bootstrap.js +2006 -0
  11. data/assets/javascript/controllers/application_controller.coffee +73 -0
  12. data/assets/javascript/controllers/service_controller.coffee +62 -0
  13. data/assets/javascript/event_handler.coffee +51 -0
  14. data/assets/javascript/models/action.coffee +28 -0
  15. data/assets/javascript/models/instance.coffee +7 -0
  16. data/assets/javascript/models/service.coffee +75 -0
  17. data/assets/javascript/models/version.coffee +3 -0
  18. data/assets/javascript/notifier.coffee +64 -0
  19. data/assets/javascript/router.coffee +3 -0
  20. data/assets/javascript/routes/application.coffee +6 -0
  21. data/assets/javascript/routes/log.coffee +4 -0
  22. data/assets/javascript/routes/service.coffee +3 -0
  23. data/assets/javascript/store.coffee +32 -0
  24. data/assets/javascript/templates/application.hbs +82 -0
  25. data/assets/javascript/templates/index.hbs +1 -0
  26. data/assets/javascript/templates/log.hbs +1 -0
  27. data/assets/javascript/templates/service.hbs +110 -0
  28. data/assets/javascript/templates/views/action.hbs +20 -0
  29. data/assets/javascript/templates/views/actions_list.hbs +26 -0
  30. data/assets/javascript/templates/views/environments_list.hbs +9 -0
  31. data/assets/javascript/templates/views/install_button.hbs +1 -0
  32. data/assets/javascript/templates/views/mass_run.hbs +30 -0
  33. data/assets/javascript/templates/views/notifier_control.hbs +7 -0
  34. data/assets/javascript/templates/views/param.hbs +6 -0
  35. data/assets/javascript/templates/views/terminal.hbs +8 -0
  36. data/assets/javascript/ui.coffee +24 -0
  37. data/assets/javascript/vendor/ansiparse.js +186 -0
  38. data/assets/javascript/vendor/audio.min.js +24 -0
  39. data/assets/javascript/vendor/audiojs.swf +0 -0
  40. data/assets/javascript/vendor/ember-data.js +10528 -0
  41. data/assets/javascript/vendor/ember.js +40583 -0
  42. data/assets/javascript/vendor/handlebars.js +2746 -0
  43. data/assets/javascript/vendor/jquery.js +8829 -0
  44. data/assets/javascript/vendor/ladda.js +157 -0
  45. data/assets/javascript/vendor/moment.min.js +6 -0
  46. data/assets/javascript/vendor/notify-osd.js +319 -0
  47. data/assets/javascript/vendor/player-graphics.gif +0 -0
  48. data/assets/javascript/vendor/spin.js +218 -0
  49. data/assets/javascript/views/action_view.coffee +18 -0
  50. data/assets/javascript/views/actions_list_view.coffee +2 -0
  51. data/assets/javascript/views/environments_list_view.coffee +2 -0
  52. data/assets/javascript/views/install_button_view.coffee +34 -0
  53. data/assets/javascript/views/mass_run_view.coffee +107 -0
  54. data/assets/javascript/views/notifier_control_view.coffee +29 -0
  55. data/assets/javascript/views/terminal_view.coffee +56 -0
  56. data/assets/stylesheets/application.css +137 -0
  57. data/assets/stylesheets/bootstrap-theme.css +347 -0
  58. data/assets/stylesheets/bootstrap.css +5785 -0
  59. data/assets/stylesheets/fonts/glyphicons-halflings-regular.eot +0 -0
  60. data/assets/stylesheets/fonts/glyphicons-halflings-regular.svg +229 -0
  61. data/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf +0 -0
  62. data/assets/stylesheets/fonts/glyphicons-halflings-regular.woff +0 -0
  63. data/assets/stylesheets/ladda-themeless.css +330 -0
  64. data/assets/stylesheets/ladda.css +392 -0
  65. data/assets/stylesheets/notify-osd.css +49 -0
  66. data/assets/stylesheets/terminal.css +12 -0
  67. data/bin/nodectl +4 -0
  68. data/lib/nodectl.rb +137 -0
  69. data/lib/nodectl/action.rb +80 -0
  70. data/lib/nodectl/binding.rb +13 -0
  71. data/lib/nodectl/cli.rb +123 -0
  72. data/lib/nodectl/context.rb +34 -0
  73. data/lib/nodectl/database.rb +55 -0
  74. data/lib/nodectl/generators/init.rb +22 -0
  75. data/lib/nodectl/generators/templates/init/.gitignore +5 -0
  76. data/lib/nodectl/generators/templates/init/config/manifest.yml +1 -0
  77. data/lib/nodectl/generators/templates/init/config/server.yml +7 -0
  78. data/lib/nodectl/instance.rb +100 -0
  79. data/lib/nodectl/log.rb +13 -0
  80. data/lib/nodectl/manager.rb +71 -0
  81. data/lib/nodectl/multi_io.rb +16 -0
  82. data/lib/nodectl/options.rb +47 -0
  83. data/lib/nodectl/process.rb +145 -0
  84. data/lib/nodectl/promised_file.rb +37 -0
  85. data/lib/nodectl/recipe.rb +197 -0
  86. data/lib/nodectl/repository.rb +80 -0
  87. data/lib/nodectl/server.rb +103 -0
  88. data/lib/nodectl/service.rb +126 -0
  89. data/lib/nodectl/stream/buffer.rb +44 -0
  90. data/lib/nodectl/stream/events_session.rb +39 -0
  91. data/lib/nodectl/stream/file.rb +69 -0
  92. data/lib/nodectl/stream/file_session.rb +35 -0
  93. data/lib/nodectl/stream/file_with_memory.rb +17 -0
  94. data/lib/nodectl/stream/websocket.rb +90 -0
  95. data/lib/nodectl/version.rb +3 -0
  96. data/lib/nodectl/watchdog.rb +17 -0
  97. data/lib/nodectl/webapp/actions.rb +21 -0
  98. data/lib/nodectl/webapp/base.rb +29 -0
  99. data/lib/nodectl/webapp/instances.rb +46 -0
  100. data/lib/nodectl/webapp/public/sounds/alert.mp3 +0 -0
  101. data/lib/nodectl/webapp/public/sounds/error.mp3 +0 -0
  102. data/lib/nodectl/webapp/public/sounds/question.mp3 +0 -0
  103. data/lib/nodectl/webapp/services.rb +39 -0
  104. data/lib/nodectl/webapp/settings.rb +7 -0
  105. data/lib/nodectl/webapp/ui.rb +19 -0
  106. data/lib/nodectl/webapp/versions.rb +12 -0
  107. data/lib/nodectl/webapp/views/ui.haml +20 -0
  108. data/nodectl.gemspec +35 -0
  109. data/spec/spec_helper.rb +12 -0
  110. data/spec/stream/buffer_spec.rb +33 -0
  111. metadata +365 -0
@@ -0,0 +1,22 @@
1
+ class Nodectl::Generators::Init < Thor::Group
2
+ include Thor::Actions
3
+
4
+ argument :host_path
5
+
6
+ def self.source_root
7
+ File.join(File.dirname(__FILE__), "templates", "init")
8
+ end
9
+
10
+ def self.destination_root
11
+ host_path
12
+ end
13
+
14
+ def copy_template
15
+ directory "./", host_path
16
+ end
17
+
18
+ def database
19
+ db = Nodectl::Database.new(File.join(host_path, "nodectl.db"))
20
+ db.reset
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ src/
2
+ log/
3
+ tmp/
4
+ nodectl.db.dir
5
+ nodectl.db.pag
@@ -0,0 +1 @@
1
+ # Write service definitions here
@@ -0,0 +1,7 @@
1
+ http_host:
2
+ http_port: 8080
3
+ websocket_port: 8090
4
+ websocket_host: 0.0.0.0
5
+
6
+ ui_default_env: development
7
+ ui_disabled: false
@@ -0,0 +1,100 @@
1
+ class Nodectl::Instance < Nodectl::Process
2
+ @manager = Nodectl::Manager.new(self)
3
+
4
+ attr_reader :service
5
+ attr_reader :options
6
+ attr_reader :command
7
+
8
+ def initialize(service, command, options = {}, &blk)
9
+ super(options) do
10
+ blk.call if blk
11
+ Process.exec command
12
+ end
13
+
14
+ @service = service
15
+ @options = options
16
+ @command = command
17
+
18
+ if @options[:started]
19
+ @status = :running
20
+ @pid = @options[:pid]
21
+ @detach = Process.detach(@pid)
22
+
23
+ Nodectl::Instance[@pid] = self
24
+ Nodectl.logger.info("#{@service.name} [#{@pid}] found")
25
+ end
26
+
27
+ onkill do
28
+ self.class.delete(pid, reason: @status)
29
+ end
30
+ end
31
+
32
+ def run
33
+ super
34
+
35
+ Nodectl::Instance[@pid] = self
36
+ Nodectl.logger.info("#{@service.name} [#{@pid}] started")
37
+ end
38
+
39
+ def as_json
40
+ {
41
+ "id" => pid,
42
+ "service_id" => service.name,
43
+ "status" => status,
44
+ "options" => options
45
+ }
46
+ end
47
+
48
+ def dump
49
+ {
50
+ "service_name" => service.name,
51
+ "pid" => pid,
52
+ "options" => options,
53
+ "command" => command
54
+ }
55
+ end
56
+
57
+ def pre_fork_block
58
+ Nodectl.shut_up!
59
+ end
60
+
61
+ class << self
62
+ def load(dump)
63
+ service = Nodectl::Service.find(dump["service_name"])
64
+
65
+ if service
66
+ puts "Service found: #{dump['service_name']}"
67
+ new(service, dump["command"], dump["options"].merge(started: true, pid: dump["pid"]))
68
+ else
69
+ puts "Service not found"
70
+ raise "service not found"
71
+ end
72
+ end
73
+
74
+ def push(pid, instance)
75
+ super(pid, instance)
76
+ save_register
77
+ end
78
+
79
+ alias :[]= :push
80
+
81
+ def delete(pid, options = {})
82
+ super(pid, options)
83
+ save_register
84
+ end
85
+
86
+ def save_register
87
+ Nodectl.database["instances"] = all.map(&:dump)
88
+ end
89
+
90
+ def load_register
91
+ database = Nodectl.database["instances"] || []
92
+
93
+ database.each do |dump|
94
+ load(dump) rescue RuntimeError
95
+ end
96
+
97
+ save_register
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,13 @@
1
+ class Nodectl::Log
2
+ attr_reader :name
3
+ attr_reader :path
4
+
5
+ def initialize(name, path)
6
+ @name = name
7
+ @path = path
8
+ end
9
+
10
+ def instance_path(service)
11
+ service.source_path.join(path)
12
+ end
13
+ end
@@ -0,0 +1,71 @@
1
+ class Nodectl::Manager
2
+ MANAGER_METHODS = [:all, :find, :find!, :push, :delete, :[], :[]=, :next_id, :onadd, :ondelete]
3
+
4
+ def initialize(target_class)
5
+ @objects = {}
6
+ @target_class = target_class
7
+
8
+ @onadd = []
9
+ @ondelete = []
10
+
11
+
12
+ MANAGER_METHODS.each do |method|
13
+ @target_class.define_singleton_method method do |*args, &blk|
14
+ @manager.send(method, *args, &blk)
15
+ end
16
+ end
17
+ end
18
+
19
+ def find(key)
20
+ @objects[key]
21
+ end
22
+
23
+ alias :[] :find
24
+
25
+ def find!(key)
26
+ object = find(key)
27
+
28
+ unless object
29
+ raise Nodectl::NotFound, "#{@target_class.name} with key '#{key}' not found"
30
+ end
31
+
32
+ object
33
+ end
34
+
35
+ def all
36
+ @objects.values
37
+ end
38
+
39
+ def push(key, object)
40
+ @objects[key] = object
41
+ onadd_call(object)
42
+ end
43
+
44
+ alias :[]= :push
45
+
46
+ def delete(key, options = {})
47
+ object = @objects.delete(key)
48
+ ondelete_call(object, options)
49
+ end
50
+
51
+ def next_id
52
+ @next_id ||= 0
53
+ @next_id += 1
54
+ end
55
+
56
+ def onadd(&blk)
57
+ @onadd << blk
58
+ end
59
+
60
+ def ondelete(&blk)
61
+ @ondelete << blk
62
+ end
63
+
64
+ def onadd_call(object)
65
+ @onadd.each { |b| b.call(object) }
66
+ end
67
+
68
+ def ondelete_call(object, options)
69
+ @ondelete.each { |b| b.call(object, options) }
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ # Redirect `write` calls to multiple targets, used with standard ruby
2
+ # logger instance for writing messages to log file and stdout at the
3
+ # same time
4
+ class Nodectl::MultiIO
5
+ def initialize(*targets)
6
+ @targets = targets
7
+ end
8
+
9
+ def write(*args)
10
+ @targets.each {|t| t.write(*args)}
11
+ end
12
+
13
+ def close
14
+ @targets.each(&:close)
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ # Boot options container, keep info about node directory structure etc
2
+ class Nodectl::Options
3
+ def initialize(overrides = {})
4
+ @options = default_options.merge(overrides)
5
+ @options = calculate_options(@options)
6
+ end
7
+
8
+ def [](option_name)
9
+ @options[option_name.to_s]
10
+ end
11
+
12
+ def inspect
13
+ @options.inspect
14
+ end
15
+
16
+ def to_s
17
+ @options.to_s
18
+ end
19
+
20
+ def to_hash
21
+ @options
22
+ end
23
+
24
+ private
25
+
26
+ def default_options
27
+ {
28
+ "root" => Dir.pwd,
29
+ "log_level" => "INFO"
30
+ }
31
+ end
32
+
33
+ def calculate_options(source_options)
34
+ options = source_options.dup
35
+
36
+ options["root"] = Pathname.new(options["root"])
37
+
38
+ options["config_dir"] ||= options["root"].join("config")
39
+ options["source_dir"] ||= options["root"].join("src")
40
+ options["logger_dir"] ||= options["root"].join("log")
41
+ options["recipe_dir"] ||= options["root"].join("recipes")
42
+ options["database"] ||= options["root"].join("nodectl.db")
43
+ options["manifest"] ||= options["config_dir"].join("manifest.yml")
44
+
45
+ options
46
+ end
47
+ end
@@ -0,0 +1,145 @@
1
+ class Nodectl::Process
2
+ ENV_BLACKLIST = %w(BUNDLE_BIN_PATH BUNDLE_GEMFILE USE_BUNDLER _ORIGINAL_GEM_PATH _ RUBYOPT)
3
+ TIMEOUT = 10
4
+
5
+ @manager = Nodectl::Manager.new(self)
6
+
7
+ attr_reader :pid
8
+ attr_reader :status
9
+ attr_reader :exit_status
10
+
11
+ def initialize(options = {}, &blk)
12
+ raise "cannot create process without block" unless block_given?
13
+
14
+ @status = :pending
15
+ @stopping = false
16
+ @options = options
17
+ @blk = blk
18
+ @oncrash = []
19
+ @onstop = []
20
+ @onkill = []
21
+ end
22
+
23
+ def run
24
+ if @status != :pending
25
+ raise "Cannot run one process twice"
26
+ end
27
+
28
+ @pid = fork do
29
+ ENV_BLACKLIST.each do |name|
30
+ ENV.delete(name)
31
+ end
32
+
33
+ args = @options[:args] || []
34
+ @blk.call(*args)
35
+
36
+ EM.stop
37
+ end
38
+
39
+ @detach = Process.detach(@pid)
40
+
41
+ @status = :running
42
+ Nodectl::Process[pid] = self
43
+ end
44
+
45
+ def alive?
46
+ Process.getpgid(pid)
47
+ true
48
+ rescue Errno::ESRCH
49
+ dead!
50
+ false
51
+ end
52
+
53
+ def stop(options = {})
54
+ @status = :stopping
55
+ signal = options[:force] ? "KILL" : "TERM"
56
+
57
+ Nodectl.logger.info("process: send #{signal} to #{pid}")
58
+
59
+ Process.kill(signal, pid)
60
+ rescue Errno::ESRCH, Errno::EPERM
61
+ dead!
62
+ end
63
+
64
+ def restart(options = {})
65
+ stop(options)
66
+ TIMEOUT.times do
67
+ unless alive?
68
+ @status = :pending
69
+ run
70
+ break
71
+ end
72
+
73
+ sleep 1
74
+ end
75
+ end
76
+
77
+ def dead!(exit_status = nil)
78
+ @detach.join
79
+
80
+ if @detach.value
81
+ @exit_status = @detach.value.exitstatus
82
+ end
83
+
84
+ if [:running, :stopping].include?(@status)
85
+ case @status
86
+ when :stopping
87
+ @status = :stopped
88
+ Nodectl.logger.info("process: [#{pid}] stopped")
89
+ onstop_call
90
+ when :running
91
+ @status = :crashed
92
+ Nodectl.logger.info("process: [#{pid}] crashed")
93
+ oncrash_call
94
+ end
95
+
96
+ onkill_call
97
+ Nodectl::Process.delete(pid)
98
+ end
99
+ end
100
+
101
+ # When process crashed
102
+ def oncrash(&blk)
103
+ @oncrash << blk
104
+ end
105
+
106
+ # When process gracefully stoped
107
+ def onstop(&blk)
108
+ @onstop << blk
109
+ end
110
+
111
+ # When process is dead, regardless of reason
112
+ def onkill(&blk)
113
+ @onkill << blk
114
+ end
115
+
116
+ private
117
+
118
+ def oncrash_call
119
+ @oncrash.each { |b| b.call(self) }
120
+ end
121
+
122
+ def onkill_call
123
+ @onkill.each { |b| b.call(self) }
124
+ end
125
+
126
+ def onstop_call
127
+ @onstop.each { |b| b.call(self) }
128
+ end
129
+
130
+ def pre_fork_block
131
+ # Override it to customize process behaviour
132
+ end
133
+
134
+ def post_fork_block
135
+ # Override it to customize process behaviour
136
+ end
137
+
138
+ def fork(&blk)
139
+ Process.fork do
140
+ pre_fork_block
141
+ blk.call
142
+ post_fork_block
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,37 @@
1
+ # Promised file is file which looks like an empty file while it does not exists
2
+ class Nodectl::PromisedFile
3
+ def initialize(path, file_class, options = {})
4
+ @path = path
5
+ @file_class = file_class
6
+ @options = options
7
+ @fake_file = Nodectl::Stream::File.new(StringIO.new)
8
+ end
9
+
10
+ def close
11
+ @closed = true
12
+ current_file.close
13
+ end
14
+
15
+ # Hook Nodectl::Stream::File#onread for call it when real
16
+ # file was created
17
+ def onread(&blk)
18
+ @onread = blk
19
+ end
20
+
21
+ def method_missing(name, *args, &blk)
22
+ current_file.public_send(name, *args, &blk)
23
+ end
24
+
25
+ private
26
+
27
+ def current_file
28
+ if !@real_file && !@closed && File.exist?(@path)
29
+ @real_file = @file_class.new(@path, @options)
30
+ @real_file.onread(&@onread) if @onread
31
+ end
32
+
33
+ @real_file || @fake_file
34
+ end
35
+
36
+
37
+ end