diff --git a/manifests/init.pp b/manifests/init.pp index fb3c64e5..b8421978 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -76,9 +76,6 @@ # (optional) Add the option to set the mount point from the UI. # Defaults to 'True'. # -# [*listen_ssl*] -# (optional) Defaults to false. -# # [*local_settings_template*] # (optional) Location of template to use for local_settings.py generation. # Defaults to 'horizon/local_settings.py.erb'. @@ -90,7 +87,25 @@ # [*compress_offline*] # (optional) Boolean to enable offline compress of assets. # Defaults to True - +# +# [*configure_apache*] +# (optional) Configure Apache for Horizon. (Defaults to true) +# +# [*bind_address*] +# (optional) Bind address in Apache for Horizon. (Defaults to '0.0.0.0') +# +# [*listen_ssl*] +# (optional) Enable SSL support in Apache. (Defaults to false) +# +# [*horizon_cert*] +# (required with listen_ssl) Certificate to use for SSL support. +# +# [*horizon_key*] +# (required with listen_ssl) Private key to use for SSL support. +# +# [*horizon_ca*] +# (required with listen_ssl) CA certificate to use for SSL support. +# # === Deprecation notes # # If any value is provided for keystone_scheme, keystone_host or keystone_port parameters, @@ -107,7 +122,6 @@ class horizon( $secret_key, $fqdn = $::fqdn, $package_ensure = 'present', - $bind_address = '0.0.0.0', $cache_server_ip = '127.0.0.1', $cache_server_port = '11211', $swift = false, @@ -120,12 +134,14 @@ class horizon( $api_result_limit = 1000, $log_level = 'DEBUG', $can_set_mount_point = 'True', + $help_url = 'http://docs.openstack.org', + $local_settings_template = 'horizon/local_settings.py.erb', + $configure_apache = true, + $bind_address = '0.0.0.0', $listen_ssl = false, $horizon_cert = undef, $horizon_key = undef, $horizon_ca = undef, - $help_url = 'http://docs.openstack.org', - $local_settings_template = 'horizon/local_settings.py.erb', $compress_offline = 'True', # DEPRECATED PARAMETERS $keystone_host = undef, @@ -133,9 +149,7 @@ class horizon( $keystone_scheme = undef, ) { - include horizon::params - include apache - include apache::mod::wsgi + include ::horizon::params if $swift { warning('swift parameter is deprecated and has no effect.') @@ -153,103 +167,26 @@ class horizon( warning('The keystone_port parameter is deprecated, use keystone_url instead.') } - file { $::horizon::params::httpd_config_file: } - Service <| title == 'memcached' |> -> Class['horizon'] package { 'horizon': ensure => $package_ensure, name => $::horizon::params::package_name, - require => Package[$::horizon::params::http_service], } file { $::horizon::params::config_file: content => template($local_settings_template), mode => '0644', - notify => Service[$::horizon::params::http_service], require => Package['horizon'], } - file { $::horizon::params::logdir: - ensure => directory, - mode => '0751', - owner => $::horizon::params::apache_user, - group => $::horizon::params::apache_group, - before => Service[$::horizon::params::http_service], - require => Package['horizon'] - } - - file_line { 'horizon_redirect_rule': - path => $::horizon::params::httpd_config_file, - line => "RedirectMatch permanent ^/$ ${::horizon::params::root_url}/", - require => Package['horizon'], - notify => Service[$::horizon::params::http_service] - } - - file_line { 'httpd_listen_on_bind_address_80': - path => $::horizon::params::httpd_listen_config_file, - match => '^Listen (.*):?80$', - line => "Listen ${bind_address}:80", - require => Package['horizon'], - notify => Service[$::horizon::params::http_service], - } - - if $listen_ssl { - include apache::mod::ssl - - if $horizon_ca == undef or $horizon_cert == undef or $horizon_key == undef { - fail('The horizon CA, cert and key are all required.') + if $configure_apache { + class { 'horizon::wsgi::apache': + bind_address => $bind_address, + listen_ssl => $listen_ssl, + horizon_cert => $horizon_cert, + horizon_key => $horizon_key, + horizon_ca => $horizon_ca, } - - file_line { 'httpd_listen_on_bind_address_443': - path => $::horizon::params::httpd_listen_config_file, - match => '^Listen (.*):?443$', - line => "Listen ${bind_address}:443", - require => Package['horizon'], - notify => Service[$::horizon::params::http_service], - } - - # Enable SSL Engine - file_line{'httpd_sslengine_on': - path => $::horizon::params::httpd_listen_config_file, - match => '^SSLEngine ', - line => 'SSLEngine on', - notify => Service[$::horizon::params::http_service], - require => Class['apache::mod::ssl'], - } - - # set the name of the ssl cert and key file - file_line{'httpd_sslcert_path': - path => $::horizon::params::httpd_listen_config_file, - match => '^SSLCertificateFile ', - line => "SSLCertificateFile ${horizon_cert}", - notify => Service[$::horizon::params::http_service], - require => Class['apache::mod::ssl'], - } - - file_line{'httpd_sslkey_path': - path => $::horizon::params::httpd_listen_config_file, - match => '^SSLCertificateKeyFile ', - line => "SSLCertificateKeyFile ${horizon_key}", - notify => Service[$::horizon::params::http_service], - require => Class['apache::mod::ssl'], - } - - file_line{'httpd_sslca_path': - path => $::horizon::params::httpd_listen_config_file, - match => '^SSLCACertificateFile ', - line => "SSLCACertificateFile ${horizon_ca}", - notify => Service[$::horizon::params::http_service], - require => Class['apache::mod::ssl'], - } - } - - $django_wsgi = '/usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi' - - file_line { 'horizon root': - path => $::horizon::params::httpd_config_file, - line => "WSGIScriptAlias ${::horizon::params::root_url} ${django_wsgi}", - match => 'WSGIScriptAlias ', - require => Package['horizon'], } } diff --git a/manifests/wsgi/apache.pp b/manifests/wsgi/apache.pp new file mode 100644 index 00000000..56df4d58 --- /dev/null +++ b/manifests/wsgi/apache.pp @@ -0,0 +1,129 @@ +# == Class: horizon::wsgi::apache +# +# Configures Apache WSGI for Horizon. +# +# === Parameters +# +# [*bind_address*] +# (optional) Bind address in Apache for Horizon. (Defaults to '0.0.0.0') +# +# [*listen_ssl*] +# (optional) Enable SSL support in Apache. (Defaults to false) +# +# [*horizon_cert*] +# (required with listen_ssl) Certificate to use for SSL support. +# +# [*horizon_key*] +# (required with listen_ssl) Private key to use for SSL support. +# +# [*horizon_ca*] +# (required with listen_ssl) CA certificate to use for SSL support. +# +class horizon::wsgi::apache ( + $bind_address = '0.0.0.0', + $listen_ssl = false, + $horizon_cert = undef, + $horizon_key = undef, + $horizon_ca = undef, +) { + + include ::horizon::params + include ::apache + include ::apache::mod::wsgi + + file { $::horizon::params::httpd_config_file: } + + Package['horizon'] -> Package[$::horizon::params::http_service] + File[$::horizon::params::config_file] ~> Service[$::horizon::params::http_service] + + file { $::horizon::params::logdir: + ensure => directory, + owner => $::horizon::params::apache_user, + group => $::horizon::params::apache_group, + before => Service[$::horizon::params::http_service], + mode => '0751', + require => Package['horizon'] + } + + file_line { 'horizon_redirect_rule': + path => $::horizon::params::httpd_config_file, + line => "RedirectMatch permanent ^/$ ${::horizon::params::root_url}/", + require => Package['horizon'], + notify => Service[$::horizon::params::http_service] + } + + file_line { 'httpd_listen_on_bind_address_80': + path => $::horizon::params::httpd_listen_config_file, + match => '^Listen (.*):?80$', + line => "Listen ${bind_address}:80", + require => Package['horizon'], + notify => Service[$::horizon::params::http_service], + } + + if $listen_ssl { + include apache::mod::ssl + + if $horizon_ca == undef { + fail('The horizon_ca parameter is required when listen_ssl is true') + } + + if $horizon_cert == undef { + fail('The horizon_cert parameter is required when listen_ssl is true') + } + + if $horizon_key == undef { + fail('The horizon_key parameter is required when listen_ssl is true') + } + + file_line { 'httpd_listen_on_bind_address_443': + path => $::horizon::params::httpd_listen_config_file, + match => '^Listen (.*):?443$', + line => "Listen ${bind_address}:443", + require => Package['horizon'], + notify => Service[$::horizon::params::http_service], + } + + # Enable SSL Engine + file_line{'httpd_sslengine_on': + path => $::horizon::params::httpd_listen_config_file, + match => '^SSLEngine ', + line => 'SSLEngine on', + notify => Service[$::horizon::params::http_service], + require => Class['apache::mod::ssl'], + } + + # set the name of the ssl cert and key file + file_line{'httpd_sslcert_path': + path => $::horizon::params::httpd_listen_config_file, + match => '^SSLCertificateFile ', + line => "SSLCertificateFile ${horizon_cert}", + notify => Service[$::horizon::params::http_service], + require => Class['apache::mod::ssl'], + } + + file_line{'httpd_sslkey_path': + path => $::horizon::params::httpd_listen_config_file, + match => '^SSLCertificateKeyFile ', + line => "SSLCertificateKeyFile ${horizon_key}", + notify => Service[$::horizon::params::http_service], + require => Class['apache::mod::ssl'], + } + + file_line{'httpd_sslca_path': + path => $::horizon::params::httpd_listen_config_file, + match => '^SSLCACertificateFile ', + line => "SSLCACertificateFile ${horizon_ca}", + notify => Service[$::horizon::params::http_service], + require => Class['apache::mod::ssl'], + } + } + + $django_wsgi = '/usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi' + + file_line { 'horizon root': + path => $::horizon::params::httpd_config_file, + line => "WSGIScriptAlias ${::horizon::params::root_url} ${django_wsgi}", + match => 'WSGIScriptAlias ', + require => Package['horizon'], + } +} diff --git a/spec/classes/horizon_init_spec.rb b/spec/classes/horizon_init_spec.rb index c3e0e055..6b514ff5 100644 --- a/spec/classes/horizon_init_spec.rb +++ b/spec/classes/horizon_init_spec.rb @@ -21,18 +21,14 @@ describe 'horizon' do shared_examples 'horizon' do - it { should contain_service('httpd').with_name(platforms_params[:http_service]) } - it { should contain_file(platforms_params[:httpd_config_file]) } - - it { - should contain_file_line('horizon_redirect_rule').with( - :line => "RedirectMatch permanent ^/$ #{platforms_params[:root_url]}/") - } - context 'with default parameters' do + it { should contain_package('horizon').with_ensure('present') } - it 'installs horizon package' do - should contain_package('horizon').with_ensure('present') + it 'configures apache' do + should contain_class('horizon::wsgi::apache').with({ + :bind_address => '0.0.0.0', + :listen_ssl => false, + }) end it 'generates local_settings.py' do @@ -109,13 +105,25 @@ describe 'horizon' do }) end - it { should contain_file_line('httpd_sslcert_path').with( - :line => "SSLCertificateFile /etc/pki/tls/certs/httpd.crt" - )} + it 'configures apache' do + should contain_class('horizon::wsgi::apache').with({ + :bind_address => '0.0.0.0', + :listen_ssl => true, + :horizon_cert => '/etc/pki/tls/certs/httpd.crt', + :horizon_key => '/etc/pki/tls/private/httpd.key', + :horizon_ca => '/etc/pki/tls/certs/ca.crt', + }) + end + end - it { should contain_file_line('httpd_sslkey_path').with( - :line => "SSLCertificateKeyFile /etc/pki/tls/private/httpd.key" - )} + context 'without apache' do + before do + params.merge!({ :configure_apache => false }) + end + + it 'does not configure apache' do + should_not contain_class('horizon::wsgi::apache') + end end context 'with overriding local_settings_template' do @@ -161,8 +169,6 @@ describe 'horizon' do let :platforms_params do { :config_file => '/etc/openstack-dashboard/local_settings', - :http_service => 'httpd', - :httpd_config_file => '/etc/httpd/conf.d/openstack-dashboard.conf', :package_name => 'openstack-dashboard', :root_url => '/dashboard' } end @@ -180,8 +186,6 @@ describe 'horizon' do let :platforms_params do { :config_file => '/etc/openstack-dashboard/local_settings.py', - :http_service => 'apache2', - :httpd_config_file => '/etc/apache2/conf.d/openstack-dashboard.conf', :package_name => 'openstack-dashboard-apache', :root_url => '/horizon' } end diff --git a/spec/classes/horizon_wsgi_apache_spec.rb b/spec/classes/horizon_wsgi_apache_spec.rb new file mode 100644 index 00000000..e7ea8f04 --- /dev/null +++ b/spec/classes/horizon_wsgi_apache_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe 'horizon::wsgi::apache' do + + let :params do + {} + end + + let :pre_condition do + "include apache\n" + + "class { 'horizon': secret_key => 's3cr3t', configure_apache => false }" + end + + let :fixtures_path do + File.expand_path(File.join(__FILE__, '..', '..', 'fixtures')) + end + + let :facts do + { :concat_basedir => '/var/lib/puppet/concat' } + end + + shared_examples 'apache for horizon' do + + context 'with default parameters' do + it 'configures apache' do + should contain_class('horizon::params') + should contain_class('apache') + should contain_class('apache::mod::wsgi') + should contain_service('httpd').with_name(platforms_params[:http_service]) + should contain_file(platforms_params[:httpd_config_file]) + should contain_file_line('horizon_redirect_rule').with( + :line => "RedirectMatch permanent ^/$ #{platforms_params[:root_url]}/") + end + end + + context 'with ssl enabled' do + before do + params.merge!({ + :listen_ssl => true, + :horizon_cert => '/etc/pki/tls/certs/httpd.crt', + :horizon_key => '/etc/pki/tls/private/httpd.key', + :horizon_ca => '/etc/pki/tls/certs/ca.crt', + }) + end + + context 'with required parameters' do + it 'configures apache for SSL' do + should contain_class('apache::mod::ssl') + should contain_file_line('httpd_sslcert_path').with( + :line => "SSLCertificateFile /etc/pki/tls/certs/httpd.crt") + should contain_file_line('httpd_sslkey_path').with( + :line => "SSLCertificateKeyFile /etc/pki/tls/private/httpd.key") + should contain_file_line('httpd_sslca_path').with( + :line => "SSLCACertificateFile /etc/pki/tls/certs/ca.crt") + end + end + + context 'without required parameters' do + + context 'without horizon_ca parameter' do + before { params.delete(:horizon_ca) } + it_raises 'a Puppet::Error', /The horizon_ca parameter is required when listen_ssl is true/ + end + + context 'without horizon_cert parameter' do + before { params.delete(:horizon_cert) } + it_raises 'a Puppet::Error', /The horizon_cert parameter is required when listen_ssl is true/ + end + + context 'without horizon_key parameter' do + before { params.delete(:horizon_key) } + it_raises 'a Puppet::Error', /The horizon_key parameter is required when listen_ssl is true/ + end + end + end + end + + context 'on RedHat platforms' do + before do + facts.merge!({ + :osfamily => 'RedHat', + :operatingsystemrelease => '6.0' + }) + end + + let :platforms_params do + { :http_service => 'httpd', + :httpd_config_file => '/etc/httpd/conf.d/openstack-dashboard.conf', + :root_url => '/dashboard' } + end + + it_behaves_like 'apache for horizon' + end + + context 'on Debian platforms' do + before do + facts.merge!({ + :osfamily => 'Debian', + :operatingsystemrelease => '6.0' + }) + end + + let :platforms_params do + { :http_service => 'apache2', + :httpd_config_file => '/etc/apache2/conf.d/openstack-dashboard.conf', + :root_url => '/horizon' } + end + + it_behaves_like 'apache for horizon' + end +end diff --git a/spec/shared_examples.rb b/spec/shared_examples.rb new file mode 100644 index 00000000..51e11c0b --- /dev/null +++ b/spec/shared_examples.rb @@ -0,0 +1,5 @@ +shared_examples_for "a Puppet::Error" do |description| + it "with message matching #{description.inspect}" do + expect { subject }.to raise_error(Puppet::Error, description) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2c6f5664..6d13239e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1 +1,6 @@ require 'puppetlabs_spec_helper/module_spec_helper' +require 'shared_examples' + +RSpec.configure do |c| + c.alias_it_should_behave_like_to :it_raises, 'raises' +end