# frozen_string_literal: true require 'active_support' require 'active_support/core_ext/object/blank' 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 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 # 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