gitlab-ce/app/models/container_registry/event.rb

187 lines
4.7 KiB
Ruby

# frozen_string_literal: true
module ContainerRegistry
class Event
include Gitlab::Utils::StrongMemoize
include Gitlab::InternalEventsTracking
ALLOWED_ACTIONS = %w[push delete].freeze
PUSH_ACTION = 'push'
DELETE_ACTION = 'delete'
EVENT_TRACKING_CATEGORY = 'container_registry:notification'
EVENT_PREFIX = 'i_container_registry'
ALLOWED_ACTOR_TYPES = %w[
personal_access_token
build
gitlab_or_ldap
deploy_token
].freeze
TRACKABLE_ACTOR_EVENTS = %w[
push_tag
delete_tag
push_repository
delete_repository
create_repository
].freeze
attr_reader :event
def initialize(event)
@event = event
end
def supported?
action.in?(ALLOWED_ACTIONS)
end
def handle!
update_project_statistics
end
def track!
tracking_action = "#{action}_#{tracked_target}"
if target_repository? && action_push? && !container_repository_exists?
tracking_action = "create_repository"
end
::Gitlab::Tracking.event(EVENT_TRACKING_CATEGORY, tracking_action)
if manifest_delete_event?
track_internal_event("delete_manifest_from_container_registry", project: project)
else
event = usage_data_event_for(tracking_action)
return unless event
event_attributes = if origin_class == DeployToken
{ additional_properties: { property: originator.id.to_s } }
elsif origin_class == User
{ user: originator }
end
track_internal_event(event, event_attributes)
end
end
private
def tracked_target
target_tag? ? :tag : :repository
end
def target_tag?
# There is no clear indication in the event structure when we delete a top-level manifest
# except existence of "tag" key
event['target'].has_key?('tag')
end
def target_digest?
event['target'].has_key?('digest')
end
def target_repository?
!target_tag? && event['target'].has_key?('repository')
end
def action
event['action']
end
def action_push?
PUSH_ACTION == action
end
def action_delete?
DELETE_ACTION == action
end
def container_repository_exists?
return unless container_registry_path
ContainerRepository.exists_by_path?(container_registry_path)
end
def container_registry_path
strong_memoize(:container_registry_path) do
path = event.dig('target', 'repository')
next unless path
ContainerRegistry::Path.new(path)
end
end
def project
container_registry_path&.repository_project
end
# counter name for unique user tracking (for MAU)
def usage_data_event_for(tracking_action)
return unless originator
return unless TRACKABLE_ACTOR_EVENTS.include?(tracking_action)
"#{EVENT_PREFIX}_#{tracking_action}_#{originator_suffix}"
end
def originator
return unless ALLOWED_ACTOR_TYPES.include?(originator_type)
origin_id = get_origin_id(originator_type)
return unless origin_id
origin_class.find(origin_id)
end
strong_memoize_attr :originator
def originator_suffix
originator.is_a?(DeployToken) ? 'deploy_token' : 'user'
end
def originator_type
event.dig('actor', 'user_type')
end
def origin_class
deploy_token?(originator_type) ? DeployToken : User
end
def get_origin_id(originator_type)
encoded_user_jwt = event.dig('actor', 'user')
return unless encoded_user_jwt
key = deploy_token?(originator_type) ? 'deploy_token_id' : 'user_id'
user_info = decode_user_info(encoded_user_jwt)
user_info&.dig('user_info', key)
end
def decode_user_info(encoded_user_jwt)
registry_key_file = File.read(Gitlab.config.registry.key)
registry_key = OpenSSL::PKey::RSA.new(registry_key_file)
JSONWebToken::RSAToken.decode(encoded_user_jwt, registry_key).first
rescue Errno::ENOENT, JWT::VerificationError, JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
nil
end
def deploy_token?(originator_type)
originator_type == 'deploy_token'
end
def manifest_delete_event?
action_delete? && target_digest?
end
def update_project_statistics
return unless supported?
return unless target_tag? || manifest_delete_event?
return unless project
Rails.cache.delete(project.root_ancestor.container_repositories_size_cache_key)
ProjectCacheWorker.perform_async(project.id, [], %w[container_registry_size])
end
end
end
::ContainerRegistry::Event.prepend_mod_with('ContainerRegistry::Event')