thundercat 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +79 -0
- data/bin/thundercat +39 -0
- data/src/monitor/monitor.rb +121 -0
- data/src/monitor/start.sh +1 -0
- data/src/monitor/stop.sh +19 -0
- data/src/rap/thundercat.rap +0 -0
- metadata +231 -0
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Deploy & Monitor RAP Packaged Rack Applications
|
2
|
+
|
3
|
+
ThunderCat is a ruby application container for Rack based applications. It is very similar in concept to war files and Tomcat. When you drop a .rap file into ThunderCats webapps directory it will auto expand and start the webapp.
|
4
|
+
There is a web admin panel which lets you upload a .rap archive and also see which webapps are running and start/stop/remove them.
|
5
|
+
|
6
|
+
[![Build Status](https://travis-ci.org/masterthought/thundercat.png?branch=master)](https://travis-ci.org/masterthought/thundercat)
|
7
|
+
|
8
|
+
## Background
|
9
|
+
|
10
|
+
ThunderCat aims to be a sort of rack application container. It's not really a container but more of a place to deploy all your sinatra/rails/rack applications in one place and start/stop/remove them. The core
|
11
|
+
philosophy is to have a single artifact (a .rap archive) which can be dropped into ThunderCat and auto startup to make deployment extremely simple.
|
12
|
+
|
13
|
+
It might not suit everyone but it could be very useful for deploying internal applications inside a big corporation or even just your blog up to your website if you control the server it lives on. Instead of using something
|
14
|
+
like capistrano you just drop a .rap archive into ThunderCat and it does the rest.
|
15
|
+
|
16
|
+
Currently thin and unicorn are supported but raise and issue if you want any others supported.
|
17
|
+
|
18
|
+
ThunderCat is designed to be used in conjunction with [Rappa](https://github.com/masterthought/rappa). Using Rappa you create your .rap archive and then upload it to ThunderCat via the admin panel or use the rappa deploy command.
|
19
|
+
|
20
|
+
## Install
|
21
|
+
|
22
|
+
gem install thundercat
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
First you need to create your ThunderCat file structure:
|
27
|
+
|
28
|
+
thundercat my_apps
|
29
|
+
|
30
|
+
This will create a directory structure with the path you provide it - the structure is as follows:
|
31
|
+
|
32
|
+
![file structure]
|
33
|
+
(https://github.com/masterthought/thundercat/raw/master/.README/thundercat_structure.jpg)
|
34
|
+
|
35
|
+
The start.sh and stop.sh scripts are used to start and stop ThunderCat. Logs go into the logs directory and webapps go into the webapps directory. The archive directory holds backups when you deploy/remove apps via the admin panel.
|
36
|
+
The monitor.rb file is what gets started and monitors the webapps directory for changes.
|
37
|
+
|
38
|
+
When you start ThunderCat for the first time it starts on port 9898 with the following credentials:
|
39
|
+
|
40
|
+
username: thundercat
|
41
|
+
password: thundercat
|
42
|
+
|
43
|
+
## Config
|
44
|
+
|
45
|
+
You can configure the username and password and the port ThunderCat starts on in the config.yml in webapps/thundercat. You have to startup thundercat first in order for the thundercat.rap to be expanded and installed - but after that you can go and edit the config.yml and restart thundercat.
|
46
|
+
You can also set the api_key - this is what you need in order to deploy from the rappa command as shown below.
|
47
|
+
|
48
|
+
### deploy
|
49
|
+
|
50
|
+
This deploys a rap archive to a thundercat server e.g.
|
51
|
+
|
52
|
+
rappa deploy -r myapp.rap -u http://thundercat/api/deploy -k your_api_key
|
53
|
+
|
54
|
+
-r is to specify your rap archive and -u is the url of the deploy api where your thundercat instance is running. -k is your api_key which is configured in your
|
55
|
+
thundercat server.
|
56
|
+
|
57
|
+
## Screenshots
|
58
|
+
|
59
|
+
### Admin Page
|
60
|
+
|
61
|
+
![admin page]
|
62
|
+
(https://github.com/masterthought/thundercat/raw/master/.README/thundercat_admin.jpg)
|
63
|
+
|
64
|
+
### Login Page
|
65
|
+
|
66
|
+
![login page]
|
67
|
+
(https://github.com/masterthought/thundercat/raw/master/.README/thundercat_login.jpg)
|
68
|
+
|
69
|
+
## Additional Info
|
70
|
+
|
71
|
+
The bootstrap.sh could be used to set an environment variable like RAILS_ENV and the start.sh could contain a rake command in which the rakefile contains a start command that uses the environment variable to deploy with the correct environment.
|
72
|
+
This does mean you will have to expand the rap, update the bootstrap script and then package again if deploying to different environments needs different config. Any suggestions around this area are welcome and since this is
|
73
|
+
in the early stages it's fairly experimental.
|
74
|
+
|
75
|
+
|
76
|
+
## Develop
|
77
|
+
|
78
|
+
Interested in contributing? Great just let me know how you want to help.
|
79
|
+
|
data/bin/thundercat
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
name = ARGV[0]
|
5
|
+
|
6
|
+
if name.nil? or name.empty?
|
7
|
+
puts '[ThunderCat] Error you must supply a target directory name'
|
8
|
+
else
|
9
|
+
|
10
|
+
begin
|
11
|
+
rap = File.dirname(File.expand_path(__FILE__)) + '/../src/rap/thundercat.rap'
|
12
|
+
monitor = File.dirname(File.expand_path(__FILE__)) + '/../src/monitor/monitor.rb'
|
13
|
+
start_script = File.dirname(File.expand_path(__FILE__)) + '/../src/monitor/start.sh'
|
14
|
+
stop_script = File.dirname(File.expand_path(__FILE__)) + '/../src/monitor/stop.sh'
|
15
|
+
|
16
|
+
target_dir = "#{name}"
|
17
|
+
if File.exists?(target_dir)
|
18
|
+
puts "[ThunderCat] The directory you supplied: #{target_dir} already exists"
|
19
|
+
else
|
20
|
+
FileUtils.mkpath(target_dir)
|
21
|
+
FileUtils.mkpath("#{target_dir}/webapps")
|
22
|
+
FileUtils.mkpath("#{target_dir}/log")
|
23
|
+
FileUtils.mkpath("#{target_dir}/archive")
|
24
|
+
FileUtils.cp(rap, "#{target_dir}/webapps")
|
25
|
+
FileUtils.cp(monitor, target_dir)
|
26
|
+
FileUtils.cp(start_script, target_dir)
|
27
|
+
FileUtils.cp(stop_script, target_dir)
|
28
|
+
puts "[ThunderCat] Successfully created ThunderCat structure at: #{target_dir}"
|
29
|
+
end
|
30
|
+
rescue => e
|
31
|
+
puts "[ThunderCat] OOps something went wrong: #{e}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fssm'
|
3
|
+
require 'dante'
|
4
|
+
require 'yaml'
|
5
|
+
require 'rappa'
|
6
|
+
|
7
|
+
class Decision
|
8
|
+
|
9
|
+
def decide(option, base, relative, type)
|
10
|
+
rap_file = base + '/' + relative
|
11
|
+
rap_dir = path_without_ext(rap_file, base)
|
12
|
+
if option == :create
|
13
|
+
clean_rap(rap_dir, rap_file, base) if rap_already_deployed?(rap_dir)
|
14
|
+
deploy_rap(rap_file, rap_dir, base)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initial(webapps_dir)
|
19
|
+
Dir.entries(webapps_dir).each do |entry|
|
20
|
+
if entry.match(/.rap$/)
|
21
|
+
decide(:create, webapps_dir, entry, 'file')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
`cd #{webapps_dir}/thundercat; ./start.sh`
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def deploy_rap(rap_file, rap_dir, base)
|
30
|
+
Rappa.new(:input_archive => rap_file, :output_archive => base).expand
|
31
|
+
rap_config = rap_dir + '/rap.yml'
|
32
|
+
if File.exists?(rap_config)
|
33
|
+
rap = YAML.load_file(rap_config)
|
34
|
+
start_script = rap[:start_script]
|
35
|
+
stop_script = rap[:stop_script]
|
36
|
+
bootstrap_script = rap[:bootstrap]
|
37
|
+
p "1. found boostrap script: #{bootstrap_script}"
|
38
|
+
`cd #{rap_dir} ; chmod +x #{start_script}`
|
39
|
+
`cd #{rap_dir} ; chmod +x #{stop_script}`
|
40
|
+
if !bootstrap_script.nil?
|
41
|
+
p "2. found boostrap script: #{bootstrap_script}"
|
42
|
+
`cd #{rap_dir} ; chmod +x #{bootstrap_script}`
|
43
|
+
puts "[Thundercat] executing boostrap: #{bootstrap_script}"
|
44
|
+
puts `cd #{rap_dir} ; ./#{bootstrap_script}`
|
45
|
+
end
|
46
|
+
`cd #{rap_dir} ; ./#{start_script}`
|
47
|
+
puts "[ThunderCat] Successfully deployed and started app at: #{rap_file}"
|
48
|
+
archive_rap(rap_file,base)
|
49
|
+
else
|
50
|
+
puts '[ThunderCat] no rap file found so could not start app'
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop_app(rap_dir)
|
56
|
+
rap_file = rap_dir + '/rap.yml'
|
57
|
+
if File.exists?(rap_file)
|
58
|
+
rap = YAML.load_file(rap_file)
|
59
|
+
stop_script = rap[:stop_script]
|
60
|
+
`cd #{rap_dir} ; chmod +x #{stop_script}`
|
61
|
+
`cd #{rap_dir} ; ./#{stop_script}`
|
62
|
+
else
|
63
|
+
puts '[ThunderCat] no rap file found so could not stop app'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def archive_existing_app(rap_dir, rap_file, base)
|
68
|
+
FileUtils.mv(rap_dir, "#{base}/../archive/#{file_without_ext(rap_file)}.#{Time.now.to_i}")
|
69
|
+
end
|
70
|
+
|
71
|
+
def archive_rap(rap_file,base)
|
72
|
+
FileUtils.mv(rap_file, "#{base}/../archive/#{file_without_ext(rap_file)}.#{Time.now.to_i}.rap")
|
73
|
+
puts "[ThunderCat] archived rap file: #{rap_file}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def rap_already_deployed?(rap_dir)
|
77
|
+
File.exists?(rap_dir)
|
78
|
+
end
|
79
|
+
|
80
|
+
def clean_rap(rap_dir, rap_file, base)
|
81
|
+
stop_app(rap_dir)
|
82
|
+
archive_existing_app(rap_dir, rap_file, base)
|
83
|
+
end
|
84
|
+
|
85
|
+
def path_without_ext(file, base)
|
86
|
+
base_name = File.basename(file)
|
87
|
+
base + '/' + base_name.chomp(File.extname(base_name))
|
88
|
+
end
|
89
|
+
|
90
|
+
def file_without_ext(file)
|
91
|
+
base_name = File.basename(file)
|
92
|
+
base_name.chomp(File.extname(base_name))
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
class Monitor
|
99
|
+
|
100
|
+
def self.go
|
101
|
+
begin
|
102
|
+
webapps_dir = File.dirname(__FILE__) + '/webapps'
|
103
|
+
Decision.new.initial(webapps_dir)
|
104
|
+
FSSM.monitor(webapps_dir, '**/*.rap', :directories => true) do
|
105
|
+
update { |base, relative, type| puts "updated #{base}, #{relative}, #{type}" }
|
106
|
+
delete { |base, relative, type| puts "delete #{base}, #{relative}, #{type}" }
|
107
|
+
create { |base, relative, type| Decision.new.decide(:create, base, relative, type) }
|
108
|
+
end
|
109
|
+
rescue => e
|
110
|
+
puts "[ThunderCat] Monitor encountered an error: #{e}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
Dante.run('monitor') do |opts|
|
117
|
+
Monitor.go
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
./monitor.rb -P ./log/monitor.pid -d -l ./log/monitor.log
|
data/src/monitor/stop.sh
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
begin
|
5
|
+
|
6
|
+
pid = File.read(File.dirname(__FILE__) + '/log/monitor.pid').strip
|
7
|
+
`kill -9 #{pid}`
|
8
|
+
FileUtils.rm(File.dirname(__FILE__) + '/log/monitor.pid')
|
9
|
+
puts "[ThunderCat] stopped thundercat monitor with process: #{pid}"
|
10
|
+
|
11
|
+
thunder_dir = File.dirname(__FILE__) + '/webapps/thundercat'
|
12
|
+
`cd #{thunder_dir} ; chmod +x ./stop.sh`
|
13
|
+
puts `cd #{thunder_dir} ; ./stop.sh`
|
14
|
+
|
15
|
+
puts "[ThunderCat] stopped thundercat webapp"
|
16
|
+
|
17
|
+
rescue => e
|
18
|
+
puts "[ThunderCat] Oops something went wrong: #{e}"
|
19
|
+
end
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thundercat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kingsley Hendrickse
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sinatra
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sinatra-formhelpers
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sinatra-single-user-auth
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sucker_punch
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: fssm
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: thin
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: log4r
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :runtime
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rake
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :runtime
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: sys-proctable
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :runtime
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: jeweler
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :runtime
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
description: ! 'Easy way to deploy and monitor Rack applications as .rap archives
|
191
|
+
|
192
|
+
'
|
193
|
+
email: kingsley@masterthought.net
|
194
|
+
executables:
|
195
|
+
- /thundercat
|
196
|
+
extensions: []
|
197
|
+
extra_rdoc_files:
|
198
|
+
- README.md
|
199
|
+
files:
|
200
|
+
- src/rap/thundercat.rap
|
201
|
+
- src/monitor/monitor.rb
|
202
|
+
- src/monitor/start.sh
|
203
|
+
- src/monitor/stop.sh
|
204
|
+
- README.md
|
205
|
+
- bin/thundercat
|
206
|
+
homepage: https://github.com/masterthought/thundercat
|
207
|
+
licenses:
|
208
|
+
- Apache 2.0
|
209
|
+
post_install_message:
|
210
|
+
rdoc_options: []
|
211
|
+
require_paths:
|
212
|
+
- src
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
214
|
+
none: false
|
215
|
+
requirements:
|
216
|
+
- - ! '>='
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
version: '0'
|
219
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
220
|
+
none: false
|
221
|
+
requirements:
|
222
|
+
- - ! '>='
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
225
|
+
requirements: []
|
226
|
+
rubyforge_project:
|
227
|
+
rubygems_version: 1.8.25
|
228
|
+
signing_key:
|
229
|
+
specification_version: 3
|
230
|
+
summary: Thundercat Rack deployment and monitoring
|
231
|
+
test_files: []
|