nginx_stage 0.3.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +3 -0
  4. data/CHANGELOG.md +199 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +19 -0
  8. data/Rakefile +36 -0
  9. data/bin/node +15 -0
  10. data/bin/ood_ruby +38 -0
  11. data/bin/python +15 -0
  12. data/bin/ruby +15 -0
  13. data/lib/nginx_stage/application.rb +125 -0
  14. data/lib/nginx_stage/configuration.rb +418 -0
  15. data/lib/nginx_stage/errors.rb +46 -0
  16. data/lib/nginx_stage/generator.rb +139 -0
  17. data/lib/nginx_stage/generator_helpers.rb +68 -0
  18. data/lib/nginx_stage/generators/app_clean_generator.rb +38 -0
  19. data/lib/nginx_stage/generators/app_config_generator.rb +101 -0
  20. data/lib/nginx_stage/generators/app_list_generator.rb +24 -0
  21. data/lib/nginx_stage/generators/app_reset_generator.rb +54 -0
  22. data/lib/nginx_stage/generators/nginx_clean_generator.rb +61 -0
  23. data/lib/nginx_stage/generators/nginx_list_generator.rb +22 -0
  24. data/lib/nginx_stage/generators/nginx_process_generator.rb +47 -0
  25. data/lib/nginx_stage/generators/nginx_show_generator.rb +48 -0
  26. data/lib/nginx_stage/generators/pun_config_generator.rb +102 -0
  27. data/lib/nginx_stage/pid_file.rb +37 -0
  28. data/lib/nginx_stage/socket_file.rb +51 -0
  29. data/lib/nginx_stage/user.rb +75 -0
  30. data/lib/nginx_stage/version.rb +4 -0
  31. data/lib/nginx_stage/views/app_config_view.rb +42 -0
  32. data/lib/nginx_stage/views/pun_config_view.rb +144 -0
  33. data/lib/nginx_stage.rb +133 -0
  34. data/nginx_stage.gemspec +24 -0
  35. data/sbin/nginx_stage +7 -0
  36. data/share/nginx_stage_example.yml +166 -0
  37. data/templates/app.conf.erb +14 -0
  38. data/templates/pun.conf.erb +79 -0
  39. data/test/minitest_helper.rb +4 -0
  40. data/test/test_nginx_stage.rb +11 -0
  41. metadata +132 -0
