125 lines
4.1 KiB
Ruby
125 lines
4.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Operations
|
|
module FeatureFlags
|
|
class Strategy < ApplicationRecord
|
|
STRATEGY_DEFAULT = 'default'
|
|
STRATEGY_GITLABUSERLIST = 'gitlabUserList'
|
|
STRATEGY_GRADUALROLLOUTUSERID = 'gradualRolloutUserId'
|
|
STRATEGY_FLEXIBLEROLLOUT = 'flexibleRollout'
|
|
STRATEGY_USERWITHID = 'userWithId'
|
|
STRATEGIES = {
|
|
STRATEGY_DEFAULT => [].freeze,
|
|
STRATEGY_GITLABUSERLIST => [].freeze,
|
|
STRATEGY_GRADUALROLLOUTUSERID => %w[groupId percentage].freeze,
|
|
STRATEGY_FLEXIBLEROLLOUT => %w[groupId rollout stickiness].freeze,
|
|
STRATEGY_USERWITHID => ['userIds'].freeze
|
|
}.freeze
|
|
USERID_MAX_LENGTH = 256
|
|
STICKINESS_SETTINGS = %w[default userId sessionId random].freeze
|
|
|
|
self.table_name = 'operations_strategies'
|
|
|
|
belongs_to :feature_flag
|
|
has_many :scopes, class_name: 'Operations::FeatureFlags::Scope'
|
|
has_one :strategy_user_list
|
|
has_one :user_list, through: :strategy_user_list
|
|
|
|
validates :name,
|
|
inclusion: {
|
|
in: STRATEGIES.keys,
|
|
message: 'strategy name is invalid'
|
|
}
|
|
|
|
validate :parameters_validations, if: -> { errors[:name].blank? }
|
|
validates :user_list, presence: true, if: -> { name == STRATEGY_GITLABUSERLIST }
|
|
validates :user_list, absence: true, if: -> { name != STRATEGY_GITLABUSERLIST }
|
|
validate :same_project_validation, if: -> { user_list.present? }
|
|
|
|
accepts_nested_attributes_for :scopes, allow_destroy: true
|
|
|
|
def user_list_id=(user_list_id)
|
|
self.user_list = ::Operations::FeatureFlags::UserList.find(user_list_id)
|
|
end
|
|
|
|
private
|
|
|
|
def same_project_validation
|
|
unless user_list.project_id == feature_flag.project_id
|
|
errors.add(:user_list, 'must belong to the same project')
|
|
end
|
|
end
|
|
|
|
def parameters_validations
|
|
validate_parameters_type &&
|
|
validate_parameters_keys &&
|
|
validate_parameters_values
|
|
end
|
|
|
|
def validate_parameters_type
|
|
parameters.is_a?(Hash) || parameters_error('parameters are invalid')
|
|
end
|
|
|
|
def validate_parameters_keys
|
|
actual_keys = parameters.keys.sort
|
|
expected_keys = STRATEGIES[name].sort
|
|
expected_keys == actual_keys || parameters_error('parameters are invalid')
|
|
end
|
|
|
|
def validate_parameters_values
|
|
case name
|
|
when STRATEGY_GRADUALROLLOUTUSERID
|
|
gradual_rollout_user_id_parameters_validation
|
|
when STRATEGY_FLEXIBLEROLLOUT
|
|
flexible_rollout_parameters_validation
|
|
when STRATEGY_USERWITHID
|
|
FeatureFlagUserXidsValidator.validate_user_xids(self, :parameters, parameters['userIds'], 'userIds')
|
|
end
|
|
end
|
|
|
|
def within_range?(value, min, max)
|
|
return false unless value.is_a?(String)
|
|
return false unless value.match?(/\A\d+\z/)
|
|
|
|
value.to_i.between?(min, max)
|
|
end
|
|
|
|
def gradual_rollout_user_id_parameters_validation
|
|
percentage = parameters['percentage']
|
|
group_id = parameters['groupId']
|
|
|
|
unless within_range?(percentage, 0, 100)
|
|
parameters_error('percentage must be a string between 0 and 100 inclusive')
|
|
end
|
|
|
|
unless group_id.is_a?(String) && group_id.match(/\A[a-z]{1,32}\z/)
|
|
parameters_error('groupId parameter is invalid')
|
|
end
|
|
end
|
|
|
|
def flexible_rollout_parameters_validation
|
|
stickiness = parameters['stickiness']
|
|
group_id = parameters['groupId']
|
|
rollout = parameters['rollout']
|
|
|
|
unless STICKINESS_SETTINGS.include?(stickiness)
|
|
options = STICKINESS_SETTINGS.to_sentence(last_word_connector: ', or ')
|
|
parameters_error("stickiness parameter must be #{options}")
|
|
end
|
|
|
|
unless group_id.is_a?(String) && group_id.match(/\A[a-z]{1,32}\z/)
|
|
parameters_error('groupId parameter is invalid')
|
|
end
|
|
|
|
unless within_range?(rollout, 0, 100)
|
|
parameters_error('rollout must be a string between 0 and 100 inclusive')
|
|
end
|
|
end
|
|
|
|
def parameters_error(message)
|
|
errors.add(:parameters, message)
|
|
false
|
|
end
|
|
end
|
|
end
|
|
end
|