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