vhost_generator 0.2.0 → 0.2.1

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,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