ridoku 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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/AUTHORS +3 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +23 -0
- data/README.md +271 -0
- data/Rakefile +1 -0
- data/bin/rid +3 -0
- data/bin/ridoku +350 -0
- data/lib/helpers.rb +13 -0
- data/lib/io-colorize.rb +35 -0
- data/lib/options.rb +142 -0
- data/lib/ridoku.rb +11 -0
- data/lib/ridoku/backup.rb +393 -0
- data/lib/ridoku/base.rb +717 -0
- data/lib/ridoku/config_wizard.rb +195 -0
- data/lib/ridoku/cook.rb +103 -0
- data/lib/ridoku/create.rb +101 -0
- data/lib/ridoku/cron.rb +227 -0
- data/lib/ridoku/db.rb +235 -0
- data/lib/ridoku/defaults.rb +68 -0
- data/lib/ridoku/deploy.rb +157 -0
- data/lib/ridoku/domain.rb +124 -0
- data/lib/ridoku/dump.rb +132 -0
- data/lib/ridoku/env.rb +118 -0
- data/lib/ridoku/list.rb +168 -0
- data/lib/ridoku/log.rb +77 -0
- data/lib/ridoku/maintenance.rb +76 -0
- data/lib/ridoku/packages.rb +93 -0
- data/lib/ridoku/rails_defaults.rb +160 -0
- data/lib/ridoku/run.rb +137 -0
- data/lib/ridoku/service.rb +158 -0
- data/lib/ridoku/services/postgres.rb +77 -0
- data/lib/ridoku/services/rabbitmq.rb +48 -0
- data/lib/ridoku/version.rb +5 -0
- data/lib/ridoku/workers.rb +138 -0
- data/ridoku.gemspec +32 -0
- metadata +211 -0
data/lib/ridoku/db.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
#
|
2
|
+
# Command: db
|
3
|
+
# Description: List/Modify the current apps database configuration
|
4
|
+
# db - lists the database parameters
|
5
|
+
# config:set KEY:VALUE [...]
|
6
|
+
# config:delete KEY
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'ridoku/base'
|
10
|
+
|
11
|
+
module Ridoku
|
12
|
+
register :db
|
13
|
+
|
14
|
+
class Db < Base
|
15
|
+
attr_accessor :dbase
|
16
|
+
|
17
|
+
def run
|
18
|
+
command = Base.config[:command]
|
19
|
+
sub_command = (command.length > 0 && command[1]) || nil
|
20
|
+
sub_sub_command = (command.length > 1 && command[2]) || nil
|
21
|
+
|
22
|
+
case sub_command
|
23
|
+
when 'list', nil, 'info'
|
24
|
+
list(false)
|
25
|
+
when 'credentials'
|
26
|
+
list(true)
|
27
|
+
when 'set', 'add'
|
28
|
+
set
|
29
|
+
when 'push', 'update'
|
30
|
+
push_update
|
31
|
+
when 'delete', 'remove', 'rm'
|
32
|
+
delete
|
33
|
+
when 'url', 'path'
|
34
|
+
url(sub_sub_command)
|
35
|
+
else
|
36
|
+
print_db_help
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def load_database
|
43
|
+
Base.fetch_stack
|
44
|
+
Base.fetch_app
|
45
|
+
self.dbase =
|
46
|
+
Base.custom_json['deploy'][Base.app[:shortname]]['database']
|
47
|
+
end
|
48
|
+
|
49
|
+
def print_db_help
|
50
|
+
$stderr.puts <<-EOF
|
51
|
+
Command: database
|
52
|
+
|
53
|
+
List/Modify the current app's database configuration.
|
54
|
+
db lists the key value pairs
|
55
|
+
db:push push changes to the servers
|
56
|
+
db:set:url attribute:value [...]
|
57
|
+
db:delete attribute [...]
|
58
|
+
db:url get the URL form of the database info
|
59
|
+
db:url:set set attributes using a URL
|
60
|
+
|
61
|
+
examples:
|
62
|
+
$ db:set database:'Survly'
|
63
|
+
Database:
|
64
|
+
adapter: postgresql
|
65
|
+
host: ec2-50-47-234-2.amazonaws.com
|
66
|
+
port: 5234
|
67
|
+
database: Survly
|
68
|
+
username: survly
|
69
|
+
reconnect: true
|
70
|
+
EOF
|
71
|
+
end
|
72
|
+
|
73
|
+
def push_update
|
74
|
+
if Base.config[:wait]
|
75
|
+
$stdout.puts 'Disabling command wait (2 commands to issue)...'
|
76
|
+
Base.config[:wait] = false
|
77
|
+
end
|
78
|
+
|
79
|
+
Base.fetch_app
|
80
|
+
|
81
|
+
cjson = {
|
82
|
+
opsworks: {
|
83
|
+
rails_stack: {
|
84
|
+
restart_command: '../../shared/scripts/unicorn force-restart'
|
85
|
+
}
|
86
|
+
},
|
87
|
+
deploy: {
|
88
|
+
Base.app[:shortname] => {
|
89
|
+
application_type: 'rails'
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
begin
|
95
|
+
$stdout.puts 'This operation will '\
|
96
|
+
"#{$stdout.colorize('RESTART', :red)} (not soft reload) "\
|
97
|
+
'your application servers and worker servers.'
|
98
|
+
sleep 2
|
99
|
+
$stdout.puts 'Hold your seats: db push and force restart in... (Press CTRL-C to Stop)'
|
100
|
+
5.times { |t| $stdout.print "#{$stdout.colorize(5-t, :red)} "; sleep 1 }
|
101
|
+
$stdout.puts "\nSilence is acceptance..."
|
102
|
+
rescue Interrupt
|
103
|
+
$stdout.puts $stdout.colorize("\nCommand canceled successfully!", :green)
|
104
|
+
exit 1
|
105
|
+
end
|
106
|
+
|
107
|
+
Base.config[:layers] = ['rails-app']
|
108
|
+
Ridoku::Cook.cook_recipe('deploy::database', cjson)
|
109
|
+
|
110
|
+
Base.config[:layers] = ['workers']
|
111
|
+
Ridoku::Cook.cook_recipe(['rails::configure', 'workers::configure'], cjson)
|
112
|
+
end
|
113
|
+
|
114
|
+
def list(cred)
|
115
|
+
load_database
|
116
|
+
if dbase.keys.length == 0
|
117
|
+
$stdout.puts 'Database Not Configured!'
|
118
|
+
else
|
119
|
+
$stdout.puts 'Database:'
|
120
|
+
dbase.each do |key, value|
|
121
|
+
$stdout.puts " #{$stdout.colorize(key, :bold)}: #{value}" if
|
122
|
+
(cred || (key != 'password' && key != 'username'))
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def set
|
128
|
+
load_database
|
129
|
+
ARGV.each do |kvpair|
|
130
|
+
kvpair.match(%r((^[^:=]+)(:|=)(.*))) do |m|
|
131
|
+
key = m[1]
|
132
|
+
value = m[3]
|
133
|
+
|
134
|
+
update = dbase.key?(key)
|
135
|
+
dbase[key] = value
|
136
|
+
$stdout.puts "#{update && 'Updating' || 'Adding'}: #{key} as '#{value}'"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
Base.save_stack
|
141
|
+
end
|
142
|
+
|
143
|
+
def delete
|
144
|
+
load_database
|
145
|
+
ARGV.each do |key|
|
146
|
+
value = dbase.delete(key)
|
147
|
+
$stdout.puts "Deleting key: #{key}, '#{value}'"
|
148
|
+
end
|
149
|
+
|
150
|
+
Base.save_stack
|
151
|
+
end
|
152
|
+
|
153
|
+
def set_url_database(subc)
|
154
|
+
if subc != 'set'
|
155
|
+
print_db_help
|
156
|
+
exit 1
|
157
|
+
end
|
158
|
+
|
159
|
+
regex = %r(^([^:]+)://([^:]+):([^@]+)@([^:]+):([^/]+)/(.*)$)
|
160
|
+
ARGV[0].match(regex) do |m|
|
161
|
+
dbase['adapter'] = Db.adapter_from_scheme(m[1])
|
162
|
+
dbase['username'] = m[2]
|
163
|
+
dbase['password'] = m[3]
|
164
|
+
dbase['host'] = m[4]
|
165
|
+
dbase['port'] = m[5]
|
166
|
+
dbase['database'] = m[6]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class << self
|
171
|
+
def scheme_hash
|
172
|
+
{
|
173
|
+
'postgresql' => 'postgres',
|
174
|
+
'mysql' => 'mysql'
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
def scheme_from_adapter(adapter)
|
179
|
+
val = scheme_hash
|
180
|
+
return val[adapter] if val.key?(adapter)
|
181
|
+
adapter
|
182
|
+
end
|
183
|
+
|
184
|
+
def adapter_from_scheme(scheme)
|
185
|
+
val = scheme_hash.invert
|
186
|
+
return val[scheme] if val.key?(scheme)
|
187
|
+
scheme
|
188
|
+
end
|
189
|
+
|
190
|
+
def gen_dbase_url(dbase)
|
191
|
+
scheme = scheme_from_adapter(dbase['adapter'])
|
192
|
+
username = dbase['username']
|
193
|
+
password = dbase['password']
|
194
|
+
host = dbase['host']
|
195
|
+
port = dbase['port']
|
196
|
+
database = dbase['database']
|
197
|
+
|
198
|
+
url = "#{scheme}://"
|
199
|
+
url += username if username
|
200
|
+
url += ":#{password}"
|
201
|
+
url += '@' if username || password
|
202
|
+
url += host
|
203
|
+
url += ":#{port}" if port
|
204
|
+
url += "/#{database}" if database
|
205
|
+
url
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def get_url_database
|
210
|
+
scheme = Db.scheme_from_adapter(dbase['adapter'])
|
211
|
+
|
212
|
+
unless dbase['database'] && scheme && dbase['host']
|
213
|
+
$stdout.puts $stdout.colorize(
|
214
|
+
"One or more required fields are not specified!",
|
215
|
+
:bold
|
216
|
+
)
|
217
|
+
$stdout.puts $stdout.colorize("adapter, host, and database", :red)
|
218
|
+
list_database
|
219
|
+
end
|
220
|
+
|
221
|
+
url = Db.gen_dbase_url(dbase)
|
222
|
+
$stdout.puts $stdout.colorize(url, :bold)
|
223
|
+
end
|
224
|
+
|
225
|
+
def url(subc)
|
226
|
+
load_database
|
227
|
+
if subc
|
228
|
+
set_url_database(subc)
|
229
|
+
Base.save_stack
|
230
|
+
else
|
231
|
+
get_url_database
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#
|
2
|
+
# Base class for Component Generation
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'active_support/core_ext/hash/deep_merge'
|
6
|
+
|
7
|
+
module Ridoku
|
8
|
+
class PropertyDefaultsUndefined < StandardError
|
9
|
+
attr_accessor :method
|
10
|
+
def initialize(_method)
|
11
|
+
self.method = _method
|
12
|
+
super "Inheritors of 'ClassProperties' must define: #{method}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ClassProperties
|
17
|
+
attr_accessor :default, :required, :input, :warnings
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
fail StandardError.new('IAM/OpsWorks permission are not yet configured.') unless
|
21
|
+
Base.roles_configured?
|
22
|
+
end
|
23
|
+
|
24
|
+
def defaults_with_wizard(type, _input)
|
25
|
+
defaults(type, _input, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def defaults(type, _input, wizard = false)
|
29
|
+
self.input = _input
|
30
|
+
|
31
|
+
fail ArgumentError.new("No default parameters for type: #{type}") unless
|
32
|
+
default.key?(type)
|
33
|
+
|
34
|
+
merge_input(type, wizard)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def merge_input(type, with_wizard = false)
|
40
|
+
fail ArgumentError.new(":default and :required lack type: #{type}") unless
|
41
|
+
default.key?(type) && required.key?(type)
|
42
|
+
|
43
|
+
fail ArgumentError.new('Inputs :default, :required, and :user must be hashes!') unless
|
44
|
+
default[type].is_a?(Hash) && input.is_a?(Hash) && required[type].is_a?(Hash)
|
45
|
+
|
46
|
+
type_default = default[type].deep_merge(input)
|
47
|
+
|
48
|
+
errors = []
|
49
|
+
collect = {}
|
50
|
+
|
51
|
+
required[type].each do |k|
|
52
|
+
unless input.key?(k)
|
53
|
+
errors << k
|
54
|
+
collect[k] = required[k]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if with_wizard
|
59
|
+
ConfigWizard.fetch_input(type_default, required[type], warnings)
|
60
|
+
else
|
61
|
+
fail ArgumentError.new("User input required: #{errors}") if
|
62
|
+
errors.length
|
63
|
+
end
|
64
|
+
|
65
|
+
type_default
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#
|
2
|
+
# Command: deploy
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'ridoku/base'
|
6
|
+
|
7
|
+
module Ridoku
|
8
|
+
register :deploy
|
9
|
+
|
10
|
+
class Deploy < Base
|
11
|
+
attr_accessor :app
|
12
|
+
|
13
|
+
def run
|
14
|
+
clist = Base.config[:command]
|
15
|
+
command = clist.shift
|
16
|
+
sub_command = clist.shift
|
17
|
+
|
18
|
+
case sub_command
|
19
|
+
when 'to', nil
|
20
|
+
deploy
|
21
|
+
when 'rollback'
|
22
|
+
rollback
|
23
|
+
when 'restart'
|
24
|
+
restart
|
25
|
+
when 'info'
|
26
|
+
info
|
27
|
+
else
|
28
|
+
print_deploy_help
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def rollback
|
35
|
+
Base.fetch_instance
|
36
|
+
Base.fetch_app
|
37
|
+
|
38
|
+
Base.instances.select! { |inst| inst[:status] == 'online' }
|
39
|
+
instance_ids = Base.instances.map { |inst| inst[:instance_id] }
|
40
|
+
|
41
|
+
# :app_source => {
|
42
|
+
# :type => "git",
|
43
|
+
# :url => "git@github.com:Protobit/Survly.git",
|
44
|
+
# :ssh_key => "*****FILTERED*****",
|
45
|
+
# :revision => "opsworks-staging"
|
46
|
+
# }
|
47
|
+
|
48
|
+
$stdout.puts "Application:"
|
49
|
+
$stdout.puts " #{$stdout.colorize(Base.app[:name], :bold)}"
|
50
|
+
$stdout.puts "Rolling back on #{Base.instances.length} instance(s):"
|
51
|
+
Base.pretty_instances($stdout).each do |inst|
|
52
|
+
$stdout.puts " #{inst}"
|
53
|
+
end
|
54
|
+
$stdout.puts "Repository:"
|
55
|
+
$stdout.puts " #{$stdout.colorize(Base.app[:app_source][:url], :bold)} " +
|
56
|
+
"@ #{$stdout.colorize(Base.app[:app_source][:revision], :bold)}"
|
57
|
+
|
58
|
+
command = Base.rollback(Base.app[:app_id], instance_ids,
|
59
|
+
Base.config[:comment], {
|
60
|
+
opsworks_custom_cookbooks: {
|
61
|
+
recipes: [
|
62
|
+
"workers::rollback"
|
63
|
+
]
|
64
|
+
}
|
65
|
+
}
|
66
|
+
)
|
67
|
+
|
68
|
+
Base.run_command(command)
|
69
|
+
end
|
70
|
+
|
71
|
+
def deploy
|
72
|
+
custom_json = {}.tap do |json|
|
73
|
+
app = Base.config[:app]
|
74
|
+
|
75
|
+
json[:deploy] = {}
|
76
|
+
json[:deploy][app] = {}
|
77
|
+
json[:deploy][app][:action] = 'force_deploy' if Base.config[:force]
|
78
|
+
json[:deploy][app][:migrate] = true if Base.config[:migrate]
|
79
|
+
|
80
|
+
json[:opsworks_custom_cookbooks] = {
|
81
|
+
recipes: [
|
82
|
+
"workers::deploy"
|
83
|
+
]
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
if Base.config[:force]
|
88
|
+
begin
|
89
|
+
$stdout.puts 'Hold your seats: force deploying in... (Press CTRL-C to Stop)'
|
90
|
+
5.times { |t| $stdout.print "#{$stdout.colorize(5-t, :red)} "; sleep 1 }
|
91
|
+
$stdout.puts "\nSilence is acceptance..."
|
92
|
+
rescue Interrupt
|
93
|
+
$stdout.puts $stdout.colorize("\nCommand canceled", :green)
|
94
|
+
exit 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
$stdout.puts 'Database will be migrated.' if Base.config[:migrate]
|
99
|
+
Base.standard_deploy(:all, custom_json)
|
100
|
+
end
|
101
|
+
|
102
|
+
def restart
|
103
|
+
Base.fetch_app
|
104
|
+
|
105
|
+
Base.config[:layers] = 'rails-app'
|
106
|
+
custom_json = {}.tap do |json|
|
107
|
+
json[Base.app[:name]] = {
|
108
|
+
deploy: {
|
109
|
+
application_type: 'rails'
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
$stdout.puts "Restarting application: #{Base.app[:name]}."
|
115
|
+
Ridoku::Cook.cook_recipe('unicorn::force-restart', custom_json)
|
116
|
+
end
|
117
|
+
|
118
|
+
def info
|
119
|
+
Base.fetch_instance('rails-app') unless Base.instances
|
120
|
+
|
121
|
+
Base.instances = Base.instances.select do |inst|
|
122
|
+
inst[:status] == 'online'
|
123
|
+
end
|
124
|
+
|
125
|
+
$stdout.puts 'Instances which will be deployed:'
|
126
|
+
$stdout.puts Base.pretty_instances($stdout)
|
127
|
+
end
|
128
|
+
|
129
|
+
def print_deploy_help
|
130
|
+
$stderr.puts <<-EOF
|
131
|
+
Command: deploy
|
132
|
+
|
133
|
+
Deploy the specified application:
|
134
|
+
deploy deploy the given application to stack instances
|
135
|
+
--layers/-l <layers,...>:
|
136
|
+
used to specify which layers in the stack to deploy to if not
|
137
|
+
specified, all online stack instances are deployed
|
138
|
+
--practice/-p:
|
139
|
+
print what would be done, but don't actually do it
|
140
|
+
deploy:rollback rollback the most recently deployed application.
|
141
|
+
NOTE: This will not rollback environment, database, or domain changes.
|
142
|
+
It will only rollback source code changes. Configurations will
|
143
|
+
remain the same.
|
144
|
+
deploy:restart Force Restart the unicorn servers (service will go down).
|
145
|
+
|
146
|
+
examples:
|
147
|
+
$ deploy
|
148
|
+
Application:
|
149
|
+
test-app
|
150
|
+
Deploying to 2 instances:
|
151
|
+
mukujara, hinoenma
|
152
|
+
Using git repository:
|
153
|
+
git@github.com:ridoku/example-app.git @ master
|
154
|
+
EOF
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|