Skip to content
代码片段 群组 项目
提交 99aa76d2 编辑于 作者: Taka Nishida's avatar Taka Nishida 提交者: Mark Chao
浏览文件

Receive and track events from GitLab Agent

Adds a new API endpoint that receives events from GitLab Agent (KAS).
It will track events using internal event tracking.
上级 4fd29edf
No related branches found
No related tags found
无相关合并请求
......@@ -554,6 +554,46 @@ curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Con
--data '{"counters": {"gitops_sync":1}}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
```
### GitLab agent events
Called from GitLab agent server (`kas`) to track events.
| Attribute | Type | Required | Description |
|:------------------------------------------------------------------------------|:--------------|:---------|:--------------------------------------------------------------------------|
| `events` | hash | no | Hash of events |
| `events["k8s_api_proxy_requests_unique_users_via_ci_access"]` | hash array | no | Array of events for `k8s_api_proxy_requests_unique_users_via_ci_access` |
| `events["k8s_api_proxy_requests_unique_users_via_ci_access"]["user_id"]` | integer | no | The user ID for the event |
| `events["k8s_api_proxy_requests_unique_users_via_ci_access"]["project_id"]` | integer | no | The project ID for the event |
| `events["k8s_api_proxy_requests_unique_users_via_user_access"]` | hash array | no | Array of events for `k8s_api_proxy_requests_unique_users_via_user_access` |
| `events["k8s_api_proxy_requests_unique_users_via_user_access"]["user_id"]` | integer | no | The user ID for the event |
| `events["k8s_api_proxy_requests_unique_users_via_user_access"]["project_id"]` | integer | no | The project ID for the event |
| `events["k8s_api_proxy_requests_unique_users_via_pat_access"]` | hash array | no | Array of events for `k8s_api_proxy_requests_unique_users_via_pat_access` |
| `events["k8s_api_proxy_requests_unique_users_via_pat_access"]["user_id"]` | integer | no | The user ID for the event |
| `events["k8s_api_proxy_requests_unique_users_via_pat_access"]["project_id"]` | integer | no | The project ID for the event |
```plaintext
POST /internal/kubernetes/agent_events
```
Example Request:
```shell
curl --request POST \
--url "http://localhost:3000/api/v4/internal/kubernetes/agent_events" \
--header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Content-Type: application/json" \
--data '{
"events": {
"k8s_api_proxy_requests_unique_users_via_ci_access": [
{
"user_id": 1,
"project_id": 1
}
]
}
}'
```
### Create Starboard vulnerability
Called from the GitLab agent server (`kas`) to create a security vulnerability
......
......@@ -54,6 +54,20 @@ def increment_unique_events
end
end
def track_events
event_lists = params[:events]&.slice(
:k8s_api_proxy_requests_unique_users_via_ci_access,
:k8s_api_proxy_requests_unique_users_via_user_access,
:k8s_api_proxy_requests_unique_users_via_pat_access
)
return if event_lists.blank?
users, projects = load_users_and_projects(event_lists)
event_lists.each do |event_name, events|
track_events_for(event_name, events, users, projects)
end
end
def track_unique_user_events
events = params[:unique_counters]&.slice(
:k8s_api_proxy_requests_unique_users_via_ci_access,
......@@ -130,6 +144,29 @@ def access_token
PersonalAccessToken.find_by_token(params[:access_key])
end
strong_memoize_attr :access_token
private
def load_users_and_projects(event_lists)
all_events = event_lists.values.flatten
unique_user_ids = all_events.pluck('user_id').compact.uniq # rubocop:disable CodeReuse/ActiveRecord -- this pluck isn't from ActiveRecord, it's from ActiveSupport
unique_project_ids = all_events.pluck('project_id').compact.uniq # rubocop:disable CodeReuse/ActiveRecord -- this pluck isn't from ActiveRecord, it's from ActiveSupport
users = User.id_in(unique_user_ids).index_by(&:id)
projects = Project.id_in(unique_project_ids).index_by(&:id)
[users, projects]
end
def track_events_for(event_name, events, users, projects)
events.each do |event|
next if event.blank?
user = users[event[:user_id]]
project = projects[event[:project_id]]
next if user.nil? || project.nil?
Gitlab::InternalEvents.track_event(event_name, user: user, project: project)
end
end
end
end
end
......
......@@ -147,6 +147,33 @@ class Kubernetes < ::API::Base
bad_request!(e.message)
end
end
namespace 'kubernetes/agent_events' do
desc 'POST agent events' do
detail 'Updates agent events'
end
params do
optional :events, type: Hash, desc: 'Array of events' do
optional :k8s_api_proxy_requests_unique_users_via_ci_access, type: Array, desc: 'An array of events that have interacted with the CI tunnel via `ci_access`' do
optional :user_id, type: Integer, desc: 'User ID'
optional :project_id, type: Integer, desc: 'Project ID'
end
optional :k8s_api_proxy_requests_unique_users_via_user_access, type: Array, desc: 'An array of events that have interacted with the CI tunnel via `ci_access`' do
optional :user_id, type: Integer, desc: 'User ID'
optional :project_id, type: Integer, desc: 'Project ID'
end
optional :k8s_api_proxy_requests_unique_users_via_pat_access, type: Array, desc: 'An array of events that have interacted with the CI tunnel via `ci_access`' do
optional :user_id, type: Integer, desc: 'User ID'
optional :project_id, type: Integer, desc: 'Project ID'
end
end
end
post '/', feature_category: :deployment_management do
track_events
no_content!
end
end
end
end
end
......
......@@ -162,6 +162,87 @@ def send_request(headers: {}, params: {})
end
end
describe 'POST /internal/kubernetes/agent_events', :clean_gitlab_redis_shared_state do
def send_request(headers: {}, params: {})
post api('/internal/kubernetes/agent_events'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
include_examples 'authorization'
include_examples 'error handling'
context 'is authenticated for an agent' do
let!(:agent_token) { create(:cluster_agent_token) }
context 'when events are valid' do
let(:request_count) { 2 }
let(:users) { create_list(:user, 3).index_by(&:id) }
let(:projects) { create_list(:project, 3).index_by(&:id) }
let(:events) do
user_ids = users.keys
project_ids = projects.keys
event_data = Array.new(3) do |i|
{ user_id: user_ids[i], project_id: project_ids[i] }
end
{
k8s_api_proxy_requests_unique_users_via_ci_access: event_data,
k8s_api_proxy_requests_unique_users_via_user_access: event_data,
k8s_api_proxy_requests_unique_users_via_pat_access: event_data
}
end
it 'tracks events and returns no_content', :aggregate_failures do
events.each do |event_name, event_list|
event_list.each do |event|
expect(Gitlab::InternalEvents).to receive(:track_event)
.with(event_name.to_s, user: users[event[:user_id]], project: projects[event[:project_id]])
.exactly(request_count).times
end
end
request_count.times do
send_request(params: { events: events })
end
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when events are empty' do
let(:events) do
{
k8s_api_proxy_requests_unique_users_via_ci_access: [],
k8s_api_proxy_requests_unique_users_via_user_access: [],
k8s_api_proxy_requests_unique_users_via_pat_access: []
}
end
it 'returns no_content for empty events' do
expect(Gitlab::InternalEvents).not_to receive(:track_event)
send_request(params: { events: events })
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when events have non-integer values' do
let(:events) do
{
k8s_api_proxy_requests_unique_users_via_ci_access: [
{ user_id: 'string', project_id: 111 }
]
}
end
it 'returns 400 for non-integer values' do
expect(Gitlab::InternalEvents).not_to receive(:track_event)
send_request(params: { events: events })
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end
describe 'POST /internal/kubernetes/agent_configuration' do
def send_request(headers: {}, params: {})
post api('/internal/kubernetes/agent_configuration'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册