# frozen_string_literal: true module HasEnvironmentScope extend ActiveSupport::Concern prepended do validates( :environment_scope, presence: true, format: { with: ::Gitlab::Regex.environment_scope_regex, message: ::Gitlab::Regex.environment_scope_regex_message } ) ## # Select rows which have a scope that matches the given environment name. # Rows are ordered by relevance, by default. The most relevant row is # placed at the end of a list. # # options: # - relevant_only: (boolean) # You can get the most relevant row only. Other rows are not be # selected even if its scope matches the environment name. # This is equivalent to using `#last` from SQL standpoint. # scope :on_environment, -> (environment_name, relevant_only: false) do order_direction = relevant_only ? 'DESC' : 'ASC' where = <<~SQL environment_scope IN (:wildcard, :environment_name) OR :environment_name LIKE #{::Gitlab::SQL::Glob.to_like('environment_scope')} SQL order = <<~SQL CASE environment_scope WHEN :wildcard THEN 0 WHEN :environment_name THEN 2 ELSE 1 END #{order_direction} SQL values = { wildcard: '*', environment_name: environment_name } sanitized_order_sql = sanitize_sql_array([order, values]) # The query is trying to find variables with scopes matching the # current environment name. Suppose the environment name is # 'review/app', and we have variables with environment scopes like: # * variable A: review # * variable B: review/app # * variable C: review/* # * variable D: * # And the query should find variable B, C, and D, because it would # try to convert the scope into a LIKE pattern for each variable: # * A: review # * B: review/app # * C: review/% # * D: % # Note that we'll match % and _ literally therefore we'll escape them. # In this case, B, C, and D would match. We also want to prioritize # the exact matched name, and put * last, and everything else in the # middle. So the order should be: D < C < B relation = where(where, values) .order(Arel.sql(sanitized_order_sql)) # `order` cannot escape for us! relation = relation.limit(1) if relevant_only relation end scope :for_environment, ->(environment) do if environment on_environment(environment) else where(environment_scope: '*') end end end def environment_scope=(new_environment_scope) super(new_environment_scope.to_s.strip) end end