ridoku 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,195 @@
1
+
2
+
3
+ module Ridoku
4
+ class ConfigWizard
5
+ attr_accessor :fields
6
+
7
+ def initialize
8
+ self.fields = {
9
+ service_role_arn: :arn_string,
10
+ instance_profile_arn: :arn_string,
11
+ ssh_key: :path
12
+ }
13
+ end
14
+
15
+ def run
16
+ $stdout.puts $stdout.colorize(ConfigWizard.help_text, :bold)
17
+
18
+ $stdout.puts "Do you wish to run the wizard now? [#{$stdout.colorize('Y', :bold)}|n]"
19
+ res = $stdin.gets
20
+ if res.match(%r(^[Nn]))
21
+ Base.config[:local_init] = true
22
+ Base.save_config(::RUNCOM)
23
+ exit 1
24
+ end
25
+
26
+ sra_info = <<-EOF
27
+ Service Role ARN is used to access OpsWorks and issue commands on your
28
+ behalf. No suitable role was found.
29
+
30
+ Please enter the appropriate Role used to issue commands on OpsWorks.
31
+ Leave the field blank to attempt to generate one or to refresh from account
32
+ credentials. 's' or 'skip' if you wish to use existing values.
33
+
34
+ #{$stdout.colorize('Current Service ARN:', [:white, :bold])} #{$stdout.colorize(Base.config[:service_arn], :green)}
35
+ EOF
36
+ inst_info = <<-EOF
37
+ Instance Profile ARN is used to for each instance created for OpsWorks.
38
+
39
+ Please enter the appropriate default Role used for instance profiles.
40
+ Leave the field blank to attempt to generate one or to refresh from account
41
+ credentials. 's' or 'skip' if you wish to use existing values.
42
+
43
+ #{$stdout.colorize('Current Instance ARN:', [:white, :bold])} #{$stdout.colorize(Base.config[:instance_arn], :green)}
44
+ EOF
45
+ info = {
46
+ service_role_arn: sra_info,
47
+ instance_profile_arn: inst_info
48
+ }
49
+
50
+ ConfigWizard.fetch_input(Base.config, fields, info)
51
+
52
+ case Base.config[:service_role_arn]
53
+ when ''
54
+ Base.config.delete(:service_arn)
55
+ Base.configure_service_roles
56
+ when 's', 'skip'
57
+ else
58
+ Base.config[:service_arn] = Base.config[:service_role_arn]
59
+ end
60
+
61
+ case Base.config[:instance_profile_arn]
62
+ when ''
63
+ Base.config.delete(:instance_arn)
64
+ Base.configure_instance_roles
65
+ when 's', 'skip'
66
+ else
67
+ Base.config[:instance_arn] = Base.config[:instance_profile_arn]
68
+ end
69
+
70
+ Base.config[:local_init] = true
71
+ Base.save_config(::RUNCOM)
72
+
73
+ $stdout.puts 'Updating required Security Groups'
74
+ Base.update_pg_security_groups_in_all_regions
75
+
76
+ $stdout.puts 'Configuration complete.'
77
+ exit 0
78
+ end
79
+ protected
80
+
81
+ class << self
82
+
83
+ def help_text
84
+ help = <<-EOF
85
+ Configuration Wizard:
86
+
87
+ In order to get ridoku configured with your OpsWorks account, Ridoku must
88
+ collect pertinent required info. The wizard can be run at any time after the
89
+ first with the command line option of `--wizard`.
90
+
91
+ Values to be configured:
92
+ ssh_key:
93
+ Path to the SSH key to be used for git repositories
94
+ (cook books, apps, etc). It is recommended that this be generated
95
+ separately from your personal SSH keys so that they can be revoked
96
+ effecting other logins.
97
+
98
+ service_role_arn:
99
+ If a valid service_role_arn cannot be found, Ridoku will attempt to
100
+ generate one for you. If you've already used OpsWorks, Ridoku should be
101
+ able to find the necessary Roles for you.
102
+
103
+ instance_role_arn:
104
+ If a valid instance_role_arn cannot be found, Ridoku will attempt to
105
+ generate one for you. If you've already used OpsWorks, Ridoku should be
106
+ able to find the necessary Roles for you.
107
+ EOF
108
+ end
109
+
110
+ def fetch_input(output, required = {}, info = {})
111
+ info ||= {}
112
+ info.merge!({
113
+ ssh_key: <<-EOF
114
+ Key files (such as SSH keys) should be provided by file path. In the case of
115
+ GIT repository SSH keys (custom cookbooks, application respository), these
116
+ should be to the private keys. I recommend generating keys specifically for
117
+ each use, so that the keys can be easily tracked and removed if necessary,
118
+ without requiring you replace your keys on every machine you access.
119
+ Enter 's' or 'skip' if you wish to keep the currently configured value.
120
+ EOF
121
+ })
122
+
123
+ recurse_required(required, output, info)
124
+ end
125
+
126
+ protected
127
+
128
+ # Recurse through required hash to collected all necessary information.
129
+ def recurse_required(req, user, info, buffer = '')
130
+ req.each do |k,v|
131
+ if v.is_a?(Hash)
132
+ buffer += info_for(k, info)
133
+ buffer += "In #{$stdout.colorize(k, :bold)}:"
134
+ recurse_required(v, user[k] ||= {}, info, buffer)
135
+ buffer = ''
136
+ else
137
+ next if user[k]
138
+ $stdout.puts '-'*80
139
+ $stdout.puts buffer if buffer.length > 0
140
+ info_block = info_for(k, info)
141
+ $stdout.puts info_block if info_block.length > 0
142
+ $stdout.puts "For #{$stdout.colorize(k, :bold)} (#{$stdout.colorize(v, :red)}):"
143
+ get_response(user, k, v)
144
+ end
145
+ end
146
+ end
147
+
148
+ # Handle getting the response from the user and validating input.
149
+ def get_response(user, key, value)
150
+ done = false
151
+ while !done
152
+ begin
153
+ val = validate_response(key, value, $stdin.gets.chomp)
154
+ user[key] = val unless val == :skip
155
+ done = true
156
+ rescue ArgumentError => e
157
+ $stdout.puts e.to_s
158
+ $stdout.puts 'Retry?'
159
+ res = $stdin.gets
160
+ done = (res.match(/^(Y|y)/) == nil)
161
+ end
162
+ end
163
+ end
164
+
165
+ # Validate input is as expected.
166
+ def validate_response(key, expect, value)
167
+ return :skip if value.match(%r(s|skip))
168
+
169
+ case key
170
+ when :ssh_key
171
+ value.gsub!(%r(^~), ENV['HOME']) if value.match(%r(^~))
172
+
173
+ fail ArgumentError.new('Invalid input provided.') unless
174
+ File.exists?(value) || value == ''
175
+ end
176
+
177
+ case expect
178
+ when :arn_string
179
+ fail ArgumentError.new('Invalid ARN provided.') unless
180
+ value.match(/^.*:.*:.*:.*:([0-9]+):.*$||/)
181
+ when :array
182
+ value = value.split(%r([^\\](:|\|)))
183
+ end
184
+
185
+ value
186
+ end
187
+
188
+ # Print warning info associated with a particular attributes.
189
+ def info_for(key, info)
190
+ return $stdout.colorize(info[key], :bold) if info.key?(key)
191
+ ''
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Command: cook
3
+ #
4
+
5
+ require 'rugged'
6
+ require 'ridoku/base'
7
+
8
+ module Ridoku
9
+ register :cook
10
+
11
+ class Cook < Base
12
+
13
+ def run
14
+ command = Base.config[:command]
15
+ sub_command = (command.length > 0 && command[1]) || nil
16
+
17
+ Base.fetch_stack
18
+
19
+ case sub_command
20
+ # when 'list', nil
21
+ # list
22
+ when 'update'
23
+ update
24
+ when 'run'
25
+ cook
26
+ else
27
+ print_cook_help
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def list
34
+ update unless File.exists?(cookbook_path)
35
+ end
36
+
37
+ def update
38
+ $stdout.puts "Updating custom cookbooks..."
39
+
40
+ Base.run_command(Base.update_cookbooks(Base.extract_instance_ids))
41
+ end
42
+
43
+ class << self
44
+ def valid_recipe_format?(recipes)
45
+ return false if recipes.length == 0
46
+
47
+ recipe = %r(^[a-zA-Z0-9_\-]+(::[a-zA-Z0-9_\-]+){0,1}$)
48
+ recipes.each do |cr|
49
+ return false unless cr.match(recipe)
50
+ end
51
+
52
+ true
53
+ end
54
+
55
+ def cook_recipe(recipes, custom_json = nil)
56
+ cook_recipe_on_layers(recipes, nil, custom_json)
57
+ end
58
+
59
+ def cook_recipe_on_layers(recipes, layers, custom_json = nil)
60
+ Base.fetch_app()
61
+
62
+ recipes = [recipes] unless recipes.is_a?(Array)
63
+
64
+ unless valid_recipe_format?(recipes)
65
+ $stderr.puts 'Invalid recipes provided.'
66
+ print_cook_help
67
+ exit 1
68
+ end
69
+
70
+ instance_ids = Base.extract_instance_ids(layers)
71
+
72
+ if instance_ids.length == 0
73
+ $stderr.puts 'No valid instances available.'
74
+ exit 1
75
+ end
76
+
77
+ $stdout.puts "Running recipes:"
78
+ recipes.each do |arg|
79
+ $stdout.puts " #{$stdout.colorize(arg, :green)}"
80
+ end
81
+
82
+ command = Base.execute_recipes(Base.app[:app_id], instance_ids,
83
+ Base.config[:comment], recipes, custom_json)
84
+
85
+ Base.run_command(command)
86
+ end
87
+ end
88
+
89
+ def cook
90
+ Ridoku::Cook.cook_recipe(ARGV)
91
+ end
92
+
93
+ def print_cook_help
94
+ $stderr.puts <<-EOF
95
+ Command: cook
96
+
97
+ List/Modify the current app's associated domains.
98
+ cook:run run a specific or set of 'cookbook::recipe'
99
+ cook:update update the specified instance 'cookboooks'
100
+ EOF
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,101 @@
1
+ #
2
+ #
3
+
4
+ require 'ridoku/base'
5
+
6
+ module Ridoku
7
+ register :create
8
+
9
+ class Create < Base
10
+
11
+ def run
12
+ command = Base.config[:command]
13
+ sub_command = (command.length > 0 && command[1]) || nil
14
+ type = (command.length > 1 && command[2]) || nil
15
+
16
+ case sub_command
17
+ when 'app'
18
+ app(type || 'rails')
19
+ else
20
+ print_create_help
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def configure_from_cwd(opt)
27
+ return unless File.exists?('.git')
28
+
29
+ `git remote -v | grep fetch`.match(%r(origin\s*(git@[^\s]*).*)) do |m|
30
+ opt[:type] = 'git'
31
+ opt[:url] = m[1]
32
+ $stdout.puts "Setting Git Application Source from environment:"
33
+ $stdout.puts "Url: #{$stdout.colorize(m[1], :green)}"
34
+ end
35
+ end
36
+
37
+ def print_create_help
38
+ $stderr.puts <<-EOF
39
+ Command: create
40
+
41
+ List/Modify the current layer's package dependencies.
42
+ create show this help
43
+ create:app[:rails] <name> create a new app on the --stack
44
+
45
+ Currently, if the stack does not exist for a particular app type, you
46
+ will have to create it manually.
47
+ EOF
48
+ end
49
+
50
+ def app(type)
51
+ Base.fetch_stack
52
+ Base.fetch_app
53
+
54
+ unless ARGV.length > 0
55
+ $stderr.puts $stderr.colorize('App name not specified', :red)
56
+ print_create_help
57
+ exit 1
58
+ end
59
+
60
+ existing = Base.app_list.select do |app|
61
+ app[:name] == ARGV[0]
62
+ end
63
+
64
+ if existing.length > 0
65
+ $stderr.puts $stderr.colorize("App #{ARGV[0]} already exists", :red)
66
+ print_create_help
67
+ exit 1
68
+ end
69
+
70
+ config = {
71
+ type: type,
72
+ name: ARGV[0],
73
+ shortname: ARGV[0].downcase.gsub(%r([^a-z0-9]), '-')
74
+ }
75
+
76
+ config.tap do |opt|
77
+ opt[:domains] = Base.config[:domains] if Base.config.key?(:domains)
78
+ opt[:app_source] = {}.tap do |as|
79
+ configure_from_cwd(as)
80
+ as[:ssh_key] = Base.config[:ssh_key] if Base.config.key?(:ssh_key)
81
+ end
82
+ opt[:attributes] = {}.tap do |atr|
83
+ atr[:rails_env] = Base.config[:rails_env] if Base.config.key?(:rails_env)
84
+ end
85
+ end
86
+
87
+ appconfig = RailsDefaults.new.defaults_with_wizard(:app, config)
88
+
89
+ begin
90
+ Base.create_app(appconfig)
91
+ rescue ::ArgumentError => e
92
+ $stderr.puts e.to_s
93
+ end
94
+ end
95
+
96
+ def instance
97
+ $stderr.puts 'Create instance not yet implemented.'
98
+ $stderr.puts 'Create an instance for a given layer using OpsWorks Dashboard.'
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,227 @@
1
+ # encoding: utf-8
2
+
3
+ #
4
+ # Command: cron
5
+ #
6
+
7
+ # [:day, :hour, :minute, :month, :weekday, :path, :type, :action]
8
+
9
+ require 'ridoku/base'
10
+
11
+ module Ridoku
12
+ register :cron
13
+
14
+ class Cron < Base
15
+ attr_accessor :cron
16
+
17
+ def run
18
+ command = Base.config[:command]
19
+ sub_command = (command.length > 0 && command[1]) || nil
20
+
21
+ load_environment
22
+
23
+ case sub_command
24
+ when 'list', nil
25
+ list
26
+ when 'set', 'add', 'update'
27
+ add
28
+ when 'delete'
29
+ delete
30
+ when 'remove'
31
+ remove
32
+ else
33
+ print_cron_help
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def load_environment
40
+ Base.fetch_stack
41
+ Base.fetch_instance('workers')
42
+
43
+ @default_instance = Base.instances.first[:hostname]
44
+ self.cron = (Base.custom_json['deploy'][Base.config[:app].downcase]['cron'] ||= {})
45
+ end
46
+
47
+ def print_cron_help
48
+ $stderr.puts <<-EOF
49
+ Command: cron
50
+
51
+ List/Modify the current app's associated cron.
52
+ cron[:list] lists the cron jobs associated with an application.
53
+ cron:add cron job, e.g., runner:scripts/runme.rb hour:0 minute:0
54
+ cron:delete delete this specific cron job
55
+ cron:remove removes a cron from the list (run after a delete and push)
56
+ cron:push update running cron jobs
57
+
58
+ Columns Value (default: *) Label
59
+ M Minute 0-59 minute
60
+ H Hour 0-23 hour
61
+ DM Day of Month 0-30 day_of_month
62
+ MO Month 0-11 month
63
+ DW Day of Week 0-6 day_of_week
64
+
65
+ All cron jobs are run on the Workers layer or the AssetMaster on the
66
+ Rails Application layer if one is not set, unless otherwise specified.
67
+
68
+ Example 'cron:list' output:
69
+ Type Scripts M H DM MO DW
70
+ runner scripts/runme.rb 0 0 * * *
71
+
72
+ The above list output indicates a DelayedJob cron and two separate
73
+ delayed_job processes. One processing 'mail'; the other processing 'sms'.
74
+
75
+ examples:
76
+ $ cron
77
+ No cron specified!
78
+ $ cron:add type:runner path:scripts/runme.rb hour:0 minute:0
79
+ $ cron:add type:runner path:scripts/runme_also.rb minute:*/5 instance:mukujara
80
+ $ cron:list
81
+ Type Scripts M H DM MO DW Instance
82
+ runner scripts/runme.rb 0 0 * * * oiwa
83
+ runner scripts/runme_also.rb */5 * * * * mukujara
84
+ $ cron:delete scrips/runme_also.rb
85
+ Type Scripts M H DM MO DW
86
+ runner scripts/runme.rb 0 0 * * * oiwa
87
+ delete scripts/runme_also.rb - - - - - mukujara
88
+ EOF
89
+ end
90
+
91
+ def list
92
+ if cron.length == 0
93
+ $stdout.puts 'No cron jobs specified!'
94
+ else
95
+
96
+ columns = {
97
+ type: 'Type',
98
+ path: 'Scripts',
99
+ minute: 'M',
100
+ hour: 'H',
101
+ day_of_month: 'DM',
102
+ month: 'MO',
103
+ day_of_week: 'DW',
104
+ instance: 'Instance'
105
+ }
106
+
107
+ offset = {}
108
+
109
+ self.cron.each do |cr|
110
+ columns.keys.each do |key|
111
+ skey = key.to_s
112
+ cr[skey] = '*' unless cr.key?(skey)
113
+ val = cr[skey].length
114
+ offset[key] = val if cr.key?(skey) && val > (offset[key] || 0)
115
+ end
116
+ end
117
+
118
+ columns.keys.each do |key|
119
+ offset[key] = columns[key].length if
120
+ columns[key].length > (offset[key] || 0)
121
+ end
122
+
123
+ $stdout.puts $stdout.colorize(line(offset, columns), :bold)
124
+ self.cron.each { |cr| $stdout.puts line(offset, cr, columns.keys) }
125
+ end
126
+ rescue =>e
127
+ puts e.backtrace
128
+ puts e
129
+ end
130
+
131
+ def add
132
+
133
+ croninfo = {}
134
+ cronindex = nil
135
+
136
+ ARGV.each do |cron|
137
+ info = cron.split(':',2)
138
+ croninfo[info[0].to_s] = info[1]
139
+ cronindex = get_path_index(info[1]) if info[0] == 'path'
140
+ end
141
+
142
+ croninfo['instance'] = @default_instance unless croninfo.key?('instance')
143
+
144
+ if cronindex.nil?
145
+ self.cron << croninfo
146
+ else
147
+ self.cron[cronindex] = croninfo
148
+ end
149
+
150
+ list
151
+ Base.save_stack
152
+ end
153
+
154
+ def delete
155
+ return print_cron_help unless ARGV.length > 0
156
+ cronindex = get_path_index(ARGV.first)
157
+
158
+ if cronindex.nil?
159
+ $stdout.puts $stdout.colorize(
160
+ 'Unable to find the specified script path in the cron list.', :red)
161
+ return list
162
+ end
163
+
164
+ cr = self.cron[cronindex]
165
+ cr['type'] = 'delete'
166
+ cr['minute'] = '-'
167
+ cr['hour'] = '-'
168
+ cr['day_of_month'] = '-'
169
+ cr['month'] = '-'
170
+ cr['day_of_week'] = '-'
171
+
172
+ list
173
+ Base.save_stack
174
+ end
175
+
176
+ def remove
177
+ return print_cron_help unless ARGV.length > 0
178
+ cronindex = get_path_index(ARGV.first)
179
+
180
+ if cronindex.nil?
181
+ $stdout.puts $stdout.colorize(
182
+ 'Unable to find the specified script path in the cron list.', :red)
183
+ return list
184
+ end
185
+
186
+ self.cron.delete_at(cronindex)
187
+
188
+ list
189
+ Base.save_stack
190
+ end
191
+
192
+ protected
193
+
194
+ def get_path_index(path)
195
+ cron.each_with_index do |cr, idx|
196
+ return idx if cr['path'] == path
197
+ end
198
+
199
+ return nil
200
+ end
201
+
202
+ def line(offset, columns, keys = nil)
203
+ keys ||= columns.keys
204
+ output = ''
205
+
206
+ keys.each do |key|
207
+ skey = key.to_s
208
+ content = columns[key] || columns[skey]
209
+ if skey == 'type'
210
+ case content
211
+ when 'delete'
212
+ output += $stdout.colorize(content, :red)
213
+ when 'runner'
214
+ output += $stdout.colorize(content, :green)
215
+ else
216
+ output += $stdout.colorize(content, :yellow)
217
+ end
218
+ else
219
+ output += content
220
+ end
221
+ output += ' '*(offset[key] - content.length + 2)
222
+ end
223
+
224
+ output
225
+ end
226
+ end
227
+ end