Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

Commit

Permalink
feat(webserver): allow extensible webserver site customization
Browse files Browse the repository at this point in the history
Allow the user to deploy multiple applications on a single server,
each listening on a distinct port:

`app['webserver']['port']` and `app['webserver']['ssl_port']` (default
80 and 443, respectively), as well as the corresponding `global` values,
will allow the user to override the port that the application listens to
in Apache2 or nginx.

Allow user to override which template is used to generate Apache2 or
nginx per-app webserver configurations (aka "site configs"):

`app['webserver']['site_config_template']` and
`app['webserver']['site_config_template_cookbook']` (defaults are
`appserver.#{webserver}.conf.erb` and `opswork_ruby`, respectively), as
well as the corresponding `global` values, will allow the user to
specify which template (from which cookbook) should be used to render
the webserver site configuration.

Fixes #100
  • Loading branch information
nickmarden committed Aug 30, 2017
1 parent 29e1040 commit 4efd130
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .kitchen.yml
Expand Up @@ -141,5 +141,7 @@ suites:
adapter: 'unicorn'
webserver:
adapter: 'apache2'
port: 8080
ssl_port: 8443
'ruby-ng':
ruby_version: '2.3'
2 changes: 2 additions & 0 deletions attributes/default.rb
Expand Up @@ -69,6 +69,8 @@
## common

default['defaults']['webserver']['adapter'] = 'nginx'
default['defaults']['webserver']['port'] = 80
default['defaults']['webserver']['ssl_port'] = 443
default['defaults']['webserver']['ssl_for_legacy_browsers'] = false
default['defaults']['webserver']['extra_config'] = ''
default['defaults']['webserver']['extra_config_ssl'] = ''
Expand Down
47 changes: 41 additions & 6 deletions docs/source/attributes.rst
Expand Up @@ -367,9 +367,9 @@ webserver

- **Default:** ``nginx``
- **Supported values:** ``apache2``, ``nginx``, ``null``
- Webserver in front of the instance. It runs on port 80,
and receives all requests from Load Balancer/Internet.
``null`` means no webserver enabled.
- Webserver in front of the instance. It runs on port 80 by default
(see ``app['webserver']['port']``), and receives all requests from the
Load Balancer/Internet. ``null`` means no webserver enabled.

- ``app['webserver']['dhparams']``

Expand All @@ -392,19 +392,54 @@ webserver
Android < 2.2) wouldn’t work with this configuration very well. If your
application needs a support for those browsers, set this parameter to ``true``.

- ``app['webserver']['port']``

- **Default** ``80``
- The port on which the webserver should listen for HTTP requests.

- ``app['webserver']['ssl_port']``

- **Default** ``443``
- The port on which the webserver should listen for HTTPs requests, if
SSL requests are enabled. Note that SSL itself is controlled by the
``app['enable_ssl']`` setting in Opsworks.

- ``app['webserver']['site_config_template']``

- **Default** ``appserver.apache2.conf.erb`` or ``appserver.nginx.conf.erb``
- The name of the cookbook template that should be used to generate per-app
configuration stanzas (known as a "site" in apache and nginx configuration
parlance). Useful in situations where inserting an ``extra_config`` text
section doesn't provide enough flexibility to customize your per-app
webserver configuration stanza to your liking.
- Note that when you use a custom site configuration template, you can
also choose to define ``extra_config`` as any data structure (e.g., Hash
or even nested Hash) to be interpreted by your custom template. This
provides somewhat unlimited flexibility to configure the webserver app
configuration however you see fit.

- ``app['webserver']['site_config_template_cookbook']``

- **Default** ``opsworks_ruby``
- The name of the cookbook in which the site configuration template can be
found. If you override ``app['webserver']['site_config_template']`` to
use a site configuration template from your own cookbook, you'll need to
override this setting as well to ensure that the opsworks_ruby cookbook
looks for the specified template in your cookbook.

apache
^^^^^^

- ``app['webserver']['extra_config']``

- Raw Apache2 configuration, which will be inserted into ``<Virtualhost *:80>``
section of the application.
(or other port, if specified) section of the application.

- ``app['webserver']['extra_config_ssl']``

- Raw Apache2 configuration, which will be inserted into ``<Virtualhost *:443>``
section of the application. If set to ``true``, the ``extra_config``
will be copied.
(or other port, if specified for SSL) section of the application. If set to
``true``, the ``extra_config`` will be copied.

- |app['webserver']['limit_request_body']|_

