cookbooks/fb_fluentbit/libraries/helpers.rb (116 lines of code) (raw):

# # Copyright (c) 2020-present, Facebook, Inc. # All rights reserved. # # 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. # module FB class Fluentbit def self.plugins_from_node(node) plugins = [] Plugin::TYPES.each do |plugin_type| node['fb_fluentbit'][plugin_type].each do |name, configs| configs.each do |human_name, config| plugins << Plugin.new( :type => plugin_type, :name => name, :human_name => human_name, :config => config, ) end end end plugins end def self.external_plugins_from_node(node) node['fb_fluentbit']['external'].map do |name, config| ExternalPlugin.new(name, config['package'], config['path']) end end def self.parsers_from_node(node) node['fb_fluentbit']['parser'].map do |name, config| Parser.new(name, config) end end class ExternalPlugin attr_reader :name, :package, :path def initialize(name, package, path) @name = name @package = package @path = path end def validate if @package.nil? fail "fb_fluentbit: external plugin '#{@name}' missing required " + 'key \'package\'' end if @path.nil? fail "fb_fluentbit: external plugin '#{@name}' missing required " + 'key \'path\'' end end end class Parser attr_reader :name, :config def initialize(name, config) @name = name @config = config end def format if @config.key?('format') @config['format'] elsif @config.key?('Format') @config['Format'] end end def validate if format.nil? fail "fb_fluentbit: parser '#{@name}' does not define format" end end end class Plugin # Supported types of plugins. TYPES = %w{input filter output}.freeze attr_reader :type, :name, :human_name, :config def initialize(type:, name:, human_name:, config:) @type = type @name = name @human_name = human_name @config = config end def validate(parsers) if @type == 'filter' && @name == 'parser' parser_names = if @config.key?('Parser') @config['Parser'] elsif @config.key?('parser') @config['parser'] end if parser_names.is_a?(String) parser_names = [parser_names] end unconfigured_parsers = parser_names - parsers.map(&:name) unless unconfigured_parsers.empty? fail "fb_fluentbit: plugin '#{@human_name}' is using undefined " + "parser(s): #{unconfigured_parsers}" end end end # Helper struct for defining config file key/value pairs. Config = Struct.new(:key, :value) # # Fluentbit's config is a little odd. It mostly seems to be key/value # based, with the following caveats # * Keys can repeat (which seems to OR all the values) # * Values can sometimes be key/value based. # # In order to support these two, the following API decisions were made: # * Multiple keys are supported by setting cookbook API values to lists. # We then replicate each key/value pair, one per value in the list. # * Values that are key/values pairs are separated by = and replicated # like the above. # * Values can be callable by passing in 'procs' (similar to other # cookbooks). # # See spec tests for examples of what this looks like. # def serialize_config final_conf = [] config.each do |key, val| if val.respond_to?(:call) final_conf << Config.new(key, val.call) elsif val.is_a?(Array) final_conf += val.map { |v| Config.new(key, v) } # Hashes can have some inner nesting, but only to a certain point. elsif val.is_a?(Hash) val.each do |k, v| if v.is_a?(Array) final_conf += v.map { |e| Config.new(key, "#{k}=#{e}") } else final_conf << Config.new(key, "#{k}=#{v}") end end else final_conf << Config.new(key, val) end end final_conf end end end end