lib/gitlab/qa/runtime/omnibus_configuration.rb (107 lines of code) (raw):
# frozen_string_literal: true
require 'active_support'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/inflections'
require 'erb'
module Gitlab
module QA
module Runtime
class OmnibusConfiguration
# @param prefixed_config The configuration to be prefixed to the new configuration
def initialize(prefixed_config = nil)
@config = ["# Generated by GitLab QA Omnibus Configurator at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"]
return unless prefixed_config
# remove generation statement if it exists within the prefixed configuration that was passed
# and insert the rest AFTER the very first generation statement
config_to_insert = prefixed_config.to_s
generation_regexp = /# Generated by GitLab QA Omnibus Configurator.*\n/
config_to_insert = config_to_insert.gsub(generation_regexp, '') if config_to_insert.match?(generation_regexp)
@config.insert(1, config_to_insert)
end
def to_s
sanitize!.join("\n")
end
ERB_PATTERN = /<%=?\s?(\S+)\s?%>/
# Execute the ERB code to produce completed template
# @example
# external_url '<%= gitlab.address %>' #=> external_url 'http://gitlab-ee-09d62235.test'
#
# @param [GitLab::QA::Component::Gitlab] gitlab
#
# @return [Array]
def expand_config_template(gitlab)
@config.map! do |item|
item.match(ERB_PATTERN) ? ERB.new(item).result(binding) : item
end
end
def configuration
raise NotImplementedError
end
# Before hook for any additional configuration
# This would usually be a container that needs to be running
# @return Any instance of [Gitlab::QA::Component::Base]
def prepare; end
# Commands to execute before tests are run against GitLab (after reconfigure)
def exec_commands
[]
end
# Ensures no duplicate entries and sanitizes configurations
# @raise RuntimeError if competing configurations exist
# rubocop:disable Metrics/AbcSize
def sanitize!
sanitized = @config.map do |config|
next config if config.start_with?('#') || config.match(/\w+\(/) # allow for comments and method invocations
next config if config.match(ERB_PATTERN)
# sometimes "=" is part of a Hash. Only split based on the first "="
k, v = config.split("=", 2)
# make sure each config is well-formed
# e.g., gitlab_rails['packages_enabled'] = true
# NOT gitlab_rails['packages_enabled']=true
v.nil? ? k.strip : "#{k.strip} = #{v.strip.tr('"', "'")}".strip
end
sanitized = split_items(sanitized).uniq
sanitized = merge_arrays(sanitized)
# check for duplicates
duplicate_keys = []
duplicates = sanitized.reject do |n|
key = n.split('=').first
duplicate_keys << key unless duplicate_keys.include?(key)
end
errors = []
duplicates.each { |duplicate| errors << "Duplicate entry found: `#{duplicate}`" }
raise "Errors exist within the Omnibus Configuration!\n#{errors.join(',')}" if errors.any?
@config = sanitized
end
# rubocop:enable Metrics/AbcSize
def <<(config)
@config << config.strip unless config.strip.empty?
end
private
# Merge Omnibus configuration values if the value is an array
# @example
# array = ['a["setting"] = [1]', 'a["setting"] = [2]']
# merge_arrays(array) #=> ['a["setting"] = [1, 2]']
#
# @param [Array] arr
#
# @return [Array]
def merge_arrays(arr)
entries_with_array = {}
arr.reject! do |item|
key, value = item.split("=", 2)
array_content_match = value&.match(/^\s?\[([\s\S]+)\][\s;]?$/)
if array_content_match
if entries_with_array[key]
entries_with_array[key] << array_content_match[1]
else
entries_with_array[key] = [array_content_match[1]]
end
end
end
entries_with_array.each do |k, v|
arr << "#{k}= [#{v.map(&:chomp).join(', ')}]".strip
end
arr
end
# Split each Omnibus setting into an array item
# @example
# input = ["a['setting_1'] = true",
# "a['setting_2'] = [
# {
# name: 'setting_2a_name'
# }
# ]
# a['setting_3'] = false"]
#
# split_items(input) #=>
# ["a['setting_1'] = true",
# "a['setting_2'] = [
# {
# name: 'setting_2a_name'
# }
# ]",
# "a['setting_3'] = false"]
#
# @param [Array] input
#
# @return [Array]
#
# rubocop:disable Metrics/AbcSize
def split_items(input)
items = []
input.each do |item|
if count_occurrences(item, ' = ') > 1
multi_line_item = []
item.split("\n").each do |line|
if /( = |external_url)/.match?(line)
if multi_line_item.count > 1
items.pop
items << multi_line_item.join("\n")
end
items << line
multi_line_item = [line]
else
multi_line_item << line
end
end
if multi_line_item.count > 1
items.pop
items << multi_line_item.join("\n")
end
else
items << item
end
end
items
end
# rubocop:enable Metrics/AbcSize
# Count occurrences of a substring in a string
# @param [String] str
# @param [String] substr
#
# @return [Array]
def count_occurrences(str, substr)
str.scan(/(?=#{substr})/).count
end
end
end
end
end