gitlab_rb_loader.rb (112 lines of code) (raw):

# frozen_string_literal: true require 'json' class GitlabRbLoader attr_accessor :flat_hash class << self def audit(reference_rb, actual_rb) errors = [] actual_rb.flat_hash.each do |name, actual| next unless reference_rb.flat_hash.key?(name) expected = reference_rb.flat_hash[name] e = GitlabRbLoader.audit_value(name, expected, actual) errors += e unless e.empty? end # Check for extract configs that do not exist in reference. extra_names = actual_rb.flat_hash.keys - reference_rb.flat_hash.keys unless extra_names.empty? errors << "ERROR: gitlab.rb has extraneous config values that do not exist in reference\n" extra_names.each { |n| errors << " #{n} = #{actual_rb.flat_hash[n]}\n" } end missing_names = reference_rb.flat_hash.keys - actual_rb.flat_hash.keys unless missing_names.empty? errors << "ERROR: gitlab.rb missing config values that are specified in the reference:\n" missing_names.each { |n| errors << " Missing: #{n} = #{reference_rb.flat_hash[n]}\n" } end errors end private def audit_value(name, expected, actual) errors = [] if expected.is_a?(Array) && actual.is_a?(Array) audit_array(errors, name, expected, actual) else audit_type(errors, name, expected, actual) end errors end def audit_type(errors, name, expected, actual) return unless expected != actual errors << if expected.instance_of?(actual.class) || [true, false].include?(expected) <<-MSG ERROR: #{name} mismatch: Reference: #{expected} Actual: #{actual} MSG else <<-MSG ERROR: #{name} type mismatch Reference: #{expected} (type: #{expected.class}) Actual: #{actual} (type: #{actual.class}) MSG end end def audit_array(errors, name, expected, actual) # If arrays contanin hashes, compare them as they are. unless expected.empty? && expected.none? { |elem| elem.is_a?(Hash) } expected = expected.sort actual = actual.sort end return unless expected != actual message = <<-MSG ERROR: #{name} mismatch Reference [#{expected.length}]: '#{expected}' Actual [#{actual.length}]: '#{actual}' MSG message += " Missing: '#{expected - actual}'\n" unless (expected - actual).empty? message += " Extra : '#{actual - expected}'\n" unless (actual - expected).empty? errors << message end end def initialize(rb_file_path, cfg_ctx = nil) # puts "Loading #{rb_file_path}" @gitlab_rb_config = {} b = binding method_names = %w[external_url roles git_data_dirs pages_external_url] gitlab_rb_config_text = File.read(rb_file_path) # Get list of Hash variables with format: `gitlab_var["...` then remove the # method names from the list. var_names = gitlab_rb_config_text.scan(/^\s*([a-z0-9A-Z_]+)\s*\[/).flatten.uniq.reject do |vn| method_names.include?(vn) end # Create hash placeholders for each variable for receiving the key # assignments. var_names.each { |vn| eval("#{vn} = Hash.new", b, __FILE__, __LINE__) } # Inject configuration context variables __VAR__ and thier values. cfg_ctx&.vars_each do |n, v| # puts "Setting name: #{n} to Value #{v}" eval("#{n} = #{v}", b, __FILE__, __LINE__) end # Web nodes have nginx nested option. Need to figure out default dict equivalent later. b.eval("nginx['status'] = {'options' => Hash.new }") if var_names.include?('nginx') # SQL nodes have patroni nested option. Need to figure out default dict equivalent later. b.eval("patroni['postgresql'] = Hash.new ") if var_names.include?('patroni') #### BEGIN GITLAB_RB eval(gitlab_rb_config_text, b) #### END GITLAB_RB # Extract variables defined in gitlab_rb into gitlab_rb_config instance var. var_names.each { |vn| @gitlab_rb_config[vn] = eval(vn, b) } # Convert dict keys from symbols to strings. cfg = JSON.parse(@gitlab_rb_config.to_json).transform_keys(&:to_s) @flat_hash = generate_flat(cfg) end # BEGIN: methods called during the eval() of gitlab.rb def external_url(url) @gitlab_rb_config['external_url'] = url end def pages_external_url(url) @gitlab_rb_config['pages_external_url'] = url end def roles(roles) @gitlab_rb_config['roles'] = roles end def git_data_dirs(data_dirs) @gitlab_rb_config['git_data_dirs'] = data_dirs end # END: methods invoked called during the eval() of gitlab.rb private # Flatten a nested hash. # input: { 'l1' => { 'l2.1' => 'value1', 'l2.2' => 'value2' }, } # output: { "l1['l2.1']" => 'value1', "l1['l2.2']" => 'value2' } # def generate_flat(nested_hash, prefix = '') raise "Not hash - #{nested_hash}/#{nested_hash.class}" unless nested_hash.is_a?(Hash) result_hash = {} nested_hash.each do |k, v| sub_prefix = prefix + (prefix == '' ? k : "['#{k}']") if v.is_a?(Hash) generate_flat(v, sub_prefix) else # puts "Adding keyname: #{sub_prefix} k: #{k} v: #{v}" result_hash[sub_prefix] = v end end result_hash end end