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.
@@ -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