Expand Down
2 changes: 1 addition & 1 deletion libraries/drivers_webserver_apache2.rb
Expand Up @@ -8,7 +8,7 @@ class Apache2 < Drivers::Webserver::Base
packages debian: 'apache2', rhel: %w[httpd24 mod24_ssl]
output filter: %i[
dhparams keepalive_timeout limit_request_body log_dir log_level proxy_timeout
ssl_for_legacy_browsers extra_config extra_config_ssl
ssl_for_legacy_browsers extra_config extra_config_ssl port ssl_port
]
notifies :deploy,
action: :restart, resource: { debian: 'service[apache2]', rhel: 'service[httpd]' }, timer: :delayed
Expand Down
18 changes: 17 additions & 1 deletion libraries/drivers_webserver_base.rb
Expand Up @@ -80,12 +80,16 @@ def add_appserver_config
opts = { application: app, deploy_dir: deploy_dir(app), out: out, conf_dir: conf_dir, adapter: adapter,
name: Drivers::Appserver::Factory.build(context, app).adapter }
return unless Drivers::Appserver::Base.adapters.include?(opts[:name])
generate_appserver_config(opts, site_config_template, site_config_template_cookbook)
end

def generate_appserver_config(opts, source_template, source_cookbook)
context.template "#{opts[:conf_dir]}/sites-available/#{app['shortname']}.conf" do
owner 'root'
group 'root'
mode '0644'
source "appserver.#{opts[:adapter]}.conf.erb"
source source_template.to_s
cookbook source_cookbook.to_s
variables opts
end
end
Expand All @@ -98,6 +102,18 @@ def enable_appserver_config
to "#{conf_path}/sites-available/#{application['shortname']}.conf"
end
end

def site_config_template
(node['deploy'][app['shortname']]['webserver'] || {})['site_config_template'] ||
node['defaults']['webserver']['site_config_template'] ||
"appserver.#{adapter}.conf.erb"
end

def site_config_template_cookbook
(node['deploy'][app['shortname']]['webserver'] || {})['site_config_template_cookbook'] ||
node['defaults']['webserver']['site_config_template_cookbook'] ||
context.cookbook_name
end
end
end
end
2 changes: 1 addition & 1 deletion libraries/drivers_webserver_nginx.rb
Expand Up @@ -8,7 +8,7 @@ class Nginx < Drivers::Webserver::Base
output filter: %i[
build_type client_body_timeout client_header_timeout client_max_body_size dhparams keepalive_timeout
log_dir log_level proxy_read_timeout proxy_send_timeout send_timeout ssl_for_legacy_browsers
extra_config extra_config_ssl enable_upgrade_method
extra_config extra_config_ssl enable_upgrade_method port ssl_port
]
notifies :deploy, action: :restart, resource: 'service[nginx]', timer: :delayed
notifies :undeploy, action: :restart, resource: 'service[nginx]', timer: :delayed
Expand Down
61 changes: 61 additions & 0 deletions spec/unit/recipes/configure_spec.rb
Expand Up @@ -128,6 +128,12 @@
end

it 'creates nginx unicorn proxy handler config' do
expect(chef_run)
.to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}.conf")
.with_content('listen 80;')
expect(chef_run)
.to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}.conf")
.with_content('listen 443;')
expect(chef_run)
.to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}.conf")
.with_content('error_log /var/log/nginx/dummy-project.example.com-ssl.error.log debug;')
Expand Down Expand Up @@ -230,6 +236,35 @@
.with_content("---\n:concurrency: 5\n:verbose: false\n:queues:\n- default")
end

it 'allows overriding of ports' do
chef_run = ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node|
deploy = node['deploy']
deploy[aws_opsworks_app['shortname']]['webserver']['port'] = 8080
deploy[aws_opsworks_app['shortname']]['webserver']['ssl_port'] = 8443
solo_node.set['deploy'] = deploy
solo_node.set['nginx'] = node['nginx']
end.converge(described_recipe)
expect(chef_run).to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}.conf").with_content(
'listen 8080;'
)
expect(chef_run).to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}.conf").with_content(
'listen 8443;'
)
end

it 'allows choosing a different template, from a different cookbook' do
chef_run = ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node|
deploy = node['deploy']
deploy[aws_opsworks_app['shortname']]['webserver']['site_config_template'] = 'appserver.test.conf.erb'
deploy[aws_opsworks_app['shortname']]['webserver']['site_config_template_cookbook'] = 'some_cookbook'
solo_node.set['deploy'] = deploy
solo_node.set['nginx'] = node['nginx']
end.converge(described_recipe)
expect(chef_run).to create_template("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}.conf")
.with_source('appserver.test.conf.erb')
.with_cookbook('some_cookbook')
end

