From 4efd1300ced4be90a7000a1646c01b8147758a1e Mon Sep 17 00:00:00 2001 From: Nick Marden Date: Fri, 25 Aug 2017 14:27:15 -0400 Subject: [PATCH] feat(webserver): allow extensible webserver site customization 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 --- .kitchen.yml | 2 + attributes/default.rb | 2 + docs/source/attributes.rst | 47 ++++++++++++-- libraries/drivers_webserver_apache2.rb | 2 +- libraries/drivers_webserver_base.rb | 18 +++++- libraries/drivers_webserver_nginx.rb | 2 +- spec/unit/recipes/configure_spec.rb | 61 +++++++++++++++++++ templates/default/appserver.apache2.conf.erb | 12 +++- templates/default/appserver.nginx.conf.erb | 4 +- .../serverspec/maximum_override_spec.rb | 4 ++ 10 files changed, 141 insertions(+), 13 deletions(-) diff --git a/.kitchen.yml b/.kitchen.yml index 1dd64b2b..ebf03251 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -141,5 +141,7 @@ suites: adapter: 'unicorn' webserver: adapter: 'apache2' + port: 8080 + ssl_port: 8443 'ruby-ng': ruby_version: '2.3' diff --git a/attributes/default.rb b/attributes/default.rb index a6b3e7fa..de8e03a0 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -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'] = '' diff --git a/docs/source/attributes.rst b/docs/source/attributes.rst index f3048f4e..031478f1 100644 --- a/docs/source/attributes.rst +++ b/docs/source/attributes.rst @@ -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']`` @@ -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 ```` - 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 ```` - 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']|_ diff --git a/libraries/drivers_webserver_apache2.rb b/libraries/drivers_webserver_apache2.rb index 1234bb5e..8775f86e 100644 --- a/libraries/drivers_webserver_apache2.rb +++ b/libraries/drivers_webserver_apache2.rb @@ -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 diff --git a/libraries/drivers_webserver_base.rb b/libraries/drivers_webserver_base.rb index c172cbf6..caa2a23e 100644 --- a/libraries/drivers_webserver_base.rb +++ b/libraries/drivers_webserver_base.rb @@ -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 @@ -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 diff --git a/libraries/drivers_webserver_nginx.rb b/libraries/drivers_webserver_nginx.rb index ea8988cc..1e1041f1 100644 --- a/libraries/drivers_webserver_nginx.rb +++ b/libraries/drivers_webserver_nginx.rb @@ -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 diff --git a/spec/unit/recipes/configure_spec.rb b/spec/unit/recipes/configure_spec.rb index bb2233b7..66014072 100644 --- a/spec/unit/recipes/configure_spec.rb +++ b/spec/unit/recipes/configure_spec.rb @@ -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;') @@ -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") @@ -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 @@ -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( + '' + ) + 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( + '' + ) + 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 diff --git a/templates/default/appserver.apache2.conf.erb b/templates/default/appserver.apache2.conf.erb index 3eaf39ab..7781b005 100644 --- a/templates/default/appserver.apache2.conf.erb +++ b/templates/default/appserver.apache2.conf.erb @@ -1,6 +1,10 @@ <% upstream = "#{@name}_#{@application[:domains].first}".gsub(/[^a-zA-Z0-9]+/, '_') %> - +<% unless @out[:port] == 80 %> +Listen <%= @out[:port] %> +<% end -%> + +> ServerName <%= node['hostname'] %> ServerAlias <%= @application[:domains].join(" ") %> @@ -55,8 +59,12 @@ <% if @application[:enable_ssl] %> +<% unless @out[:ssl_port] == 443 %> +Listen <%= @out[:ssl_port] %> +<% end -%> + SSLStaplingCache "shmcb:logs/stapling-cache(150000)" - +> ServerName <%= node['hostname'] %> ServerAlias <%= @application[:domains].join(" ") %> diff --git a/templates/default/appserver.nginx.conf.erb b/templates/default/appserver.nginx.conf.erb index 00b07b5b..259091f8 100644 --- a/templates/default/appserver.nginx.conf.erb +++ b/templates/default/appserver.nginx.conf.erb @@ -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] %>; @@ -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] %>; diff --git a/test/integration/maximum_override/serverspec/maximum_override_spec.rb b/test/integration/maximum_override/serverspec/maximum_override_spec.rb index 114c462d..59103e15 100644 --- a/test/integration/maximum_override/serverspec/maximum_override_spec.rb +++ b/test/integration/maximum_override/serverspec/maximum_override_spec.rb @@ -69,6 +69,10 @@ its(:content) { should include '' } 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 '' } + its(:content) { should include 'Listen 8443' } + its(:content) { should include '' } end end