vhost_generator 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+ Wraps "foreman export" and "vhost-generator" in a single command.
5
+ TODO: rewrite as a real application, not just a glorified shell script.
6
+ =end
7
+
8
+ require 'optparse'
9
+ require 'ostruct'
10
+ require 'shellwords'
11
+
12
+ class ForemanExportApplication
13
+ def initialize(output_stream)
14
+ @output_stream = output_stream
15
+ end
16
+
17
+ def run(argv)
18
+ standard_exception_handling do
19
+ handle_arguments!(argv)
20
+ apply_defaults_and_sanitize_config
21
+ config.instance_ports = InstancePortCalculator.new(config).apply
22
+ commands = String(ShellBuilder.new(config, argv).apply)
23
+ if config.dry_run
24
+ puts commands
25
+ puts "# Now, try to run this script again without the --dry-run switch!"
26
+ else
27
+ exec commands
28
+ end
29
+ end
30
+ end
31
+
32
+ protected
33
+
34
+ def config
35
+ @config ||= OpenStruct.new
36
+ end
37
+
38
+ def handle_arguments!(argv)
39
+ OptionParser.new do |opts|
40
+ opts.banner = "Usage: foreman-export-vhost FORMAT LOCATION"
41
+
42
+ opts.separator ""
43
+ opts.separator "Foreman options:"
44
+ foreman_options.each { |args| opts.on(*args) }
45
+
46
+ opts.separator ""
47
+ opts.separator "Vhost-generator options:"
48
+ generator_options.each { |args| opts.on(*args) }
49
+
50
+ opts.separator ""
51
+
52
+ opts.on_tail("-h", "--help", "-H", "Display this help message.") do
53
+ puts opts
54
+ exit(true)
55
+ end
56
+ end.parse!(argv)
57
+ end
58
+
59
+ def foreman_options
60
+ [
61
+ ['-a', '--app=APP', lambda { |value| config.app = value }],
62
+ ['-l', '--log=LOG', lambda { |value| config.log = value }],
63
+ ['-e', '--env=ENV',
64
+ '# Specify an environment file to load, defaults to .env',
65
+ lambda { |value| config.env = value }],
66
+ ['-p', '--port=N',
67
+ '# Default: 5000',
68
+ lambda { |value| config.port = Integer(value) }],
69
+ ['-u', '--user=USER', lambda { |value| config.user = value }],
70
+ ['-t', '--template=TEMPLATE', lambda { |value| config.template = value }],
71
+ ['-c', '--concurrency=alpha=5,bar=3',
72
+ lambda { |value| config.concurrency = value }],
73
+ ['-f', '--procfile=PROCFILE',
74
+ '# Default: Procfile',
75
+ lambda { |value| config.procfile = value }],
76
+ ['-d', '--root=ROOT',
77
+ '# Default: Procfile directory',
78
+ lambda { |value| config.root = value }],
79
+ ].freeze
80
+ end
81
+
82
+ def generator_options
83
+ [
84
+ ['-F', '--static-folder=FOLDER',
85
+ '# Default: "public" folder in Procfile directory',
86
+ lambda { |value| config.static_folder = value }],
87
+ ['-L', '--server-ports=PORTS',
88
+ '# Default: 80',
89
+ lambda { |value| config.server_ports = value }],
90
+ ['-S', '--server-names=NAMES',
91
+ '# Default: localhost',
92
+ lambda { |value| config.server_names = value }],
93
+ ['-K', '--foreman-process-type=TYPE',
94
+ '# Default: web (must have entry of that name in Procfile)',
95
+ lambda { |value| config.process_type = value }],
96
+ ['-G', '--generator=GENERATOR',
97
+ '# Default: nginx (only supported for now)',
98
+ lambda { |value| config.generator = value }],
99
+ ['-N', '--dry-run', lambda { |value| config.dry_run = true }],
100
+ ['-R', '--stop-start-service',
101
+ '# Starts and stops APP service (may not work everywhere)',
102
+ lambda { |value| config.service = true }],
103
+ ].freeze
104
+ end
105
+
106
+ def apply_defaults_and_sanitize_config
107
+ config.app ||= 'myapp'
108
+ config.port ||= 5000
109
+ config.procfile ||= 'Procfile'
110
+ config.procfile = File.expand_path(config.procfile)
111
+ config.root ||= File.dirname(config.procfile)
112
+ config.root = File.expand_path(config.root)
113
+ config.static_folder ||= File.join(config.root, 'public')
114
+ config.static_folder = File.expand_path(config.static_folder)
115
+ config.generator ||= 'nginx'
116
+ config.process_type ||= 'web'
117
+ end
118
+
119
+ class InstancePortCalculator
120
+ attr_reader :config
121
+
122
+ def initialize(config)
123
+ @config = config
124
+ end
125
+
126
+ def apply
127
+ c = process_concurrency(config.concurrency, config.process_type)
128
+ o = process_offset(config.procfile, config.process_type)
129
+ raise ArgumentError, "Can't find process type %s in %s, sorry." % [
130
+ config.process_type.inspect, config.procfile.inspect
131
+ ] unless o
132
+ base_port = config.port + 100 * o
133
+ (base_port...base_port + c).to_a.join(',')
134
+ end
135
+
136
+ def process_concurrency(concurrency, process_type)
137
+ if concurrency && process_type
138
+ per_type = Hash[concurrency.split(',').map { |v| v.split('=', 2) }]
139
+ Integer(per_type[process_type] || 1)
140
+ else
141
+ 1
142
+ end
143
+ end
144
+
145
+ def process_offset(procfile, process_type)
146
+ process_type_re = %r(#{Regexp.escape(process_type)}:)
147
+ File.read(procfile).lines.each_with_index do |l,i|
148
+ return i if l =~ process_type_re
149
+ end
150
+ nil # not found
151
+ end
152
+ end
153
+
154
+ class ShellBuilder
155
+ def initialize(config, argv, commands=Array.new)
156
+ @config = config
157
+ @argv = argv
158
+ @commands = commands
159
+ end
160
+
161
+ def to_str
162
+ commands.collect { |c| c.join(' ') }.join($/)
163
+ end
164
+
165
+ def apply
166
+ commands << %w(bundle exec foreman run vhost-generator) +
167
+ escape(generator_flags) +
168
+ %w(| sudo tee) + escape(vhost_config)
169
+ commands << %w(sudo service) + escape(service) + %w(reload)
170
+ commands << %w(sudo service) + escape(app) +
171
+ %w(stop >/dev/null 2>&1 || true) if config.service
172
+ commands << %w(sudo rm -rf) +
173
+ Array(Shellwords.escape("#{target}/#{app}") + "-*.conf")
174
+ commands << %w(sudo bundle exec foreman export) +
175
+ escape(argv + foreman_flags)
176
+ commands << %w(sudo service) + escape(app) + %w(start)
177
+ commands << ['echo',
178
+ '"Finished, now open your browser and see if all works."']
179
+ self
180
+ end
181
+
182
+ protected
183
+
184
+ attr_reader :config, :argv, :commands
185
+
186
+ def generator_flags(flags=Array.new)
187
+ flags << '-f' << config.static_folder if config.static_folder
188
+ flags << '-l' << config.server_ports if config.server_ports
189
+ flags << '-s' << config.server_names if config.server_names
190
+ flags << '-p' << config.instance_ports if config.instance_ports
191
+ flags << '-g' << config.generator if config.generator
192
+ flags << '-o' << "upstream=#{app}" if config.generator == 'nginx' && app
193
+ end
194
+
195
+ def foreman_flags(flags=Array.new)
196
+ flags << '-a' << config.app if config.app
197
+ flags << '-l' << config.log if config.log
198
+ flags << '-e' << config.env if config.env
199
+ flags << '-p' << String(config.port) if config.port
200
+ flags << '-u' << config.user if config.user
201
+ flags << '-t' << config.template if config.template
202
+ flags << '-c' << config.concurrency if config.concurrency
203
+ flags << '-f' << config.procfile if config.procfile
204
+ flags << '-d' << config.root if config.root
205
+ end
206
+
207
+ def app
208
+ config.app
209
+ end
210
+
211
+ def service
212
+ config.generator # nginx
213
+ end
214
+
215
+ def target
216
+ argv[1] # eg. /etc/init
217
+ end
218
+
219
+ def vhost_config
220
+ "/etc/#{service}/sites-enabled/rails-#{app}.conf"
221
+ end
222
+
223
+ def escape(args)
224
+ Array(args).map { |c| Shellwords.shellescape(c) }
225
+ end
226
+ end
227
+
228
+ private
229
+
230
+ def puts(*args)
231
+ @output_stream.puts(*args)
232
+ end
233
+
234
+ # Provide standard exception handling for the given block.
235
+ def standard_exception_handling
236
+ begin
237
+ yield
238
+ rescue SystemExit => ex
239
+ # Exit silently with current status
240
+ raise
241
+ rescue OptionParser::InvalidOption => ex
242
+ $stderr.puts ex.message
243
+ exit(false)
244
+ rescue Exception => ex
245
+ # Exit with error message
246
+ display_error_message(ex)
247
+ exit(false)
248
+ end
249
+ end
250
+
251
+ # Display the error message that caused the exception.
252
+ def display_error_message(ex)
253
+ $stderr.puts "#{@name} aborted!"
254
+ $stderr.puts ex.message
255
+ $stderr.puts ex.backtrace.join("\n")
256
+ end
257
+ end
258
+
259
+ ForemanExportApplication.new($stdout).run(ARGV)
260
+
@@ -0,0 +1,62 @@
1
+ Feature: Wrap "foreman export"
2
+
3
+ In order to generate and install a foreman web application to a virtualhost
4
+ As a user of the library
5
+ I want to run "foreman export" and "vhost-generator" together with DRY arguments.
6
+
7
+ Scenario: display help
8
+ When I run `bundle exec foreman-export-vhost --help`
9
+ Then it should pass with:
10
+ """
11
+ Usage: foreman-export-vhost FORMAT LOCATION
12
+
13
+ Foreman options:
14
+ -a, --app=APP
15
+ -l, --log=LOG
16
+ -e, --env=ENV # Specify an environment file to load, defaults to .env
17
+ -p, --port=N # Default: 5000
18
+ -u, --user=USER
19
+ -t, --template=TEMPLATE
20
+ -c, --concurrency=alpha=5,bar=3
21
+ -f, --procfile=PROCFILE # Default: Procfile
22
+ -d, --root=ROOT # Default: Procfile directory
23
+
24
+ Vhost-generator options:
25
+ -F, --static-folder=FOLDER # Default: "public" folder in Procfile directory
26
+ -L, --server-ports=PORTS # Default: 80
27
+ -S, --server-names=NAMES # Default: localhost
28
+ -K, --foreman-process-type=TYPE # Default: web (must have entry of that name in Procfile)
29
+ -G, --generator=GENERATOR # Default: nginx (only supported for now)
30
+ -N, --dry-run
31
+ -R, --stop-start-service # Starts and stops APP service (may not work everywhere)
32
+
33
+ -h, -H, --help Display this help message.
34
+ """
35
+
36
+ Scenario: display source that will be executed
37
+ Given a file named "Procfile" with:
38
+ """
39
+ clock: ....
40
+ web: bundle exec webserver -p $PORT
41
+ """
42
+ When I run `bundle exec foreman-export-vhost upstart /etc/init -a MYAPP -u MYUSER -p 6000 -c clock=1,web=2 -L 80,81 -S localhost,myapp.com -K web -G nginx -N -R`
43
+ Then the output should match:
44
+ """
45
+ bundle exec foreman run vhost-generator -f /.*/public -l 80,81 -s localhost,myapp.com -p 6100,6101 -g nginx -o upstream=MYAPP | sudo tee /etc/nginx/sites-enabled/rails-MYAPP.conf
46
+ """
47
+ And the output should contain:
48
+ """
49
+ sudo service nginx reload
50
+ sudo service MYAPP stop >/dev/null 2>&1 || true
51
+ sudo rm -rf /etc/init/MYAPP-*.conf
52
+ """
53
+ And the output should match:
54
+ """
55
+ sudo bundle exec foreman export upstart /etc/init -a MYAPP -p 6000 -u MYUSER -c clock\\=1,web\\=2 -f /.*/Procfile -d /.*$
56
+ """
57
+ And the output should contain:
58
+ """
59
+ sudo service MYAPP start
60
+ echo "Finished, now open your browser and see if all works."
61
+ # Now, try to run this script again without the --dry-run switch!
62
+ """
@@ -8,5 +8,5 @@ Feature: Output program version
8
8
  When I run `bundle exec vhost-generator --version`
9
9
  Then it should pass with:
10
10
  """
11
- vhost-generator, version 0.2.0
11
+ vhost-generator, version 0.2.1
12
12
  """
@@ -1,3 +1,3 @@
1
1
  module VhostGenerator
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vhost_generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-01 00:00:00.000000000 Z
12
+ date: 2012-10-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -80,6 +80,7 @@ description: vhost_generator outputs a general-purpose VirtualHost configuration
80
80
  email:
81
81
  - julien.perville@mingalar.fr
82
82
  executables:
83
+ - foreman-export-vhost
83
84
  - vhost-generator
84
85
  extensions: []
85
86
  extra_rdoc_files: []
@@ -89,7 +90,9 @@ files:
89
90
  - LICENSE.txt
90
91
  - README.md
91
92
  - Rakefile
93
+ - bin/foreman-export-vhost
92
94
  - bin/vhost-generator
95
+ - features/foreman-export-vhost.feature
93
96
  - features/help.feature
94
97
  - features/output-nginx.feature
95
98
  - features/step_definitions/dev_steps.rb
@@ -123,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
126
  version: '0'
124
127
  segments:
125
128
  - 0
126
- hash: -418120369
129
+ hash: -251905965
127
130
  required_rubygems_version: !ruby/object:Gem::Requirement
128
131
  none: false
129
132
  requirements:
@@ -132,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
135
  version: '0'
133
136
  segments:
134
137
  - 0
135
- hash: -418120369
138
+ hash: -251905965
136
139
  requirements: []
137
140
  rubyforge_project:
138
141
  rubygems_version: 1.8.23
@@ -141,6 +144,7 @@ specification_version: 3
141
144
  summary: vhost_generator outputs nginx or apache VirtualHost configurations to run
142
145
  your web application
143
146
  test_files:
147
+ - features/foreman-export-vhost.feature
144
148
  - features/help.feature
145
149
  - features/output-nginx.feature
146
150
  - features/step_definitions/dev_steps.rb