File: event.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (177 lines) | stat: -rw-r--r-- 4,506 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# frozen_string_literal: true

module ContainerRegistry
  class Event
    include Gitlab::Utils::StrongMemoize

    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?
        ::Gitlab::UsageDataCounters::ContainerRegistryEventCounter.count("#{EVENT_PREFIX}_delete_manifest")
      else
        event = usage_data_event_for(tracking_action)
        ::Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event, values: originator.id) if event
      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, [], [:container_registry_size])
    end
  end
end

::ContainerRegistry::Event.prepend_mod_with('ContainerRegistry::Event')