@@ -0,0 +1,418 @@
1
+ require 'yaml'
2
+
3
+ module NginxStage
4
+ # An object that stores the configuration options to control NginxStage's
5
+ # behavior.
6
+ module Configuration
7
+ #
8
+ # Global
9
+ #
10
+
11
+ # Location of ERB templates used as NGINX configs
12
+ # @return [String] the ERB templates root path
13
+ attr_accessor :template_root
14
+
15
+ #
16
+ # NGINX-specific configuration options
17
+ #
18
+
19
+ # The reverse proxy daemon user used to access the sockets
20
+ # @return [String] the reverse-proxy-daemon user
21
+ attr_accessor :proxy_user
22
+
23
+ # Path to system-installed NGINX binary
24
+ # @return [String] the system-installed NGINX binary
25
+ attr_accessor :nginx_bin
26
+
27
+ # A whitelist of signals that can be sent to the NGINX process
28
+ # @return [Array<Symbol>] whitelist of NGINX process signals
29
+ attr_accessor :nginx_signals
30
+
31
+ # Path to system-installed NGINX mime.types config file
32
+ # @return [String] the system-installed NGINX mime.types config
33
+ attr_accessor :mime_types_path
34
+
35
+ # Path to system-installed Passenger locations.ini file
36
+ # @return [String] the system-installed Passenger locations.ini
37
+ attr_accessor :passenger_root
38
+
39
+ # Path to system-installed Ruby binary
40
+ # @return [String] the system-installed Ruby binary
41
+ attr_accessor :passenger_ruby
42
+
43
+ # Path to system-installed NodeJS binary
44
+ # @return [String] the system-installed NodeJS binary
45
+ attr_accessor :passenger_nodejs
46
+
47
+ # Path to system-installed python binary
48
+ # @return [String] the system-installed python binary
49
+ attr_accessor :passenger_python
50
+
51
+ #
52
+ # per-user NGINX configuration options
53
+ #
54
+
55
+ # Root location where per-user NGINX configs are generated
56
+ # Path to generated per-user NGINX config file
57
+ # @example User Bob's nginx config
58
+ # pun_config_path(user: 'bob')
59
+ # #=> "/var/log/nginx/config/puns/bob.conf"
60
+ # @param user [String] the user of the nginx process
61
+ # @return [String] the path to the per-user nginx config file
62
+ def pun_config_path(user:)
63
+ File.expand_path @pun_config_path % {user: user}
64
+ end
65
+
66
+ attr_writer :pun_config_path
67
+
68
+ # Path to user's personal tmp root
69
+ # @example User Bob's nginx tmp root
70
+ # pun_tmp_root(user: 'bob')
71
+ # #=> "/var/lib/nginx/tmp/bob"
72
+ # @param user [String] the user of the nginx process
73
+ # @return [String] the path to the tmp root
74
+ def pun_tmp_root(user:)
75
+ File.expand_path @pun_tmp_root % {user: user}
76
+ end
77
+
78
+ attr_writer :pun_tmp_root
79
+
80
+ # Path to the user's personal access.log
81
+ # @example User Bob's nginx access log
82
+ # pun_access_log_path(user: 'bob')
83
+ # #=> "/var/log/nginx/bob/access.log"
84
+ # @param user [String] the user of the nginx process
85
+ # @return [String] the path to the nginx access log
86
+ def pun_access_log_path(user:)
87
+ File.expand_path @pun_access_log_path % {user: user}
88
+ end
89
+
90
+ attr_writer :pun_access_log_path
91
+
92
+ # Path to the user's personal error.log
93
+ # @example User Bob's nginx error log
94
+ # pun_error_log_path(user: 'bob')
95
+ # #=> "/var/log/nginx/bob/error.log"
96
+ # @param user [String] the user of the nginx process
97
+ # @return [String] the path to the nginx error log
98
+ def pun_error_log_path(user:)
99
+ File.expand_path @pun_error_log_path % {user: user}
100
+ end
101
+
102
+ attr_writer :pun_error_log_path
103
+
104
+ # Path to the user's per-user NGINX pid file
105
+ # @example User Bob's pid file
106
+ # pun_pid_path(user: 'bob')
107
+ # #=> "/var/run/nginx/bob/passenger.pid"
108
+ # @param user [String] the user of nginx process
109
+ # @return [String] the path to the pid file
110
+ def pun_pid_path(user:)
111
+ File.expand_path @pun_pid_path % {user: user}
112
+ end
113
+
114
+ attr_writer :pun_pid_path
115
+
116
+ # Path to the user's per-user NGINX socket file
117
+ # @example User Bob's socket file
118
+ # socket_path(user: 'bob')
119
+ # #=> "/var/run/nginx/bob/passenger.sock"
120
+ # @param user [String] the user of nginx process
121
+ # @return [String] the path to the socket file
122
+ def pun_socket_path(user:)
123
+ File.expand_path @pun_socket_path % {user: user}
124
+ end
125
+
126
+ attr_writer :pun_socket_path
127
+
128
+ # Path to the local filesystem root where the per-user Nginx serves files
129
+ # from with the sendfile feature
130
+ # @example Filesystem root for user Bob
131
+ # pun_sendfile_root(user: 'bob')
132
+ # #=> "/"
133
+ # @param user [String] the user of the nginx process
134
+ # @return [String] the path to the filesystem root that is served
135
+ def pun_sendfile_root(user:)
136
+ File.expand_path @pun_sendfile_root % {user: user}
137
+ end
138
+
139
+ attr_writer :pun_sendfile_root
140
+
141
+ # The internal URI used to access the filesystem for downloading files from
142
+ # the browser, not including any base-uri
143
+ # @example
144
+ # pun_sendfile_uri
145
+ # #=> "/sendfile"
146
+ # @return [String] the internal URI used to access filesystem
147
+ attr_accessor :pun_sendfile_uri
148
+
149
+ # List of hashes that help define the location of the app configs for the
150
+ # per-user NGINX config. These will be arguments for {#app_config_path}.
151
+ # @example User Bob's app configs
152
+ # pun_app_configs(user: 'bob')
153
+ # #=> [ {env: :dev, owner: 'bob', name: '*'},
154
+ # {env: :usr, owner: '*', name: '*'} ]
155
+ # @param user [String] the user of the nginx process
156
+ # @return [Array<Hash>] list of hashes detailing app config locations
157
+ def pun_app_configs(user:)
158
+ @pun_app_configs.map do |envmt|
159
+ envmt.each_with_object({}) do |(k, v), h|
160
+ h[k] = v.respond_to?(:%) ? (v % {user: user}) : v
161
+ h[k] = v.to_sym if k == :env
162
+ end
163
+ end
164
+ end
165
+
166
+ attr_writer :pun_app_configs
167
+
168
+ #
169
+ # per-user NGINX app configuration options
170
+ #
171
+
172
+ # Path to generated NGINX app config
173
+ # @example Dev app owned by Bob
174
+ # app_config_path(env: :dev, owner: 'bob', name: 'rails1')
175
+ # #=> "/var/lib/nginx/config/apps/dev/bob/rails1.conf"
176
+ # @example User app owned by Dan
177
+ # app_config_path(env: :usr, owner: 'dan', name: 'fillsim')
178
+ # #=> "/var/lib/nginx/config/apps/usr/dan/fillsim.conf"
179
+ # @param env [Symbol] environment the app is run under
180
+ # @param owner [String] the owner of the app
181
+ # @param name [String] the name of the app
182
+ # @return [String] the path to the nginx app config on the local filesystem
183
+ def app_config_path(env:, owner:, name:)
184
+ File.expand_path @app_config_path[env] % {env: env, owner: owner, name: name}
185
+ end
186
+
187
+ attr_writer :app_config_path
188
+
189
+ # Path to the app root on the local filesystem
190
+ # @example App root for dev app owned by Bob
191
+ # app_root(env: :dev, owner: 'bob', name: 'rails1')
192
+ # #=> "~bob/ondemand/dev/rails1"
193
+ # @example App root for user app owned by Dan
194
+ # app_root(env: :usr, owner: 'dan', name: 'fillsim')
195
+ # #=> "/var/www/ood/apps/usr/dan/gateway/fillsim"
196
+ # @param env [Symbol] environment the app is run under
197
+ # @param owner [String] the owner of the app
198
+ # @param name [String] the name of the app
199
+ # @return [String] the path to the app root on the local filesystem
200
+ # @raise [InvalidRequest] if the environment specified doesn't exist
201
+ def app_root(env:, owner:, name:)
202
+ File.expand_path(
203
+ @app_root.fetch(env) do
204
+ raise InvalidRequest, "invalid request environment: #{env}"
205
+ end % {env: env, owner: owner, name: name}
206
+ )
207
+ end
208
+
209
+ attr_writer :app_root
210
+
211
+ # The URI used to access the app from the browser, not including any base-uri
212
+ # @example URI for dev app owned by Bob
213
+ # app_request_uri(env: :dev, owner: 'bob', name: 'rails1')
214
+ # #=> "/dev/rails1"
215
+ # @example URI for user app owned by Dan
216
+ # app_request_uri(env: :dev, owner: 'dan', name: 'fillsim')
217
+ # #=> "/usr/dan/fillsim"
218
+ # @param env [Symbol] environment the app is run under
219
+ # @param owner [String] the owner of the app
220
+ # @param name [String] the name of the app
221
+ # @return [String] the URI used to access a given app
222
+ # @raise [InvalidRequest] if the environment specified doesn't exist
223
+ def app_request_uri(env:, owner:, name:)
224
+ @app_request_uri.fetch(env) do
225
+ raise InvalidRequest, "invalid request environment: #{env}"
226
+ end % {env: env, owner: owner, name: name}
227
+ end
228
+
229
+ attr_writer :app_request_uri
230
+
231
+ # Regular expression used to distinguish the environment from a request URI
232
+ # and from there distinguish the app owner and app name
233
+ # @see #app_request_uri
234
+ # @return [Hash] hash of regular expressions used to determine app from app
235
+ # namespace for given environment
236
+ def app_request_regex
237
+ @app_request_regex.each_with_object({}) { |(k, v), h| h[k] = ::Regexp.new v }
238
+ end
239
+
240
+ attr_writer :app_request_regex
241
+
242
+ # Token used to identify a given app from the other apps
243
+ # @example app token for dev app owned by Bob
244
+ # app_token(env: :dev, owner: 'bob', name: 'rails1')
245
+ # #=> "dev/bob/rails1"
246
+ # @example app token for user app owned by Dan
247
+ # app_request_uri(env: :dev, owner: 'dan', name: 'fillsim')
248
+ # #=> "usr/dan/fillsim"
249
+ # @param env [Symbol] environment the app is run under
250
+ # @param owner [String] the owner of the app
251
+ # @param name [String] the name of the app
252
+ # @return [String] the token identifying the app
253
+ # @raise [InvalidRequest] if the environment specified doesn't exist
254
+ def app_token(env:, owner:, name:)
255
+ @app_token.fetch(env) do
256
+ raise InvalidRequest, "invalid request environment: #{env}"
257
+ end % {env: env, owner: owner, name: name}
258
+ end
259
+
260
+ attr_writer :app_token
261
+
262
+ # The passenger environment used for the given app environment
263
+ # @example Dev app owned by Bob
264
+ # app_passenger_env(env: :dev, owner: 'bob', name: 'rails1')
265
+ # #=> "development"
266
+ # @example User app owned by Dan
267
+ # app_passenger_env(env: :usr, owner: 'dan', name: 'fillsim')
268
+ # #=> "production"
269
+ # @param env [Symbol] environment the app is run under
270
+ # @param owner [String] the owner of the app
271
+ # @param name [String] the name of the app
272
+ # @return [String] the passenger environment to run app with in PUN
273
+ def app_passenger_env(env:, owner:, name:)
274
+ @app_passenger_env.fetch(env, "production")
275
+ end
276
+
277
+ attr_writer :app_passenger_env
278
+
279
+ #
280
+ # Validation configuration options
281
+ #
282
+
283
+ # Regular expression used to validate a given user name
284
+ # @return [Regexp] user name regular expression
285
+ def user_regex
286
+ /\A#{@user_regex}\z/
287
+ end
288
+
289
+ attr_writer :user_regex
290
+
291
+ # Minimum user id required to run the per-user NGINX as this user. This
292
+ # restricts running processes as special users (i.e., 'root')
293
+ # @return [Integer] minimum user id required to run as user
294
+ attr_accessor :min_uid
295
+
296
+ # Restrict starting up per-user NGINX process as user with this shell.
297
+ # NB: This only affects the <tt>pun</tt> command, you are still able to
298
+ # start or stop the PUN using other commands (e.g., <tt>nginx</tt>,
299
+ # <tt>nginx_clean</tt>, ...)
300
+ # @return [String] user shell that is blocked
301
+ attr_accessor :disabled_shell
302
+
303
+ #
304
+ # Configuration module
305
+ #
306
+
307
+ # Default configuration file
308
+ # @return [String] path to default yaml configuration file
309
+ def default_config_path
310
+ config = config_file
311
+ unless File.file?(config)
312
+ config = File.join root, 'config', 'nginx_stage.yml'
313
+ warn "[DEPRECATION] The file '#{config}' is being deprecated. Please move this file to '#{config_file}'." if File.file?(config)
314
+ end
315
+ config
316
+ end
317
+
318
+ # Yields the configuration object.
319
+ # @yieldparam [Configuration] config The library configuration
320
+ # @return [void]
321
+ def configure
322
+ yield self
323
+ end
324
+
325
+ # Sets default configuration options in any class that extends {Configuration}
326
+ def self.extended(base)
327
+ base.set_default_configuration
328
+ end
329
+
330
+ # Sets the default configuration options
331
+ # @return [void]
332
+ def set_default_configuration
333
+ self.template_root = "#{root}/templates"
334
+
335
+ self.proxy_user = 'apache'
336
+ self.nginx_bin = '/opt/rh/nginx16/root/usr/sbin/nginx'
337
+ self.nginx_signals = %i(stop quit reopen reload)
338
+ self.mime_types_path = '/opt/rh/nginx16/root/etc/nginx/mime.types'
339
+ self.passenger_root = '/opt/rh/rh-passenger40/root/usr/share/passenger/phusion_passenger/locations.ini'
340
+ self.passenger_ruby = "#{root}/bin/ruby"
341
+ self.passenger_nodejs = "#{root}/bin/node"
342
+ self.passenger_python = "#{root}/bin/python"
343
+
344
+ self.pun_config_path = '/var/lib/nginx/config/puns/%{user}.conf'
345
+ self.pun_tmp_root = '/var/lib/nginx/tmp/%{user}'
346
+ self.pun_access_log_path = '/var/log/nginx/%{user}/access.log'
347
+ self.pun_error_log_path = '/var/log/nginx/%{user}/error.log'
348
+ self.pun_pid_path = '/var/run/nginx/%{user}/passenger.pid'
349
+ self.pun_socket_path = '/var/run/nginx/%{user}/passenger.sock'
350
+ self.pun_sendfile_root = '/'
351
+ self.pun_sendfile_uri = '/sendfile'
352
+ self.pun_app_configs = [
353
+ {env: :dev, owner: '%{user}', name: '*'},
354
+ {env: :usr, owner: '*', name: '*'},
355
+ {env: :sys, owner: '', name: '*'}
356
+ ]
357
+
358
+ self.app_config_path = {
359
+ dev: '/var/lib/nginx/config/apps/dev/%{owner}/%{name}.conf',
360
+ usr: '/var/lib/nginx/config/apps/usr/%{owner}/%{name}.conf',
361
+ sys: '/var/lib/nginx/config/apps/sys/%{name}.conf'
362
+ }
363
+ self.app_root = {
364
+ dev: '~%{owner}/ondemand/dev/%{name}',
365
+ usr: '/var/www/ood/apps/usr/%{owner}/gateway/%{name}',
366
+ sys: '/var/www/ood/apps/sys/%{name}'
367
+ }
368
+ self.app_request_uri = {
369
+ dev: '/dev/%{name}',
370
+ usr: '/usr/%{owner}/%{name}',
371
+ sys: '/sys/%{name}'
372
+ }
373
+ self.app_request_regex = {
374
+ dev: '^/dev/(?<name>[-\w.]+)',
375
+ usr: '^/usr/(?<owner>[\w]+)/(?<name>[-\w.]+)',
376
+ sys: '^/sys/(?<name>[-\w.]+)'
377
+ }
378
+ self.app_token = {
379
+ dev: 'dev/%{owner}/%{name}',
380
+ usr: 'usr/%{owner}/%{name}',
381
+ sys: 'sys/%{name}'
382
+ }
383
+ self.app_passenger_env = {
384
+ dev: 'development',
385
+ usr: 'production',
386
+ sys: 'production'
387
+ }
388
+
389
+ self.user_regex = '[\w@\.\-]+'
390
+ self.min_uid = 1000
391
+ self.disabled_shell = '/access/denied'
392
+
393
+ read_configuration(default_config_path) if File.file?(default_config_path)
394
+ end
395
+
396
+ # Read in a configuration from a file
397
+ # @param file [String] path to the yaml configuration file
398
+ # @return [void]
399
+ def read_configuration(file)
400
+ config_hash = symbolize(YAML.load_file(file)) || {}
401
+ config_hash.each do |k,v|
402
+ if instance_variable_defined? "@#{k}"
403
+ self.send("#{k}=", v)
404
+ else
405
+ $stderr.puts %{Warning: invalid configuration option "#{k}"}
406
+ end
407
+ end
408
+ end
409
+
410
+ private
411
+ # Recursively symbolize keys in hash
412
+ def symbolize(obj)
413
+ return obj.each_with_object({}) {|(k, v), h| h[k.to_sym] = symbolize(v)} if obj.is_a? Hash
414
+ return obj.each_with_object([]) {|v, a| a << symbolize(v)} if obj.is_a? Array
415
+ return obj
416
+ end
417
+ end
418
+ end
@@ -0,0 +1,46 @@
1
+ module NginxStage
2
+ # The root exception class that all NginxStage-specific exceptions inherit from
3
+ class Error < StandardError; end
4
+
5
+ # An exception raised when attempting to set an invalid configuration option
6
+ class InvalidConfigOption < Error; end
7
+
8
+ # An exception raised when attempting to resolve an option that doesn't exist
9
+ class MissingOption < Error; end
10
+
11
+ # An exception raised when attempting to resolve a command that doesn't exist
12
+ class MissingCommand < Error; end
13
+
14
+ # An exception raised when attempting to resolve a command that is invalid
15
+ class InvalidCommand < Error; end
16
+
17
+ # An exception raised when attempting to resolve a user that is invalid
18
+ class InvalidUser < Error; end
19
+
20
+ # An exception raised when attempting to resolve a socket that already exists
21
+ class InvalidSocket < Error; end
22
+
23
+ # An exception raised when attempting to resolve an invalid request option
24
+ class InvalidRequest < Error; end
25
+
26
+ # An exception raised when attempting to resolve an invalid sub-uri option
27
+ class InvalidSubUri < Error; end
28
+
29
+ # An exception raised when attempting to resolve an invalid app-init-url option
30
+ class InvalidAppInitUrl < Error; end
31
+
32
+ # An exception raised when attempting to read a pid file that doesn't exist
33
+ class MissingPidFile < Error; end
34
+
35
+ # An exception raised when attempting to read an invalid pid file
36
+ class InvalidPidFile < Error; end
37
+
38
+ # An exception raised when a Pid file has a process id that isn't running
39
+ class StalePidFile < Error; end
40
+
41
+ # An exception raised when attempting to access a socket file that doesn't exist
42
+ class MissingSocketFile < Error; end
43
+
44
+ # An exception raised when attempting to access an invalid socket file
45
+ class InvalidSocketFile < Error; end
46
+ end
@@ -0,0 +1,139 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require 'open3'
4
+
5
+ require_relative "generator_helpers"
6
+
7
+ module NginxStage
8
+ # Base class for objects that add new sub-commands to NginxStage. {Generator}
9
+ # is basically a class with helper methods and the ability to invoke all
10
+ # callback methods in a sequence.
11
+ class Generator
12
+ extend GeneratorHelpers
13
+
14
+ # Adds a new hook method that is invoked in the order it is defined
15
+ # @param name [Symbol] unique key defining callback method
16
+ # @yield The body of the generator's callback
17
+ # @return [void]
18
+ def self.add_hook(name, &block)
19
+ self.hooks[name] = block
20
+ end
21
+
22
+ # Removes a hook method from the callback chain
23
+ # @param name [Symbol] unique key defining callback method
24
+ # @return [void]
25
+ def self.rem_hook(name)
26
+ self.hooks.delete(name)
27
+ end
28
+
29
+ # Returns a hash of callback methods in the order they will be invoked
30
+ # @return [Hash] the callback methods
31
+ def self.hooks
32
+ @hooks ||= from_superclass(:hooks, {})
33
+ end
34
+
35
+ # Adds a new option expected from CLI and treats it as an attribute
36
+ # @param name [Symbol] unique key defining option
37
+ # @yield The body of the option's callback which should return a hash
38
+ # @return [void]
39
+ def self.add_option(name, &block)
40
+ attr_reader name
41
+ self._options[name] = block
42
+ end
43
+
44
+ # Removes an option expected from the CLI and removes attribute method
45
+ # @param name [Symbol] unique key defining option
46
+ # @return [void]
47
+ def self.rem_option(name)
48
+ undef name
49
+ self._options.delete(name)
50
+ end
51
+
52
+ # Returns a hash of options with callbacks that return a hash of their
53
+ # attributes
54
+ # @return [Hash] a hash of options with the corresponding callback
55
+ def self._options
56
+ @options ||= from_superclass(:_options, {})
57
+ end
58
+
59
+ # Returns a hash of options that point to a hash of their attributes
60
+ # @return [Hash] a hash of options with the corresponding hash of attributes
61
+ def self.options
62
+ Hash[self._options.map { |k,v| [k, v.call] }]
63
+ end
64
+
65
+ # Returns the description of generator
66
+ # @return [String] description of generator
67
+ def self.desc(desc = nil)
68
+ @desc ||= desc
69
+ end
70
+
71
+ # Returns the footer description of generator
72
+ # @return [String] footer description of generator
73
+ def self.footer(footer = nil)
74
+ @footer ||= footer
75
+ end
76
+
77
+ # @param opts [Hash] various options for controlling the behavior of the generator
78
+ def initialize(opts = {})
79
+ self.class.options.each do |k,v|
80
+ value = opts.fetch(k) do
81
+ raise MissingOption, "missing option: #{k}" if v[:required]
82
+ v[:default]
83
+ end
84
+ value = v[:before_init].call(value) if v[:before_init]
85
+ instance_variable_set("@#{k}", value)
86
+ end
87
+ end
88
+
89
+ # Invokes all the callbacks in the order they are defined in the {hooks}
90
+ # hash
91
+ # @return [void]
92
+ def invoke
93
+ self.class.hooks.each {|k,v| self.instance_eval(&v)}
94
+ end
95
+
96
+ # Gets an ERB template at the relative source, executes it and makes a copy
97
+ # at the relative destination
98
+ # @param source [String] the relative path to the source file
99
+ # @param destination [String] the relative path to the destination file
100
+ # @return [void]
101
+ def template(source, destination)
102
+ data = File.read File.join(NginxStage.template_root, source)
103
+ create_file destination, render(data)
104
+ end
105
+
106
+ # Create a new file at the destination path with the given data
107
+ # @param destination [String] the relative path to the destination file
108
+ # @param data [String] the given data
109
+ # @return [void]
110
+ def create_file(destination, data = "")
111
+ empty_directory File.dirname(destination)
112
+ File.open(destination, "wb", 0644) { |f| f.write data }
113
+ end
114
+
115
+ # Create an empty directory if it doesn't already exist
116
+ # @param destination [String] the directory path
117
+ # @param mode [Fixnum] the mode to set the directory as
118
+ # @return [void]
119
+ def empty_directory(destination, mode: 0755)
120
+ FileUtils.mkdir_p destination, mode: mode
121
+ end
122
+
123
+ private
124
+ # Retrieves a value from superclass. If it reaches the baseclass,
125
+ # returns default
126
+ def self.from_superclass(method, default = nil)
127
+ if self == NginxStage::Generator || !superclass.respond_to?(method, true)
128
+ default
129
+ else
130
+ superclass.send(method).dup
131
+ end
132
+ end
133
+
134
+ # Use ERB to render templates
135
+ def render(data)
136
+ ERB.new(data, nil, '-').result(binding)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,68 @@
1
+ module NginxStage
2
+ # Module that adds common options to generators.
3
+ module GeneratorHelpers
4
+ # Add support for accepting USER as an option
5
+ # @param required [Boolean] whether user option is required
6
+ # @return [void]
7
+ def add_user_support(required: true)
8
+ # @!method user
9
+ # The user that the per-user NGINX will run as
10
+ # @return [User] the user of the nginx process
11
+ # @raise [MissingOption] if user isn't supplied
12
+ self.add_option :user do
13
+ {
14
+ opt_args: ["-u", "--user=USER", "# The USER of the per-user nginx process"],
15
+ required: required,
16
+ before_init: -> (user) do
17
+ raise InvalidUser, "invalid user name syntax: #{user}" unless user =~ NginxStage.user_regex
18
+ user ? User.new(user) : nil
19
+ end
20
+ }
21
+ end
22
+
23
+ if required
24
+ # Validate that the user isn't a special user (i.e., `root`)
25
+ self.add_hook :validate_user_not_special do
26
+ min_uid = NginxStage.min_uid
27
+ if user.uid < min_uid
28
+ raise InvalidUser, "user is special: #{user} (#{user.uid} < #{min_uid})"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Add support for accepting SKIP_NGINX as an option
35
+ # @return [void]
36
+ def add_skip_nginx_support
37
+ # @!method skip_nginx
38
+ # Whether we skip calling the NGINX process
39
+ # @return [Boolean] if true, skip calling the nginx binary
40
+ add_option :skip_nginx do
41
+ {
42
+ opt_args: ["-N", "--[no-]skip-nginx", "# Skip execution of the per-user nginx process", "# Default: false"],
43
+ default: false
44
+ }
45
+ end
46
+ end
47
+
48
+ # Add support for accepting SUB_URI as an option
49
+ # @return [void]
50
+ def add_sub_uri_support
51
+ # @!method sub_uri
52
+ # The sub-uri that distinguishes the per-user NGINX process
53
+ # @example An app is requested through '/pun/usr/user/appname/...'
54
+ # sub_uri #=> "/pun"
55
+ # @return [String] the sub-uri for nginx
56
+ add_option :sub_uri do
57
+ {
58
+ opt_args: ["-i", "--sub-uri=SUB_URI", "# The SUB_URI that requests the per-user nginx", "# Default: ''"],
59
+ default: '',
60
+ before_init: -> (sub_uri) do
61
+ raise InvalidSubUri, "invalid sub-uri syntax: #{sub_uri}" if sub_uri =~ /[^-\w\/]/
62
+ sub_uri
63
+ end
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end