diff --git a/lib/haproxy/config.rb b/lib/haproxy/config.rb index 80cac42..5a92989 100644 --- a/lib/haproxy/config.rb +++ b/lib/haproxy/config.rb @@ -1,5 +1,6 @@ module HAProxy Default = Struct.new(:name, :options, :config) + Userlist = Struct.new(:name, :options, :config) Backend = Struct.new(:name, :options, :config, :servers) Listener = Struct.new(:name, :host, :port, :options, :config, :servers) Frontend = Struct.new(:name, :host, :port, :options, :config) @@ -34,7 +35,7 @@ class Backend # Represents an haproxy configuration file. class Config - attr_accessor :original_parse_tree, :listeners, :backends, :frontends, :global, :defaults + attr_accessor :original_parse_tree, :listeners, :backends, :frontends, :global, :defaults, :userlists def initialize(parse_tree) self.original_parse_tree = parse_tree @@ -42,6 +43,7 @@ def initialize(parse_tree) self.listeners = [] self.frontends = [] self.defaults = [] + self.userlists = [] self.global = {} end @@ -53,6 +55,10 @@ def backend(name) self.backends.find { |b| b.name == name } end + def userlist(name) + self.userlists.find { |b| b.name == name } + end + def frontend(name) self.frontends.find { |f| f.name == name } end diff --git a/lib/haproxy/parser.rb b/lib/haproxy/parser.rb index ec0dc6a..6dfbbbf 100644 --- a/lib/haproxy/parser.rb +++ b/lib/haproxy/parser.rb @@ -51,6 +51,7 @@ def build_config(result) config.backends += collect_backends(result) config.listeners += collect_listeners(result) config.defaults += collect_defaults(result) + config.userlists += collect_userlists(result) end end @@ -64,6 +65,14 @@ def build_frontend(fs) end end + def build_userlist(fs) + Userlist.new.tap do |f| + f.name = try_send(fs.userlist_header, :proxy_name, :content) + f.options = options_hash_from_config_section(fs) + f.config = config_hash_from_config_section(fs) + end + end + def build_backend(bs) Backend.new.tap do |b| b.name = try_send(bs.backend_header, :proxy_name, :content) @@ -100,6 +109,10 @@ def collect_backends(result) result.backends.map { |bs| build_backend(bs) } end + def collect_userlists(result) + result.userlists.map { |bs| build_userlist(bs) } + end + def collect_listeners(result) result.listeners.map { |ls| build_listener(ls) } end @@ -180,4 +193,4 @@ def config_hash_from_config_section(cs) end end -end \ No newline at end of file +end diff --git a/lib/haproxy/renderer.rb b/lib/haproxy/renderer.rb index 2ecb168..5e69248 100644 --- a/lib/haproxy/renderer.rb +++ b/lib/haproxy/renderer.rb @@ -1,3 +1,7 @@ +CONFIG_NODES = [HAProxy::Default, HAProxy::Backend, HAProxy::Listener, HAProxy::Frontend, HAProxy::Userlist] +OPTION_NODES = [HAProxy::Default, HAProxy::Backend, HAProxy::Listener, HAProxy::Frontend, HAProxy::Userlist] +SERVER_NODES = [HAProxy::Listener, HAProxy::Backend] + module HAProxy # Responsible for rendering an HAProxy::Config instance to a string. class Renderer @@ -9,8 +13,10 @@ def initialize(config, source_tree) self.source_tree = source_tree @server_list = {} @config_list = {} + @option_list = {} @context = self.config @prev_context = self.config + @linebreak_active = false @config_text = '' end @@ -29,28 +35,48 @@ def render_node(node) if e.class == HAProxy::Treetop::ServerLine # Keep track of the servers that we've seen, so that we can detect and render new ones. @server_list[e.name] = e + # Don't render the server element if it's been deleted from the config. next if @context.servers[e.name].nil? - end - if e.class == HAProxy::Treetop::ConfigLine and @context.class == HAProxy::Default + # Use a custom rendering method for servers, since we allow them to be + # added/removed/changed. + render_server_element(e) + elsif e.class == HAProxy::Treetop::ConfigLine and CONFIG_NODES.include?(@context.class) # Keep track of the configs in this config block we've seen, so that we can detect and render new ones. @config_list[e.key] = e + # Don't render the config *if* it's been removed from the config block. - next if @context.config[e.key].nil? - end + next unless @context.config.has_key?(e.key) - if e.class == HAProxy::Treetop::ServerLine - # Use a custom rendering method for servers, since we allow them to be - # added/removed/changed. - render_server_element(e) - elsif e.class== HAProxy::Treetop::ConfigLine and @context.class == HAProxy::Default # Use a custom rendering method for configs, since we allow them to be # added/removed/changed. render_config_line_element(e) + elsif e.class == HAProxy::Treetop::OptionLine and OPTION_NODES.include?(@context.class) + # Keep track of the options in this option block we've seen, so that we can detect and render new ones. + @option_list[e.key] = e + + # Don't render the option *if* it's been removed from the option block. + next unless @context.options.has_key?(e.key) + + # Use a custom rendering method for options, since we allow them to be + # added/removed/changed. + render_option_line_element(e) elsif e.elements && e.elements.size > 0 render_node(e) else + if e.class == HAProxy::Treetop::LineBreak + @linebreak_active = true + elsif @linebreak_active + if e.text_value =~ /\S/ + if e.text_value.size == 1 + @config_text << "\t" + end + @linebreak_active = false + else + next + end + end @config_text << e.text_value end end @@ -74,6 +100,17 @@ def render_config_line(key, value) @config_text << "\t#{key} #{value}\n" end + def render_option_line_element(e) + option_key = e.key.gsub(/\s+/, ' ') + option_value = @context.options[e.key] + option_value = option_value.gsub(/\s+/, ' ') if not option_value.nil? + render_option_line(option_key, option_value) + end + + def render_option_line(key, value) + @config_text << "\toption #{key} #{value}\n" + end + def render_server_element(e) server = @context.servers[e.name] render_server(server) @@ -85,7 +122,7 @@ def render_server(server) end def handle_context_change - if [HAProxy::Default].include?(@prev_context.class) + if CONFIG_NODES.include?(@prev_context.class) # Render any configs which were added new_configs = @prev_context.config.keys - @config_list.keys @@ -95,7 +132,17 @@ def handle_context_change end end - if [HAProxy::Listener, HAProxy::Backend].include?(@prev_context.class) + if OPTION_NODES.include?(@prev_context.class) + # Render any configs which were added + new_options = @prev_context.options.keys - @option_list.keys + + new_options.each do |option_name| + option_value = @prev_context.options[option_name] + render_option_line(option_name, option_value) + end + end + + if SERVER_NODES.include?(@prev_context.class) # Render any servers that were added new_servers = @prev_context.servers.keys - @server_list.keys @@ -105,6 +152,8 @@ def handle_context_change end end @server_list = {} + @config_list = {} + @option_list = {} end def render_server_attributes(attributes) @@ -137,6 +186,9 @@ def update_render_context(e) when 'HAProxy::Treetop::BackendSection' section_name = e.backend_header.proxy_name ? e.backend_header.proxy_name.content : nil @context = @config.backend(section_name) + when 'HAProxy::Treetop::UserlistSection' + section_name = e.userlist_header.proxy_name ? e.userlist_header.proxy_name.content : nil + @context = @config.userlist(section_name) else @context = @prev_context end diff --git a/lib/haproxy/treetop/config.treetop b/lib/haproxy/treetop/config.treetop index 8b58486..cb52e3b 100644 --- a/lib/haproxy/treetop/config.treetop +++ b/lib/haproxy/treetop/config.treetop @@ -36,7 +36,7 @@ module HAProxy::Treetop end rule userlist_header - whitespace "userlist" whitespace proxy_name comment_text? line_break + whitespace "userlist" whitespace proxy_name comment_text? line_break end rule defaults_header @@ -68,7 +68,7 @@ module HAProxy::Treetop end rule config_line - whitespace !("defaults" / "global" / "listen" / "frontend" / "backend") keyword whitespace value? comment_text? line_break + whitespace !("defaults" / "global" / "listen" / "frontend" / "backend" / "userlist") keyword whitespace value? comment_text? line_break end rule comment_line @@ -88,7 +88,18 @@ module HAProxy::Treetop end rule keyword - (("errorfile" / "timeout") whitespace)? [a-z0-9\-\.]+ + (( + "capture response header" / + "capture request header" / + "use_backend" / + "errorfile" / + "errorloc" / + "timeout" / + "group" / + "stats" / + "user" / + "acl" + ) whitespace)? [A-Za-z0-9\-\._]+ end rule server_name diff --git a/lib/haproxy/treetop/nodes.rb b/lib/haproxy/treetop/nodes.rb index 8cbd030..b2210e9 100644 --- a/lib/haproxy/treetop/nodes.rb +++ b/lib/haproxy/treetop/nodes.rb @@ -119,6 +119,14 @@ def attribute class OptionLine < ::Treetop::Runtime::SyntaxNode include StrippedTextContent include OptionalValueElement + + def key + self.keyword.content + end + + def attribute + self.value.content + end end class ServerLine < ::Treetop::Runtime::SyntaxNode @@ -204,6 +212,10 @@ def frontends self.elements.select {|e| e.class == FrontendSection} end + def userlists + self.elements.select {|e| e.class == UserlistSection} + end + def backends self.elements.select {|e| e.class == BackendSection} end