redirus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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