From 25b6ecd10e4080dabbff0072ad9b6750002e555d Mon Sep 17 00:00:00 2001 From: Eduardo Bonet Date: Mon, 9 Sep 2024 10:59:51 +0200 Subject: [PATCH] Logs events for self-hosted model deletion Logs an audit event when a self-hosted model configuration was deleted. Changelog: added --- .../types/self_hosted_model_destroyed.yml | 9 ++++ doc/user/compliance/audit_event_types.md | 6 +++ .../admin/ai/self_hosted_models_controller.rb | 4 +- .../mutations/ai/self_hosted_models/delete.rb | 24 +++------ .../ai/self_hosted_models/destroy_service.rb | 39 ++++++++++++++ .../destroy_service_spec.rb | 52 +++++++++++++++++++ 6 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 config/audit_events/types/self_hosted_model_destroyed.yml create mode 100644 ee/app/services/ai/self_hosted_models/destroy_service.rb create mode 100644 ee/spec/services/ai/self_hosted_models/destroy_service_spec.rb diff --git a/config/audit_events/types/self_hosted_model_destroyed.yml b/config/audit_events/types/self_hosted_model_destroyed.yml new file mode 100644 index 00000000000000..970227e234711d --- /dev/null +++ b/config/audit_events/types/self_hosted_model_destroyed.yml @@ -0,0 +1,9 @@ +name: self_hosted_model_destroyed +description: A new self-hosted model configuration was destroyed +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/477999 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321 +feature_category: self-hosted_models +milestone: '17.4' +saved_to_database: true +scope: [Instance, User] +streamed: true diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index f38d27ef7a4091..4ce3f57f936d8b 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -449,6 +449,12 @@ Audit event types belong to the following product categories. |:------------|:------------|:------------------|:---------|:--------------|:--------------| | [`policy_project_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102154) | This event is triggered whenever the security policy project is updated for a project. | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/377877) | Group, Project | +### Self-hosted models + +| Name | Description | Saved to database | Streamed | Introduced in | Scope | +|:------------|:------------|:------------------|:---------|:--------------|:--------------| +| [`self_hosted_model_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321) | A new self-hosted model configuration was destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance, User | + ### Source code management | Name | Description | Saved to database | Streamed | Introduced in | Scope | diff --git a/ee/app/controllers/admin/ai/self_hosted_models_controller.rb b/ee/app/controllers/admin/ai/self_hosted_models_controller.rb index 7d8cead2550689..8e05afe3a9a197 100644 --- a/ee/app/controllers/admin/ai/self_hosted_models_controller.rb +++ b/ee/app/controllers/admin/ai/self_hosted_models_controller.rb @@ -46,7 +46,9 @@ def update def destroy @self_hosted_model = ::Ai::SelfHostedModel.find(params[:id]) - if @self_hosted_model.destroy + result = ::Ai::SelfHostedModels::DestroyService.new(@self_hosted_model, current_user).execute + + if result.success? redirect_to admin_ai_self_hosted_models_url, notice: _("Self-Hosted Model was deleted") else render :index diff --git a/ee/app/graphql/mutations/ai/self_hosted_models/delete.rb b/ee/app/graphql/mutations/ai/self_hosted_models/delete.rb index c178f33e9a58ff..efb2e407d6cc95 100644 --- a/ee/app/graphql/mutations/ai/self_hosted_models/delete.rb +++ b/ee/app/graphql/mutations/ai/self_hosted_models/delete.rb @@ -15,28 +15,20 @@ class Delete < Base def resolve(**args) check_feature_access! - result = delete_self_hosted_model(args) - - if result[:errors].present? - { - self_hosted_model: nil, - errors: Array(result[:errors]) - } - else - { self_hosted_model: result, errors: [] } - end - end - - private - - def delete_self_hosted_model(args) model = find_object(id: args[:id]) return { errors: ["Self-hosted model not found"] } unless model - model.destroy + result = ::Ai::SelfHostedModels::DestroyService.new(model, current_user).execute + + { + self_hosted_model: result.success? ? result.payload : nil, + errors: result.error? ? Array.wrap(result.errors) : [] + } end + private + def find_object(id:) GitlabSchema.object_from_id(id, expected_type: ::Ai::SelfHostedModel).sync end diff --git a/ee/app/services/ai/self_hosted_models/destroy_service.rb b/ee/app/services/ai/self_hosted_models/destroy_service.rb new file mode 100644 index 00000000000000..b98c9ebde737c2 --- /dev/null +++ b/ee/app/services/ai/self_hosted_models/destroy_service.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Ai + module SelfHostedModels + class DestroyService + def initialize(self_hosted_model, user) + @self_hosted_model = self_hosted_model + @user = user + end + + def execute + if self_hosted_model.destroy + audit_destroy_event + + ServiceResponse.success(payload: self_hosted_model) + else + ServiceResponse.error(message: self_hosted_model.errors.full_messages.join(", ")) + end + end + + private + + attr_accessor :self_hosted_model, :user + + def audit_destroy_event + model = self_hosted_model + audit_context = { + name: 'self_hosted_model_destroyed', + author: user, + scope: user, + target: model, + message: "Self-hosted model #{model.name}/#{model.model}/#{model.endpoint} destroyed" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/spec/services/ai/self_hosted_models/destroy_service_spec.rb b/ee/spec/services/ai/self_hosted_models/destroy_service_spec.rb new file mode 100644 index 00000000000000..e37eb86d7d4c1f --- /dev/null +++ b/ee/spec/services/ai/self_hosted_models/destroy_service_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Ai::SelfHostedModels::DestroyService, feature_category: :"self-hosted_models" do + let_it_be(:user) { create(:user) } + + let(:self_hosted_model) { create(:ai_self_hosted_model) } + + let(:service) { described_class.new(self_hosted_model, user) } + + let(:audit_event) do + model = self_hosted_model + { + name: 'self_hosted_model_destroyed', + author: user, + scope: user, + target: model, + message: "Self-hosted model #{model.name}/#{model.model}/#{model.endpoint} destroyed" + } + end + + describe '#execute', :aggregate_failures do + subject(:result) { service.execute } + + context 'when the model is successfully destroyed' do + it 'returns a success response' do + expect(Gitlab::Audit::Auditor).to receive(:audit).with(audit_event) + + expect { result }.to change { ::Ai::SelfHostedModel.count }.by(-1) + + expect(result).to be_success + expect(result.payload).to eq(self_hosted_model) + end + end + + context 'when the model fails to be destroyed' do + before do + allow(self_hosted_model).to receive(:destroy).and_return(false) + allow(self_hosted_model).to receive_message_chain(:errors, :full_messages).and_return(['Error message']) + end + + it 'returns an error response' do + expect(Gitlab::Audit::Auditor).not_to receive(:audit) + expect { result }.not_to change { ::Ai::SelfHostedModel.count } + + expect(result).to be_error + expect(result.message).to eq('Error message') + end + end + end +end -- GitLab