redirus 0.1.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.
@@ -0,0 +1,72 @@
1
+ require 'yaml'
2
+
3
+ ROOT_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
+
5
+ module Redirus
6
+ class Config
7
+ def initialize(path=nil)
8
+ if File.exists?(path)
9
+ @config = YAML.load_file(path)
10
+ else
11
+ @config = {}
12
+ end
13
+ end
14
+
15
+ def queues
16
+ @config['queues'] || ['default']
17
+ end
18
+
19
+ def redis_url
20
+ @config['redis_url'] || 'redis://localhost:6379'
21
+ end
22
+
23
+ def namespace
24
+ @config['namespace'] || 'redirus'
25
+ end
26
+
27
+ def nginx_pid_file
28
+ nginx_prop :pid, 'nginx.pid'
29
+ end
30
+
31
+ def configs_path
32
+ nginx_prop :configs_path, 'sites-enabled'
33
+ end
34
+
35
+ def http_template
36
+ nginx_prop :http_template, 'listen *:80;'
37
+ end
38
+
39
+ def https_template
40
+ nginx_prop :https_template, %q[listen *:443 ssl;
41
+ ssl_certificate /usr/share/ssl/certs/localhost/host.cert;
42
+ ssl_certificate_key /usr/share/ssl/certs/localhost/host.key;
43
+ ]
44
+ end
45
+
46
+ def config_template
47
+ nginx_prop :config_template, %q[#{upstream}
48
+
49
+ server {
50
+ #{listen}
51
+
52
+ server_name #{name}.localhost;
53
+ server_tokens off;
54
+
55
+ location / {
56
+ proxy_pass http://#{upstream_name};
57
+ }
58
+ }]
59
+ end
60
+
61
+ def allowed_properties
62
+ nginx_prop :allowed_properties, []
63
+ end
64
+
65
+ private
66
+
67
+ def nginx_prop(type, default=nil)
68
+ value = @config['nginx'][type.to_s] if @config['nginx']
69
+ value || default
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,48 @@
1
+ require 'sidekiq'
2
+
3
+ module Redirus
4
+ class Proxy
5
+ include Sidekiq::Worker
6
+
7
+ def perform(*params)
8
+ begin
9
+ perform_action(*params)
10
+ restart_nginx
11
+ rescue Errno::EACCES => e
12
+ $stderr << "Error: Cannot write to config files - continuing\n"
13
+ $stderr << "#{e}\n"
14
+ rescue Errno::ENOENT => e
15
+ $stderr << "Error: Trying to remove non existing config files - continuing\n"
16
+ $stderr << "#{e}\n"
17
+ rescue Errno::ESRCH => e
18
+ $stderr << "Warning: Nginx is dead - continuing\n"
19
+ $stderr << "#{e}\n"
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def perform_action(*params)
26
+ #by default do nothing
27
+ end
28
+
29
+ def full_name(name, type)
30
+ "#{name}_#{type}"
31
+ end
32
+
33
+ def config_file_path(name, type)
34
+ File.join(config.configs_path, full_name(name, type))
35
+ end
36
+
37
+ def config
38
+ @config ||= Redirus.config
39
+ end
40
+
41
+ def restart_nginx
42
+ File.open(config.nginx_pid_file) do |file|
43
+ pid = file.read.to_i
44
+ Process.kill :SIGHUP, pid
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,79 @@
1
+ require 'optparse'
2
+ require 'singleton'
3
+ require 'sidekiq/cli'
4
+
5
+ module Redirus
6
+ class ServerCLI
7
+ include Singleton
8
+
9
+ attr_reader :options
10
+
11
+ def parse(args = ARGV)
12
+ init_options(args)
13
+ validate!
14
+ end
15
+
16
+ def run
17
+ sidekiq_cli = Sidekiq::CLI.instance
18
+ args = queues + [
19
+ '-c', '1',
20
+ '-r', runner_path,
21
+ options[:config_path]
22
+ ]
23
+
24
+ sidekiq_cli.parse(args)
25
+ sidekiq_cli.run
26
+ end
27
+
28
+ private
29
+
30
+ def queues
31
+ Redirus.config.queues.inject([]) do |arr, q|
32
+ arr << '-q'
33
+ arr << q
34
+ end
35
+ end
36
+
37
+ def runner_path
38
+ module_path = File.expand_path(File.join(File.dirname(__FILE__), '..'))
39
+ File.join(module_path, 'redirus.rb')
40
+ end
41
+
42
+ def init_options(args)
43
+ opts = parse_options(args)
44
+ opts[:config_path] ||= 'config.yml'
45
+
46
+ Redirus.config_path = opts[:config_path]
47
+
48
+ @options = opts
49
+ end
50
+
51
+ def parse_options(args)
52
+ opts = {}
53
+
54
+ OptionParser.new do |o|
55
+ o.banner = 'Usage: redirus [options]'
56
+
57
+ o.on('-c',
58
+ '--configuration PATH',
59
+ 'Yaml redirus configuration path') do |arg|
60
+ opts[:config_path] = arg
61
+ end
62
+
63
+ o.on_tail('-h', '--help', 'Show this message') do
64
+ puts opts
65
+ exit
66
+ end
67
+ end.parse!
68
+
69
+ opts
70
+ end
71
+
72
+ def validate!
73
+ unless File.exist?(options[:config_path])
74
+ puts "ERROR: Configuration file #{options[:config_path]} does not exist"
75
+ exit(1)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module Redirus
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,51 @@
1
+ module Redirus
2
+ module Worker
3
+ class AddProxy < Proxy
4
+
5
+ def perform_action(name, workers, type, props = nil)
6
+ params = config_propertie(name, workers, type, props)
7
+ File.open(config_file_path(name, type), 'w') do |file|
8
+ param_regexp = '#{\w*}'
9
+ file.write config.config_template
10
+ .gsub(/#{param_regexp}/) { |p| params[p[2..-2]] }
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def config_propertie(name, workers, type, props)
17
+ {
18
+ 'name' => name,
19
+ 'listen' => https?(type) ? config.https_template : config.http_template,
20
+ 'upstream' => upstream_conf(name, workers, type),
21
+ 'upstream_name' => full_name(name, type),
22
+ 'properties' => location_properties(props)
23
+ }
24
+ end
25
+
26
+ def https?(type)
27
+ type.to_s == 'https'
28
+ end
29
+
30
+ def upstream_conf(name, workers, type)
31
+ "upstream #{name}_#{type} {\n#{workers_conf(workers)}\}\n"
32
+ end
33
+
34
+ def workers_conf(workers)
35
+ workers.collect { |worker| " server #{worker};\n" }.join
36
+ end
37
+
38
+ def location_properties(props)
39
+ props.inject([]) do |tab, prop|
40
+ tab << "#{prop};\n" if allowed? prop
41
+ tab
42
+ end.join('') if props
43
+ end
44
+
45
+ def allowed?(prop)
46
+ config.allowed_properties
47
+ .any? { |prop_regexp| /#{prop_regexp}/.match(prop) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ module Redirus
2
+ module Worker
3
+ class RmProxy < Proxy
4
+
5
+ def perform_action(name, type)
6
+ File.delete(config_file_path(name, type))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'sidekiq'
2
+
3
+ module Redirus
4
+ module Worker
5
+ autoload :AddProxy, 'redirus/worker/add_proxy'
6
+ autoload :RmProxy, 'redirus/worker/rm_proxy'
7
+ end
8
+ end
9
+
10
+ Sidekiq.configure_server do |config|
11
+ config.redis = {
12
+ namespace: Redirus.config.namespace,
13
+ url: Redirus.config.redis_url
14
+ }
15
+ end
16
+
17
+ Sidekiq.configure_client do |c|
18
+ c.redis = {
19
+ namespace: Redirus.config.namespace,
20
+ url: Redirus.config.redis_url
21
+ }
22
+ end
data/lib/redirus.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'sidekiq'
2
+
3
+ module Redirus
4
+ autoload :Version, 'redirus/version'
5
+ autoload :Config, 'redirus/config'
6
+ autoload :Proxy, 'redirus/proxy'
7
+ autoload :CLI, 'redirus/cli'
8
+ autoload :ServerCLI, 'redirus/server_cli'
9
+
10
+ def self.config
11
+ @@config ||= Redirus::Config.new(config_path)
12
+ end
13
+
14
+ def self.config_path
15
+ @@config_path ||= ARGV[0] || 'config.yml'
16
+ end
17
+
18
+ def self.config_path=(path)
19
+ @@config_path = path
20
+ @@config = nil
21
+ end
22
+ end
23
+
24
+ require 'redirus/worker'
data/redirus.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'redirus/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'redirus'
8
+ spec.version = Redirus::VERSION
9
+ spec.authors = ['Marek Kasztelnik']
10
+ spec.email = ['mkasztelnik@gmail.com']
11
+ spec.description = %q{Redirus}
12
+ spec.summary = %q{Redirus is responsible for managing http/https redirections}
13
+ spec.homepage = 'https://github.com/dice-cyfronet/redirus'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'sidekiq'
22
+
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'guard-rspec'
26
+ spec.add_development_dependency 'shoulda-matchers'
27
+ spec.add_development_dependency 'rspec-sidekiq'
28
+ spec.add_development_dependency 'coveralls'
29
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Redirus::Config do
4
+
5
+ context 'loading default values' do
6
+ let(:config) { Redirus::Config.new 'nonexisting_config_file' }
7
+
8
+ it 'returns redis config' do
9
+ expect(config.queues).to eq ['default']
10
+ expect(config.redis_url).to eq 'redis://localhost:6379'
11
+ expect(config.namespace).to eq 'redirus'
12
+ end
13
+
14
+ it 'returns nginx files location' do
15
+ expect(config.nginx_pid_file).to eq 'nginx.pid'
16
+ expect(config.configs_path).to eq 'sites-enabled'
17
+ expect(config.http_template).to eq 'listen *:80;'
18
+ expect(config.https_template).to start_with 'listen *:443 ssl;'
19
+ expect(config.config_template).to start_with '#{upstream}'
20
+ expect(config.allowed_properties).to eq []
21
+ end
22
+ end
23
+
24
+ context 'loading config from file' do
25
+ let(:config) { Redirus::Config.new File.join(SPEC_DIR, 'resources', 'config.yml') }
26
+
27
+ it 'returns redis config' do
28
+ expect(config.queues).to eq ['first', 'second']
29
+ expect(config.redis_url).to eq 'configfile-redis://localhost:6379'
30
+ expect(config.namespace).to eq 'configfile-redirus'
31
+ end
32
+
33
+ it 'returns nginx files location' do
34
+ expect(config.nginx_pid_file).to eq 'configfile-nginx.pid'
35
+ expect(config.configs_path).to eq 'configfile-sites-enabled'
36
+ expect(config.http_template).to eq 'listen *:8000;'
37
+ expect(config.https_template).to start_with 'listen *:8443 ssl;'
38
+ expect(config.config_template).to start_with '## configfile'
39
+ expect(config.allowed_properties).to eq ['proxy_send_timeout \d', 'proxy_read_timeout \d']
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Redirus::Proxy do
4
+ let(:nginx_pid_file) { double('nginx pid file') }
5
+ let(:config) {
6
+ double('worker configuration',
7
+ nginx_pid_file: 'nginx_pid_file'
8
+ )
9
+ }
10
+
11
+ before do
12
+ Redirus.stub(:config).and_return(config)
13
+ allow(File).to receive(:open).with('nginx_pid_file').and_yield(nginx_pid_file)
14
+ allow(nginx_pid_file).to receive(:read).and_return('123')
15
+ end
16
+
17
+ it 'invokes perform action' do
18
+ allow(Process).to receive(:kill).with(:SIGHUP, 123)
19
+ expect(subject).to receive(:perform_action).with('param', 123, :http)
20
+ subject.perform('param', 123, :http)
21
+ end
22
+
23
+ it 'restarts nginx' do
24
+ expect(Process).to receive(:kill).with(:SIGHUP, 123)
25
+ subject.perform('param')
26
+ end
27
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Redirus::Worker::AddProxy do
4
+ let(:proxy_file) { double('proxy file') }
5
+ let(:nginx_pid_file) { double('nginx pid file') }
6
+ let(:config) {
7
+ double('worker configuration',
8
+ configs_path: 'configs_base_path',
9
+ http_template: 'http_section',
10
+ https_template: 'https_section',
11
+ base_server_name: 'localhost',
12
+ config_template: %q[#{upstream}
13
+ server {
14
+ #{listen}
15
+ server_name #{name}.my.server.pl;
16
+ location / {
17
+ proxy_pass http://#{upstream_name};
18
+ #{properties}
19
+ }
20
+ }
21
+ ],
22
+ allowed_properties: ['proxy_send_timeout \d', 'proxy_read_timeout \d'],
23
+ nginx_pid_file: 'nginx_pid_file'
24
+ )
25
+ }
26
+
27
+ before do
28
+ allow(Redirus).to receive(:config).and_return(config)
29
+ allow(File).to receive(:open).with('nginx_pid_file').and_yield(nginx_pid_file)
30
+ allow(nginx_pid_file).to receive(:read).and_return('123')
31
+ allow(Process).to receive(:kill)
32
+ end
33
+
34
+ context 'when http redirection is required' do
35
+ before do
36
+ allow(File).to receive(:open).with('configs_base_path/subdomain_http', 'w').and_yield(proxy_file)
37
+ allow(proxy_file).to receive(:write)
38
+ subject.perform('subdomain', ['10.100.10.112:80', '10.100.10.113:80'], :http)
39
+ end
40
+
41
+ it 'sets http listen section' do
42
+ expect(proxy_file).to have_received(:write).with(/.*http_section.*/)
43
+ end
44
+
45
+ it 'sets http upstream name in proxy pass section' do
46
+ expect(proxy_file).to have_received(:write).with(/.*proxy_pass http:\/\/subdomain_http;.*/)
47
+ end
48
+
49
+ it 'has http upstream section with 2 upstream servers' do
50
+ expect(proxy_file).to have_received(:write).with(/.*upstream subdomain_http {\n\s*server 10.100.10.112:80;\n\s*server 10.100.10.113:80;\n\s*}.*/)
51
+ end
52
+
53
+ it 'sets subdomain.my.server.pl server name' do
54
+ expect(proxy_file).to have_received(:write).with(/.*subdomain\.my\.server\.pl;.*/)
55
+ end
56
+
57
+ it 'restarts nginx' do
58
+ expect(Process).to have_received(:kill).with(:SIGHUP, 123)
59
+ end
60
+ end
61
+
62
+ context 'when https redirection is required' do
63
+ before do
64
+ allow(File).to receive(:open).with('configs_base_path/subdomain_https', 'w').and_yield(proxy_file)
65
+ allow(proxy_file).to receive(:write)
66
+ subject.perform('subdomain', ['10.100.10.112:80'], :https)
67
+ end
68
+
69
+ it 'sets https listen section' do
70
+ expect(proxy_file).to have_received(:write).with(/.*https_section.*/)
71
+ end
72
+
73
+ it 'sets https upstream name in proxy pass section' do
74
+ expect(proxy_file).to have_received(:write).with(/.*proxy_pass http:\/\/subdomain_https;.*/)
75
+ end
76
+
77
+ it 'has https upstream section with upstream server' do
78
+ expect(proxy_file).to have_received(:write).with(/.*upstream subdomain_https {\n\s*server 10.100.10.112:80;\n\s*}.*/)
79
+ end
80
+ end
81
+
82
+ context 'when redirection with properties is required' do
83
+ before do
84
+ allow(File).to receive(:open).with('configs_base_path/subdomain_http', 'w').and_yield(proxy_file)
85
+ allow(proxy_file).to receive(:write)
86
+ end
87
+
88
+ it 'writes static properties into location section' do
89
+ expect(proxy_file).to receive(:write).with(/location \/ {\s*.*\s*proxy_send_timeout 600;\s*proxy_read_timeout 600;\s*}/)
90
+
91
+ subject.perform('subdomain', ['10.100.10.112:80'], :http, ['proxy_send_timeout 600', 'proxy_read_timeout 600'])
92
+ end
93
+
94
+ it 'discard not allowed properties' do
95
+ expect(proxy_file).to_not receive(:write).with(/not allowed property/)
96
+
97
+ subject.perform('subdomain', ['10.100.10.112:80'], :http, ['not allowed property'])
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Redirus::Worker::RmProxy do
4
+ let(:proxy_file) { double('proxy file') }
5
+ let(:nginx_pid_file) { double('nginx pid file') }
6
+ let(:config) {
7
+ double('worker configuration',
8
+ configs_path: 'configs_base_path',
9
+ nginx_pid_file: 'nginx_pid_file'
10
+ )
11
+ }
12
+
13
+ before do
14
+ allow(Redirus).to receive(:config).and_return(config)
15
+ allow(File).to receive(:open).with('nginx_pid_file').and_yield(nginx_pid_file)
16
+ allow(nginx_pid_file).to receive(:read).and_return('123')
17
+ allow(Process).to receive(:kill).with(:SIGHUP, 123)
18
+ allow(File).to receive(:delete)
19
+ end
20
+
21
+
22
+ context 'when http redirection' do
23
+ before { subject.perform('subdomain', :http) }
24
+
25
+ it 'removes redirection configuration file' do
26
+ expect(File).to have_received(:delete).with('configs_base_path/subdomain_http')
27
+ end
28
+
29
+ it 'restarts nginx' do
30
+ expect(Process).to have_received(:kill).with(:SIGHUP, 123)
31
+ end
32
+ end
33
+
34
+ context 'when https redirection' do
35
+ before { subject.perform('subdomain', :https) }
36
+
37
+ it 'removes redirection configuration file' do
38
+ expect(File).to have_received(:delete).with('configs_base_path/subdomain_https')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ queues:
2
+ - first
3
+ - second
4
+ redis_url: configfile-redis://localhost:6379
5
+ namespace: configfile-redirus
6
+ nginx:
7
+ pid: configfile-nginx.pid
8
+ configs_path: configfile-sites-enabled
9
+ http_template: 'listen *:8000;'
10
+ https_template: |
11
+ listen *:8443 ssl;
12
+ ssl_certificate /usr/share/ssl/certs/localhost/host.cert;
13
+ ssl_certificate_key /usr/share/ssl/certs/localhost/host.key;
14
+ config_template: |
15
+ ## configfile
16
+ #{upstream}
17
+ server {
18
+ #{listen}
19
+ server_name #{name}.localhost;
20
+ server_tokens off;
21
+ location / {
22
+ proxy_pass http://#{upstream_name};
23
+ #{properties}
24
+ }
25
+ }
26
+ allowed_properties:
27
+ - proxy_send_timeout \d
28
+ - proxy_read_timeout \d
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'redirus'
5
+
6
+ SPEC_DIR = File.dirname(__FILE__)
7
+ Dir[SPEC_DIR + "/support/**/*.rb"].each {|f| require f}
8
+
9
+ if ENV['TRAVIS']
10
+ require 'coveralls'
11
+ Coveralls.wear!
12
+ end
13
+
14
+ RSpec.configure do |config|
15
+ # Disable the old-style object.should syntax.
16
+ config.expect_with :rspec do |c|
17
+ c.syntax = :expect
18
+ end
19
+
20
+ config.alias_example_to :expect_it
21
+ end
22
+
23
+ RSpec::Core::MemoizedHelpers.module_eval do
24
+ alias to should
25
+ alias to_not should_not
26
+ end
@@ -0,0 +1,44 @@
1
+ RSpec::Matchers.define :conf_be_empty do
2
+ match do |actual|
3
+ actual[:proxy] == ''
4
+ actual[:upstream] == ''
5
+ end
6
+ end
7
+
8
+ RSpec::Matchers.define :have_config do |path, proxy_pass|
9
+ match do |actual|
10
+ actual[:proxy] =~ /location "#{excape(path)}" {\n proxy_pass "http:\/\/#{proxy_pass}\/";/
11
+ end
12
+
13
+ def excape(path)
14
+ path.gsub('/', '\/')
15
+ end
16
+ end
17
+
18
+ RSpec::Matchers.define :have_upstream_config do |proxy_pass, workers|
19
+ match do |actual|
20
+ expected = <<-ENTRY
21
+ upstream "#{proxy_pass}" {
22
+ #{workers_config(workers)}
23
+ }
24
+ ENTRY
25
+ actual[:upstream].gsub(/[\s\n]/, "").include? expected.gsub(/[\s\n]/, "")
26
+ end
27
+
28
+ def workers_config(workers)
29
+ workers.collect do |worker|
30
+ "server #{worker};\n"
31
+ end.join
32
+ end
33
+ end
34
+
35
+ RSpec::Matchers.define :have_property do |path, property|
36
+ match do |actual|
37
+
38
+ actual[:proxy] =~ /location \"#{excape(path)}\" {(\n.+)*#{excape(property)};/
39
+ end
40
+
41
+ def excape(path)
42
+ path.gsub('/', '\/')
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ start on starting redirus
2
+ stop on stopping redirus
3
+
4
+ setuid CHANGEME_USER_NAME
5
+
6
+ env DAEMON=CHANGEME_NGINX_PATH/sbin/nginx
7
+ env PID=CHANGEME_NGINX_PATH/nginx.pid
8
+
9
+ expect fork
10
+ respawn
11
+ respawn limit 10 5
12
+ #oom never
13
+
14
+ pre-start script
15
+ $DAEMON -t
16
+ if [ $? -ne 0 ]
17
+ then exit $?
18
+ fi
19
+ end script
20
+
21
+ exec $DAEMON