context 'rhel' do
it 'creates sidekiq.monitrc conf' do
expect(chef_run_rhel).to create_template("/etc/monit.d/sidekiq_#{aws_opsworks_app['shortname']}.monitrc")
Expand Down Expand Up @@ -486,6 +521,9 @@
expect(chef_run)
.not_to render_file("/etc/apache2/sites-available/#{aws_opsworks_app['shortname']}.conf")
.with_content('extra_config_ssl {}')
expect(chef_run)
.not_to render_file("/etc/apache2/sites-available/#{aws_opsworks_app['shortname']}.conf")
.with_content(/^Listen/)
expect(chef_run).to create_link("/etc/apache2/sites-enabled/#{aws_opsworks_app['shortname']}.conf")
end

Expand Down Expand Up @@ -523,6 +561,29 @@
.with_content('--- DH PARAMS ---')
end

it 'allows overriding of ports' do
chefrun = ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node|
deploy = node['deploy']
deploy[aws_opsworks_app['shortname']]['webserver']['adapter'] = 'apache2'
deploy[aws_opsworks_app['shortname']]['webserver']['port'] = 8080
deploy[aws_opsworks_app['shortname']]['webserver']['ssl_port'] = 8443
solo_node.set['deploy'] = deploy
end.converge(described_recipe)

expect(chefrun).to render_file("/etc/apache2/sites-available/#{aws_opsworks_app['shortname']}.conf").with_content(
'<VirtualHost *:8080>'
)
expect(chefrun).to render_file("/etc/apache2/sites-available/#{aws_opsworks_app['shortname']}.conf").with_content(
'Listen 8080'
)
expect(chefrun).to render_file("/etc/apache2/sites-available/#{aws_opsworks_app['shortname']}.conf").with_content(
'<VirtualHost *:8443>'
)
expect(chefrun).to render_file("/etc/apache2/sites-available/#{aws_opsworks_app['shortname']}.conf").with_content(
'Listen 8443'
)
end

it 'cleans default sites' do
expect(chef_run).to run_execute('find /etc/apache2/sites-enabled -maxdepth 1 -mindepth 1 -exec rm -rf {} \;')
end
Expand Down
12 changes: 10 additions & 2 deletions templates/default/appserver.apache2.conf.erb
@@ -1,6 +1,10 @@
<% upstream = "#{@name}_#{@application[:domains].first}".gsub(/[^a-zA-Z0-9]+/, '_') %>
<VirtualHost *:80>
<% unless @out[:port] == 80 %>
Listen <%= @out[:port] %>
<% end -%>

<VirtualHost *:<%= @out[:port] %>>
ServerName <%= node['hostname'] %>
ServerAlias <%= @application[:domains].join(" ") %>

Expand Down Expand Up @@ -55,8 +59,12 @@
</VirtualHost>

<% if @application[:enable_ssl] %>
<% unless @out[:ssl_port] == 443 %>
Listen <%= @out[:ssl_port] %>
<% end -%>

SSLStaplingCache "shmcb:logs/stapling-cache(150000)"
<VirtualHost *:443>
<VirtualHost *:<%= @out[:ssl_port] %>>
ServerName <%= node['hostname'] %>
ServerAlias <%= @application[:domains].join(" ") %>

Expand Down
4 changes: 2 additions & 2 deletions templates/default/appserver.nginx.conf.erb
Expand Up @@ -3,7 +3,7 @@ upstream <%= @name %>_<%= @application[:domains].first %> {
}

server {
listen 80;
listen <%= @out[:port] %>;
server_name <%= @application[:domains].join(" ") %> <%= node['hostname'] %>;
access_log <%= @out[:log_dir] %>/<%= @application[:domains].first %>.access.log;
error_log <%= @out[:log_dir] %>/<%= @application[:domains].first %>.error.log <%= @out[:log_level] %>;
Expand Down Expand Up @@ -68,7 +68,7 @@ server {

<% if @application[:enable_ssl] %>
server {
listen 443;
listen <%= @out[:ssl_port] %>;
server_name <%= @application[:domains].join(" ") %> <%= node['hostname'] %>;
access_log <%= @out[:log_dir] %>/<%= @application[:domains].first %>-ssl.access.log;
error_log <%= @out[:log_dir] %>/<%= @application[:domains].first %>-ssl.error.log <%= @out[:log_level] %>;
Expand Down
Expand Up @@ -69,6 +69,10 @@
its(:content) { should include '<Proxy balancer://unicorn_other_project_example_com>' }
its(:content) { should include 'SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH' }
its(:content) { should include 'DocumentRoot /srv/www/other_project/current/public' }
its(:content) { should include 'Listen 8080' }
its(:content) { should include '<VirtualHost *:8080>' }
its(:content) { should include 'Listen 8443' }
its(:content) { should include '<VirtualHost *:8443>' }
end
end

Expand Down

0 comments on commit 4efd130

Please sign in to comment.