lib/fluent/config/v1_parser.rb
#
# Fluentd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'strscan'
require 'uri'
require 'fluent/config/error'
require 'fluent/config/basic_parser'
require 'fluent/config/literal_parser'
require 'fluent/config/element'
module Fluent
module Config
class V1Parser < LiteralParser
ELEMENT_NAME = /[a-zA-Z0-9_]+/
def self.parse(data, fname, basepath = Dir.pwd, eval_context = nil)
ss = StringScanner.new(data)
ps = V1Parser.new(ss, basepath, fname, eval_context)
ps.parse!
end
def initialize(strscan, include_basepath, fname, eval_context)
super(strscan, eval_context)
@include_basepath = include_basepath
@fname = fname
@logger = defined?($log) ? $log : nil
end
def parse!
attrs, elems = parse_element(true, nil)
root = Element.new('ROOT', '', attrs, elems)
root.v1_config = true
spacing
unless eof?
parse_error! "expected EOF"
end
return root
end
ELEM_SYMBOLS = ['match', 'source', 'filter', 'system']
RESERVED_PARAMS = %W(@type @id @label @log_level)
def parse_element(root_element, elem_name, attrs = {}, elems = [])
while true
spacing
if eof?
if root_element
break
end
parse_error! "expected end tag '</#{elem_name}>' but got end of file"
end
if skip(/\<\//)
e_name = scan(ELEMENT_NAME)
spacing
unless skip(/\>/)
parse_error! "expected character in tag name"
end
unless line_end
parse_error! "expected end of line after end tag"
end
if e_name != elem_name
parse_error! "unmatched end tag"
end
break
elsif skip(/\</)
e_name = scan(ELEMENT_NAME)
spacing
e_arg = scan_string(/(?:#{ZERO_OR_MORE_SPACING}\>)/)
spacing
unless skip(/\>/)
parse_error! "expected '>'"
end
unless line_end
parse_error! "expected end of line after tag"
end
e_arg ||= ''
# call parse_element recursively
e_attrs, e_elems = parse_element(false, e_name)
new_e = Element.new(e_name, e_arg, e_attrs, e_elems)
new_e.v1_config = true
elems << new_e
elsif root_element && skip(/(\@include|include)#{SPACING}/)
if !prev_match.start_with?('@')
@logger.warn "'include' is deprecated. Use '@include' instead" if @logger
end
parse_include(attrs, elems)
else
k = scan_string(SPACING)
spacing_without_comment
if prev_match.include?("\n") || eof? # support 'tag_mapped' like "without value" configuration
attrs[k] = ""
else
if k == '@include'
parse_include(attrs, elems)
elsif RESERVED_PARAMS.include?(k)
v = parse_literal
unless line_end
parse_error! "expected end of line"
end
attrs[k] = v
else
if k.start_with?('@')
if root_element || ELEM_SYMBOLS.include?(elem_name)
parse_error! "'@' is the system reserved prefix. Don't use '@' prefix parameter in the configuration: #{k}"
else
# TODO: This is for backward compatibility. It will throw an error in the future.
@logger.warn "'@' is the system reserved prefix. It works in the nested configuration for now but it will be rejected: #{k}" if @logger
end
end
v = parse_literal
unless line_end
parse_error! "expected end of line"
end
attrs[k] = v
end
end
end
end
return attrs, elems
end
def parse_include(attrs, elems)
uri = scan_string(LINE_END)
eval_include(attrs, elems, uri)
line_end
end
def eval_include(attrs, elems, uri)
# replace space(s)(' ') with '+' to prevent invalid uri due to space(s).
# See: https://github.com/fluent/fluentd/pull/2780#issuecomment-576081212
u = URI.parse(uri.tr(' ', '+'))
if u.scheme == 'file' || (!u.scheme.nil? && u.scheme.length == 1) || u.path == uri.tr(' ', '+') # file path
# When the Windows absolute path then u.scheme.length == 1
# e.g. C:
path = URI.decode_www_form_component(u.path)
if path[0] != ?/
pattern = File.expand_path("#{@include_basepath}/#{path}")
else
pattern = path
end
Dir.glob(pattern).sort.each { |entry|
basepath = File.dirname(entry)
fname = File.basename(entry)
data = File.read(entry)
data.force_encoding('UTF-8')
ss = StringScanner.new(data)
V1Parser.new(ss, basepath, fname, @eval_context).parse_element(true, nil, attrs, elems)
}
else
require 'open-uri'
basepath = '/'
fname = path
data = URI.open(uri) { |f| f.read }
data.force_encoding('UTF-8')
ss = StringScanner.new(data)
V1Parser.new(ss, basepath, fname, @eval_context).parse_element(true, nil, attrs, elems)
end
rescue SystemCallError => e
cpe = ConfigParseError.new("include error #{uri} - #{e}")
cpe.set_backtrace(e.backtrace)
raise cpe
end
# override
def error_sample
"#{@fname} #{super}"
end
end
end
end