haproxy-tools 0.1.0 → 0.2.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,189 @@
1
+ require 'haproxy/treetop/nodes'
2
+
3
+ module HAProxy::Treetop
4
+
5
+ grammar Config
6
+ rule configuration
7
+ (comment_line / global_section / defaults_section / userlist_section / listen_section / frontend_section / backend_section)* <ConfigurationFile>
8
+ end
9
+
10
+ rule global_section
11
+ global_header config_block <GlobalSection>
12
+ end
13
+
14
+ rule defaults_section
15
+ defaults_header config_block <DefaultsSection>
16
+ end
17
+
18
+ rule userlist_section
19
+ userlist_header config_block <UserlistSection>
20
+ end
21
+
22
+ rule listen_section
23
+ listen_header config_block <ListenSection>
24
+ end
25
+
26
+ rule frontend_section
27
+ frontend_header config_block <FrontendSection>
28
+ end
29
+
30
+ rule backend_section
31
+ backend_header config_block <BackendSection>
32
+ end
33
+
34
+ rule global_header
35
+ whitespace "global" whitespace comment_text? line_break <GlobalHeader>
36
+ end
37
+
38
+ rule userlist_header
39
+ whitespace "userlist" whitespace proxy_name comment_text? line_break <UseristHeader>
40
+ end
41
+
42
+ rule defaults_header
43
+ whitespace "defaults" whitespace proxy_name? whitespace comment_text? line_break <DefaultsHeader>
44
+ end
45
+
46
+ rule listen_header
47
+ whitespace "listen" whitespace proxy_name whitespace service_address? value? comment_text? line_break <ListenHeader>
48
+ end
49
+
50
+ rule frontend_header
51
+ whitespace "frontend" whitespace proxy_name whitespace service_address? value? comment_text? line_break <FrontendHeader>
52
+ end
53
+
54
+ rule backend_header
55
+ whitespace "backend" whitespace proxy_name whitespace value? comment_text? line_break <BackendHeader>
56
+ end
57
+
58
+ rule config_block
59
+ (server_line / option_line / config_line / comment_line / blank_line)* <ConfigBlock>
60
+ end
61
+
62
+ rule server_line
63
+ whitespace "server" whitespace server_name whitespace service_address value? comment_text? line_break <ServerLine>
64
+ end
65
+
66
+ rule option_line
67
+ whitespace "option" whitespace keyword whitespace value? comment_text? line_break <OptionLine>
68
+ end
69
+
70
+ rule config_line
71
+ whitespace !("defaults" / "global" / "listen" / "frontend" / "backend") keyword whitespace value? comment_text? line_break <ConfigLine>
72
+ end
73
+
74
+ rule comment_line
75
+ whitespace comment_text line_break <CommentLine>
76
+ end
77
+
78
+ rule blank_line
79
+ whitespace line_break <BlankLine>
80
+ end
81
+
82
+ rule comment_text
83
+ '#' char* &line_break <CommentText>
84
+ end
85
+
86
+ rule line_break
87
+ [\n] <LineBreak>
88
+ end
89
+
90
+ rule keyword
91
+ [a-z\-\.]+ <Keyword>
92
+ end
93
+
94
+ rule server_name
95
+ [a-zA-Z0-9\-_\.:]+ <ServerName>
96
+ end
97
+
98
+ rule service_address
99
+ host [:]? port <ServiceAddress>
100
+ end
101
+
102
+ rule host
103
+ ipv4_host / dns_host / wildcard_host
104
+ end
105
+
106
+ rule port
107
+ [\d]* <Port>
108
+ end
109
+
110
+ rule ipv4_host
111
+ [\d] 1..3 '.' [\d] 1..3 '.' [\d] 1..3 '.' [\d] 1..3 <Host>
112
+ end
113
+
114
+ rule wildcard_host
115
+ "*" <Host>
116
+ end
117
+
118
+ rule dns_host
119
+ [a-zA-Z\-\.] 4..255 <Host>
120
+ end
121
+
122
+ rule proxy_name
123
+ [a-zA-Z0-9\-_\.:]+ <ProxyName>
124
+ end
125
+
126
+ rule value
127
+ [^#\n]+ <Value>
128
+ end
129
+
130
+ rule char
131
+ ![\n] . <Char>
132
+ end
133
+
134
+ rule whitespace
135
+ [ \t]* <Whitespace>
136
+ end
137
+
138
+ # Valid Global Keywords (1.4)
139
+ # * Process management and security
140
+ # - chroot <jail dir>
141
+ # - daemon
142
+ # - gid <number>
143
+ # - group <group name>
144
+ # - log <address> <facility> [max level [min level]]
145
+ # - log-send-hostname [<string>]
146
+ # ? log-tag <string>
147
+ # - nbproc <number>
148
+ # - pidfile <pidfile>
149
+ # - uid <number>
150
+ # - ulimit-n <number>
151
+ # - user <user name>
152
+ # - stats socket <path> [{uid | user} <uid>] [{gid | group} <gid>] [mode <mode>] [level <level>]
153
+ # - stats timeout <timeout, in milliseconds>
154
+ # - stats maxconn <connections>
155
+ # - node <name>
156
+ # - description <text>
157
+ #
158
+ # * Performance tuning
159
+ # - maxconn <number>
160
+ # - maxpipes <number>
161
+ # - noepoll
162
+ # - nokqueue
163
+ # - nopoll
164
+ # - nosepoll
165
+ # - nosplice
166
+ # - spread-checks <0..50, in percent>
167
+ # - tune.bufsize <number>
168
+ # - tune.chksize <number>
169
+ # - tune.maxaccept <number>
170
+ # - tune.maxpollevents <number>
171
+ # - tune.maxrewrite <number>
172
+ # - tune.rcvbuf.client <number>
173
+ # - tune.rcvbuf.server <number>
174
+ # - tune.sndbuf.client <number>
175
+ # - tune.sndbuf.server <number>
176
+ #
177
+ # * Debugging
178
+ # - debug
179
+ # - quiet
180
+
181
+ # Valid Userlist keywords
182
+ # - userlist <listname>
183
+ # - group <groupname> [users <user>,<user>,(...)]
184
+ # - user <username> [password|insecure-password <password>] [groups <group>,<group>,(...)]
185
+
186
+
187
+ end
188
+ end
189
+
@@ -0,0 +1,174 @@
1
+ module HAProxy
2
+ module Treetop
3
+ extend self
4
+
5
+ module StrippedTextContent
6
+ def content
7
+ self.text_value.strip
8
+ end
9
+ end
10
+
11
+ module ConfigBlockContainer
12
+ def option_lines
13
+ self.config_block.elements.select {|e| e.class == OptionLine}
14
+ end
15
+
16
+ def config_lines
17
+ self.config_block.elements.select {|e| e.class == ConfigLine}
18
+ end
19
+ end
20
+
21
+ module ServiceAddressContainer
22
+ def service_address
23
+ self.elements.find {|e| e.class == ServiceAddress }
24
+ end
25
+
26
+ def host
27
+ self.service_address.host.text_value.strip
28
+ end
29
+
30
+ def port
31
+ self.service_address.port.text_value.strip
32
+ end
33
+ end
34
+
35
+ module ServerContainer
36
+ def servers
37
+ self.config_block.elements.select {|e| e.class == ServerLine}
38
+ end
39
+ end
40
+
41
+ module OptionalValueElement
42
+ def value
43
+ self.elements.find {|e| e.class == Value}
44
+ end
45
+ end
46
+
47
+ class Whitespace < ::Treetop::Runtime::SyntaxNode
48
+ def content
49
+ self.text_value
50
+ end
51
+ end
52
+ class LineBreak < ::Treetop::Runtime::SyntaxNode; end
53
+ class Char < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
54
+ class Keyword < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
55
+ class ProxyName < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
56
+ class ServerName < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
57
+ class Host < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
58
+ class Port < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
59
+ class Value < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
60
+ class CommentText < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
61
+
62
+ class ServiceAddress < ::Treetop::Runtime::SyntaxNode
63
+ include StrippedTextContent
64
+ end
65
+
66
+ class CommentLine < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
67
+ class BlankLine < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
68
+ class ConfigLine < ::Treetop::Runtime::SyntaxNode
69
+ include StrippedTextContent
70
+ include OptionalValueElement
71
+ end
72
+
73
+ class OptionLine < ::Treetop::Runtime::SyntaxNode
74
+ include StrippedTextContent
75
+ include OptionalValueElement
76
+ end
77
+
78
+ class ServerLine < ::Treetop::Runtime::SyntaxNode
79
+ include StrippedTextContent
80
+ include ServiceAddressContainer
81
+ include OptionalValueElement
82
+
83
+ def name
84
+ self.server_name.content
85
+ end
86
+ end
87
+
88
+ class GlobalHeader < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
89
+
90
+ class DefaultsHeader < ::Treetop::Runtime::SyntaxNode
91
+ include StrippedTextContent
92
+ def proxy_name
93
+ self.elements.select {|e| e.class == ProxyName}.first
94
+ end
95
+ end
96
+
97
+ class UserlistHeader < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
98
+ class BackendHeader < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
99
+
100
+ class FrontendHeader < ::Treetop::Runtime::SyntaxNode
101
+ include ServiceAddressContainer
102
+ end
103
+
104
+ class ListenHeader < ::Treetop::Runtime::SyntaxNode
105
+ include ServiceAddressContainer
106
+ end
107
+
108
+ class ConfigBlock < ::Treetop::Runtime::SyntaxNode; end
109
+
110
+ class DefaultsSection < ::Treetop::Runtime::SyntaxNode
111
+ include ConfigBlockContainer
112
+ end
113
+
114
+ class GlobalSection < ::Treetop::Runtime::SyntaxNode
115
+ include ConfigBlockContainer
116
+ end
117
+
118
+ class UserlistSection < ::Treetop::Runtime::SyntaxNode
119
+ include ConfigBlockContainer
120
+ end
121
+
122
+ class FrontendSection < ::Treetop::Runtime::SyntaxNode
123
+ include ConfigBlockContainer
124
+ end
125
+
126
+ class ListenSection < ::Treetop::Runtime::SyntaxNode
127
+ include ConfigBlockContainer
128
+ include ServerContainer
129
+ end
130
+
131
+ class BackendSection < ::Treetop::Runtime::SyntaxNode
132
+ include ConfigBlockContainer
133
+ include ServerContainer
134
+ end
135
+
136
+ class ConfigurationFile < ::Treetop::Runtime::SyntaxNode
137
+ def global
138
+ self.elements.select {|e| e.class == GlobalSection}.first
139
+ end
140
+
141
+ def defaults
142
+ self.elements.select {|e| e.class == DefaultsSection}
143
+ end
144
+
145
+ def listeners
146
+ self.elements.select {|e| e.class == ListenSection}
147
+ end
148
+
149
+ def frontends
150
+ self.elements.select {|e| e.class == FrontendSection}
151
+ end
152
+
153
+ def backends
154
+ self.elements.select {|e| e.class == BackendSection}
155
+ end
156
+ end
157
+
158
+ def print_node(e, depth, options = nil)
159
+ options ||= {}
160
+ options = {:max_depth => 2}.merge(options)
161
+
162
+ puts if depth == 0
163
+ print "--" * depth
164
+ print " #{e.class.name.split('::').last}"
165
+ print " [#{e.text_value}]" if e.class == ::Treetop::Runtime::SyntaxNode
166
+ print " [#{e.content}]" if e.respond_to? :content
167
+ puts
168
+ e.elements.each do |child|
169
+ print_node(child, depth + 1, options)
170
+ end if depth < options[:max_depth] && e.elements && !e.respond_to?(:content)
171
+ end
172
+ end
173
+ end
174
+
data/lib/haproxy_tools.rb CHANGED
@@ -1,2 +1,12 @@
1
+ require 'polyglot'
2
+ require 'treetop'
3
+ require 'orderedhash'
4
+ require 'haproxy/treetop/config'
1
5
  require 'haproxy/config'
6
+ require 'haproxy/renderer'
2
7
  require 'haproxy/parser'
8
+
9
+ module HAProxy
10
+ VERSION = File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
11
+ end
12
+
@@ -1,3 +1,4 @@
1
+ # This is a top-level comment. It should be allowed.
1
2
  global
2
3
  maxconn 4096 # Total Max Connections. This is dependent on ulimit
3
4
  daemon
@@ -10,7 +11,7 @@ defaults
10
11
  contimeout 4000
11
12
  option httpclose # Disable Keepalive
12
13
 
13
- listen http_proxy 55.55.55.55:80
14
+ listen http_proxy 55.55.55.55:80
14
15
  balance roundrobin # Load Balancing algorithm
15
16
  option httpchk
16
17
  option forwardfor # This sets X-Forwarded-For
@@ -1,4 +1,103 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "HAProxy::Config" do
4
+ describe 'render multi-backend config' do
5
+ before(:each) do
6
+ @config = HAProxy::Config.parse_file('spec/fixtures/multi-pool.haproxy.cfg')
7
+ end
8
+
9
+ it 'can re-render a config file with a server removed' do
10
+ l = @config.backend('www_main')
11
+ l.servers.delete('prd_www_1')
12
+
13
+ new_config_text = @config.render
14
+
15
+ new_config = HAProxy::Parser.new.parse(new_config_text)
16
+ new_config.backend('www_main').servers['prd_www_1'].should be_nil
17
+ end
18
+
19
+ it 'can re-render a config file with a server attribute added' do
20
+ b = @config.backend('www_main')
21
+ b.servers['prd_www_1'].attributes['disabled'] = true
22
+ new_config_text = @config.render
23
+
24
+ new_config = HAProxy::Parser.new.parse(new_config_text)
25
+ s = new_config.backend('www_main').servers['prd_www_1']
26
+ s.should_not be_nil
27
+ s.attributes['disabled'].should be_true
28
+ end
29
+
30
+ it 'can re-render a config file with a server added' do
31
+ b = @config.backend('www_main')
32
+ b.add_server('prd_www_4', '99.99.99.99', :port => '8000')
33
+
34
+ new_config_text = @config.render
35
+
36
+ new_config = HAProxy::Parser.new.parse(new_config_text)
37
+ s = new_config.backend('www_main').servers['prd_www_4']
38
+ s.should_not be_nil
39
+ s.name.should == 'prd_www_4'
40
+ s.host.should == '99.99.99.99'
41
+ s.port.should == '8000'
42
+ s.attributes.to_a.should == []
43
+ end
44
+
45
+ it 'can re-render a config file with a server added based on template' do
46
+ b = @config.backend('www_main')
47
+ b.add_server('prd_www_4', '99.99.99.99', :template => b.servers['prd_www_1'])
48
+
49
+ new_config_text = @config.render
50
+
51
+ new_config = HAProxy::Parser.new.parse(new_config_text)
52
+ s = new_config.backend('www_main').servers['prd_www_4']
53
+ s.should_not be_nil
54
+ s.name.should == 'prd_www_4'
55
+ s.host.should == '99.99.99.99'
56
+ s.port.should == '8000'
57
+ s.attributes.to_a.should == [
58
+ ['cookie','i-prd_www_1'],
59
+ ['check',true],
60
+ ['inter','3000'],
61
+ ['rise','2'],
62
+ ['fall','3'],
63
+ ['maxconn','1000']
64
+ ]
65
+ end
66
+ end
67
+
68
+ describe 'render simple config' do
69
+ before(:each) do
70
+ @config = HAProxy::Config.parse_file('spec/fixtures/simple.haproxy.cfg')
71
+ end
72
+
73
+ it 'can re-render a config file with a server removed' do
74
+ l = @config.listener('http_proxy')
75
+ l.servers.delete('web1')
76
+
77
+ new_config_text = @config.render
78
+
79
+ new_config = HAProxy::Parser.new.parse(new_config_text)
80
+ new_config.listener('http_proxy').servers['web1'].should be_nil
81
+ end
82
+
83
+ it 'can re-render a config file with a server added' do
84
+ l = @config.listener('http_proxy')
85
+ l.add_server('web4', '99.99.99.99', :template => l.servers['web1'])
86
+
87
+ new_config_text = @config.render
88
+
89
+ new_config = HAProxy::Parser.new.parse(new_config_text)
90
+ s = new_config.listener('http_proxy').servers['web4']
91
+ s.should_not be_nil
92
+ s.name.should == 'web4'
93
+ s.host.should == '99.99.99.99'
94
+ s.port.should == '80'
95
+ s.attributes.to_a.should == [
96
+ ['weight','1'],
97
+ ['maxconn','512'],
98
+ ['check',true]
99
+ ]
100
+ end
101
+ end
4
102
  end
103
+