From 3b5ebadba028b9c21dcf2b68174d89769b45370b Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Thu, 1 May 2025 10:23:19 +0300 Subject: [PATCH 01/11] Implement deleting custom images --- lib/travis/api/v3/artifact_manager_client.rb | 73 +++++++++++++++++++ lib/travis/api/v3/queries/custom_images.rb | 5 ++ lib/travis/api/v3/routes.rb | 1 + .../api/v3/services/custom_images/delete.rb | 21 ++++++ .../v3/services/custom_images/for_owner.rb | 2 + 5 files changed, 102 insertions(+) create mode 100644 lib/travis/api/v3/artifact_manager_client.rb create mode 100644 lib/travis/api/v3/services/custom_images/delete.rb diff --git a/lib/travis/api/v3/artifact_manager_client.rb b/lib/travis/api/v3/artifact_manager_client.rb new file mode 100644 index 000000000..d2e26f7b6 --- /dev/null +++ b/lib/travis/api/v3/artifact_manager_client.rb @@ -0,0 +1,73 @@ +module Travis::API::V3 + class ArtifactManagerClient + class ConfigurationError < StandardError; end + + def initialize(user_id) + @user_id = user_id + end + + def images(owner_type, owner_id) + response = connection.get("/images?owner_type=#{owner_type}&id=#{owner_id}") + handle_images_response(response) + end + + def delete_images(image_ids) + response = connection.delete('/images') do |req| + req.body = { image_ids: }.to_json + end + handle_errors_and_respond(response) + end + + private + + def handle_images_response(response) + handle_errors_and_respond(response) do |r| + r['images'].map { |image_data| Travis::API::V3::Models::CustomImage.new(image_data) } + end + end + + def handle_errors_and_respond(response) + body = response.body.is_a?(String) && response.body.length.positive? ? JSON.parse(response.body) : response.body + + case response.status + when 200, 201 + yield(body) if block_given? + when 202 + true + when 204 + true + when 400 + raise Travis::API::V3::ClientError, body['error'] + when 403 + raise Travis::API::V3::InsufficientAccess, body['rejection_code'] + when 404 + raise Travis::API::V3::NotFound, body['error'] + when 422 + raise Travis::API::V3::UnprocessableEntity, body['error'] + else + raise Travis::API::V3::ServerError, 'Artifact manager failed' + end + end + + def connection(timeout: 10) + @connection ||= Faraday.new(url: artifact_manager_url, ssl: { ca_path: '/usr/lib/ssl/certs' }) do |conn| + conn.request(:authorization, :basic, '_', artifact_manager_auth_key) + conn.headers['X-Travis-User-Id'] = @user_id.to_s + conn.headers['Content-Type'] = 'application/json' + conn.request :json + conn.response :json + conn.options[:open_timeout] = timeout + conn.options[:timeout] = timeout + conn.adapter :net_http + end + end + + def artifact_manager_url + Travis.config.artifact_manager&.url || raise(ConfigurationError, 'No artifact manager url configured') + end + + def artifact_manager_auth_key + Travis.config.artifact_manager&.auth_key || raise(ConfigurationError, 'No artifact manager auth key configured') + end + end +end diff --git a/lib/travis/api/v3/queries/custom_images.rb b/lib/travis/api/v3/queries/custom_images.rb index 4ceaf0066..c77b5298b 100644 --- a/lib/travis/api/v3/queries/custom_images.rb +++ b/lib/travis/api/v3/queries/custom_images.rb @@ -4,6 +4,11 @@ def for_owner(owner) Models::CustomImage.where(owner_id: owner.id, owner_type: owner_type(owner)) end + def delete(image_ids, sender) + client = ArtifactManagerClient.new(sender.id) + client.delete_images(image_ids) + end + private def owner_type(owner) diff --git a/lib/travis/api/v3/routes.rb b/lib/travis/api/v3/routes.rb index ceebf1ba6..0ca0e725e 100644 --- a/lib/travis/api/v3/routes.rb +++ b/lib/travis/api/v3/routes.rb @@ -173,6 +173,7 @@ module Routes resource :custom_images do route '/custom_images' get :for_owner + delete :delete end end diff --git a/lib/travis/api/v3/services/custom_images/delete.rb b/lib/travis/api/v3/services/custom_images/delete.rb new file mode 100644 index 000000000..9b7ff063d --- /dev/null +++ b/lib/travis/api/v3/services/custom_images/delete.rb @@ -0,0 +1,21 @@ +module Travis::API::V3 + class Services::CustomImages::Delete < Service + params :image_ids + + def run! + raise LoginRequired unless access_control.full_access_or_logged_in? + + owner = query(:owner).find + raise NotFound unless owner + + if owner.is_a?(Travis::API::V3::Models::User) + access_control.permissions(owner).write! + else + access_control.permissions(owner).admin! + end + + query.delete(params['image_ids'], access_control.user) + deleted + end + end +end diff --git a/lib/travis/api/v3/services/custom_images/for_owner.rb b/lib/travis/api/v3/services/custom_images/for_owner.rb index 5fa4b75f8..b7c6a44f1 100644 --- a/lib/travis/api/v3/services/custom_images/for_owner.rb +++ b/lib/travis/api/v3/services/custom_images/for_owner.rb @@ -9,8 +9,10 @@ def run! owner = query(:owner).find raise NotFound unless owner + repo = owner.repositories.first raise InsufficientAccess unless repo + access_control.permissions(repo).build_create! results = query(:custom_images).for_owner(owner) From eba366768d7a813ffb1c84537492f65d9f108ff1 Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Mon, 5 May 2025 12:25:56 +0300 Subject: [PATCH 02/11] Add specs --- lib/travis/testing/factories.rb | 27 +++++++++++ spec/v3/artifact_manager_client_spec.rb | 32 +++++++++++++ spec/v3/services/custom_images/delete_spec.rb | 45 ++++++++++++++++++ .../services/custom_images/for_owner_spec.rb | 47 +++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 spec/v3/artifact_manager_client_spec.rb create mode 100644 spec/v3/services/custom_images/delete_spec.rb create mode 100644 spec/v3/services/custom_images/for_owner_spec.rb diff --git a/lib/travis/testing/factories.rb b/lib/travis/testing/factories.rb index 2b4a02ca8..81a3a4494 100644 --- a/lib/travis/testing/factories.rb +++ b/lib/travis/testing/factories.rb @@ -244,4 +244,31 @@ owner_type { 'User' } accepted_at { nil } end + + factory :custom_image, class: Travis::API::V3::Models::CustomImage do + name { 'custom_image_name' } + description { 'custom_image_description' } + state { 'available' } + owner { User.first || FactoryBot.create(:user) } + owner_type { 'User' } + architecture { 'x86' } + size_bytes { 1024 } + created_at { Time.now.utc } + updated_at { Time.now.utc } + + after(:build) do |custom_image| + custom_image.define_singleton_method(:readonly?) { false } + end + end + + factory :custom_image_log, class: Travis::API::V3::Models::CustomImageLog do + custom_image { CustomImage.first || FactoryBot.create(:custom_image) } + action { 'created' } + sender_id { User.first.id || FactoryBot.create(:user).id } + created_at { Time.now.utc } + + after(:build) do |custom_image_log| + custom_image_log.define_singleton_method(:readonly?) { false } + end + end end diff --git a/spec/v3/artifact_manager_client_spec.rb b/spec/v3/artifact_manager_client_spec.rb new file mode 100644 index 000000000..68b427713 --- /dev/null +++ b/spec/v3/artifact_manager_client_spec.rb @@ -0,0 +1,32 @@ +describe Travis::API::V3::ArtifactManagerClient do + let(:client) { described_class.new(user_id) } + let(:url) { 'https://artifact-manager.travis-ci.com' } + let(:user_id) { rand(999) } + let(:auth_key) { 'super_secret' } + + before do + Travis.config.artifact_manager.url = url + Travis.config.artifact_manager.auth_key = auth_key + end + + describe '#delete_images' do + let(:image_ids) { [1, 2, 3] } + subject { client.delete_images(image_ids) } + + it 'sends a DELETE request to the artifact manager' do + stub_request(:delete, "#{url}/images") + .with(body: { image_ids: image_ids }.to_json, headers: { 'X-Travis-User-Id' => user_id.to_s }) + .to_return(status: 200, body: { images: [] }.to_json, headers: { 'Content-Type' => 'application/json' }) + + subject + end + + it 'raises an error if the request fails' do + stub_request(:delete, "#{url}/images") + .with(body: { image_ids: image_ids }.to_json, headers: { 'X-Travis-User-Id' => user_id.to_s }) + .to_return(status: 400, body: { error: 'Bad Request' }.to_json, headers: { 'Content-Type' => 'application/json' }) + + expect { subject }.to raise_error(Travis::API::V3::ClientError, 'Bad Request') + end + end +end diff --git a/spec/v3/services/custom_images/delete_spec.rb b/spec/v3/services/custom_images/delete_spec.rb new file mode 100644 index 000000000..33055e521 --- /dev/null +++ b/spec/v3/services/custom_images/delete_spec.rb @@ -0,0 +1,45 @@ +describe Travis::API::V3::Services::CustomImages::Delete, set_app: true do + let(:user) { FactoryBot.create(:user) } + let(:token) { Travis::Api::App::AccessToken.create(user: user, app_id: 1) } + let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} + let(:parsed_body) { JSON.load(body) } + + before do + Travis.config.host = 'travis-ci.com' + end + + context 'authenticated' do + describe "deleting custom images by id list" do + before do + stub_request(:delete, "#{Travis.config.artifact_manager.url}/images") + .with( + body: { image_ids: ['1', '2', '3'] }.to_json, + headers: { 'X-Travis-User-Id' => user.id.to_s } + ) + .to_return(status: 204, headers: { 'Content-Type' => 'application/json' }) + end + + it 'makes call to artifact manager' do + delete("/v3/owner/#{user.login}/custom_images", { image_ids: [1, 2, 3] }, headers) + + expect(last_response.status).to eq(204) + expect(last_response.body).to be_empty + end + end + + describe "deleting custom images with no permissions" do + let(:organization) { FactoryBot.create(:org) } + + before { stub_request(:get, %r((.+)/roles/org/(.+))).to_return(status: 200, body: JSON.generate({ 'roles' => [] })) } + + it 'returns an error' do + delete("/v3/owner/GitHub/#{organization.name}/custom_images", { image_ids: [1, 2, 3] }, headers) + + expect(last_response.status).to eq(403) + expect(parsed_body).to include( + 'error_type' => 'insufficient_access' + ) + end + end + end +end diff --git a/spec/v3/services/custom_images/for_owner_spec.rb b/spec/v3/services/custom_images/for_owner_spec.rb new file mode 100644 index 000000000..e3687c624 --- /dev/null +++ b/spec/v3/services/custom_images/for_owner_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +RSpec.describe Travis::API::V3::Services::CustomImages::ForOwner, set_app: true do + let(:json_headers) { { 'HTTP_ACCEPT' => 'application/json' } } + let(:authorization) { { 'permissions' => [ 'repository_state_update', 'repository_build_create' ] } } + + before do + Travis.config.host = 'travis-ci.com' + stub_request(:get, %r((.+)/permissions/repo/(.+))).to_return(status: 200, body: JSON.generate(authorization)) + end + + context 'authenticated' do + let(:user) { FactoryBot.create(:user, name: 'Joe', login: 'joe') } + let(:user_token) { Travis::Api::App::AccessToken.create(user: user, app_id: 1) } + let!(:repository) { FactoryBot.create(:repository, owner: user) } + let!(:custom_image) { FactoryBot.create(:custom_image, owner: user) } + let!(:custom_image_log) { FactoryBot.create(:custom_image_log, custom_image: custom_image, sender_id: user.id) } + + context 'when user has custom images' do + it 'returns custom images' do + get("/v3/owner/#{user.login}/custom_images", {}, json_headers.merge('HTTP_AUTHORIZATION' => "token #{user_token}")) + + expect(last_response).to be_ok + expect(JSON.parse(last_response.body)['custom_images'].first).to include( + 'id' => custom_image.id, + 'name' => custom_image.name, + 'size_bytes' => custom_image.size_bytes + ) + end + end + + context 'when user has no build create permission' do + let(:other_user) { FactoryBot.create(:user, name: 'Jane', login: 'jane') } + let(:other_user_token) { Travis::Api::App::AccessToken.create(user: other_user, app_id: 1) } + let!(:repository) { FactoryBot.create(:repository, owner: other_user) } + let(:authorization) { { 'permissions' => [ 'repository_state_update' ] } } + + it 'returns an empty list' do + get("/v3/owner/#{other_user.login}/custom_images", {}, json_headers.merge('HTTP_AUTHORIZATION' => "token #{other_user_token}")) + + expect(JSON.parse(last_response.body)).to include( + 'error_type' => 'insufficient_access' + ) + end + end + end +end From dc885e061cc5156fa55dc27cd933768dd6e22077 Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Mon, 5 May 2025 12:39:56 +0300 Subject: [PATCH 03/11] Fix spec --- spec/v3/services/custom_images/delete_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/v3/services/custom_images/delete_spec.rb b/spec/v3/services/custom_images/delete_spec.rb index 33055e521..e21d6eea3 100644 --- a/spec/v3/services/custom_images/delete_spec.rb +++ b/spec/v3/services/custom_images/delete_spec.rb @@ -3,8 +3,12 @@ let(:token) { Travis::Api::App::AccessToken.create(user: user, app_id: 1) } let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} let(:parsed_body) { JSON.load(body) } + let(:url) { 'https://artifact-manager.travis-ci.com' } + let(:auth_key) { 'super_secret' } before do + Travis.config.artifact_manager.url = url + Travis.config.artifact_manager.auth_key = auth_key Travis.config.host = 'travis-ci.com' end From d12e949634fafa8c6481b9db5e235310ddbfae16 Mon Sep 17 00:00:00 2001 From: gabriel-arc <57348209+GbArc@users.noreply.github.com> Date: Wed, 28 May 2025 12:03:16 +0200 Subject: [PATCH 04/11] custom image restart (#1371) * custom image restart * split to create/use * handling full image response from am on check (#1372) * handling full image response from am on check --------- Co-authored-by: GbArc --- .../api/enqueue/services/restart_model.rb | 58 ++++++++++++++++++- lib/travis/api/v3/artifact_manager_client.rb | 26 +++++++++ .../enqueue/services/restart_model_spec.rb | 24 ++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/lib/travis/api/enqueue/services/restart_model.rb b/lib/travis/api/enqueue/services/restart_model.rb index 7ee56d1e8..c7af11db3 100644 --- a/lib/travis/api/enqueue/services/restart_model.rb +++ b/lib/travis/api/enqueue/services/restart_model.rb @@ -27,7 +27,7 @@ def push(event, payload) end def accept? - current_user && permission? && resetable? && billing? + current_user && permission? && resetable? && billing? && custom_image_create_allowed? && custom_image_use_allowed? end def billing? @@ -61,12 +61,64 @@ def billing? end end + def custom_image_create_allowed? + return true if !!Travis.config.enterprise + + @_custom_image_create_allowed ||= begin + jobs = target.is_a?(Job) ? [target] : target.matrix + jobs.map do |job| + next unless job.config + + create_name = job.config.dig(:vm, :create, :name) + if create_name + return false unless !!artifact_manager.create(owner: repository.owner, image_name: create_name, job_restart: true) + end + end + true + rescue Travis::API::V3::Error + false + end + end + + + def custom_image_use_allowed? + return true if !!Travis.config.enterprise + + @_custom_image_use_allowed ||= begin + jobs = target.is_a?(Job) ? [target] : target.matrix + + jobs.map do |job| + next unless job.config + + use_name = job.config.dig(:vm, :use) + use_name = use_name[:name] if use_name.is_a?(Hash) + + if use_name + return false unless can_use_custom_image?(owner: repository.owner, image_name: use_name) + end + end + true + rescue Travis::API::V3::Error + false + end + end + + def can_use_custom_image?(owner:, image_name:) + !!artifact_manager.use(owner: , image_name:) + rescue Travis::API::V3::NotFound + true + rescue Travis::API::V3::Error + false + end + def messages messages = [] messages << { notice: "The #{type} was successfully restarted." } if accept? messages << { error: 'You do not seem to have sufficient permissions.' } unless permission? messages << { error: 'You do not have enough credits.' } unless billing? messages << { error: "This #{type} currently can not be restarted." } unless resetable? + messages << { error: "Image creation build restart not allowed." } unless custom_image_create_allowed? + messages << { error: "Can't use the custom image. Make sure it's already created" } unless custom_image_use_allowed? messages end @@ -88,6 +140,10 @@ def target private + def artifact_manager + @_artifact_manger = Travis::API::V3::ArtifactManagerClient.new(current_user&.id) + end + def subscription Subscription.where(owner: repository.owner)&.first end diff --git a/lib/travis/api/v3/artifact_manager_client.rb b/lib/travis/api/v3/artifact_manager_client.rb index d2e26f7b6..e0f9a4744 100644 --- a/lib/travis/api/v3/artifact_manager_client.rb +++ b/lib/travis/api/v3/artifact_manager_client.rb @@ -6,6 +6,32 @@ def initialize(user_id) @user_id = user_id end + def create(owner:, image_name:, job_restart: false) + params = { + owner_type: owner.class.name.downcase, + id: owner.id, + name: image_name, + job_restart: + } + handle_errors_and_respond(connection.post("/create", params)) do |body| + body.include?('image_id') ? body['image_id'] : false + end + rescue Faraday::Error + raise ArtifactManagerConnectionError + end + + def use(owner:, image_name:) + handle_errors_and_respond(connection.get("/image/#{owner.class.name.downcase}/#{owner.id}/#{image_name}")) do |body| + return body['image_id'] if body.include?('image_id') + + return body['image']['id'] if body.include?('image') + + false + end + rescue Faraday::Error + raise ArtifactManagerConnectionError + end + def images(owner_type, owner_id) response = connection.get("/images?owner_type=#{owner_type}&id=#{owner_id}") handle_images_response(response) diff --git a/spec/lib/travis/api/enqueue/services/restart_model_spec.rb b/spec/lib/travis/api/enqueue/services/restart_model_spec.rb index 1f167d64c..fda57f1f0 100644 --- a/spec/lib/travis/api/enqueue/services/restart_model_spec.rb +++ b/spec/lib/travis/api/enqueue/services/restart_model_spec.rb @@ -96,6 +96,30 @@ end end end + + context 'when trying to restart a custom image build' do + let(:job) { FactoryBot.create(:job, repository: repository, state: 'canceled', config: { vm: { create: { name: 'testimg1'} } }) } + + before do + repository.permissions.create(user: user, build: true) + Travis.config.artifact_manager = { url: 'http://localhost:9911' , auth_key: 'test_test'} + stub_request(:post, "http://localhost:9911/create").to_return(status: 200, body: { image_id: 1}.to_json) + end + + include_examples 'restarts the job' + end + + context 'when trying to restart a custom image build without permission' do + let(:job) { FactoryBot.create(:job, repository: repository, state: 'canceled', config: { vm: { create: { name: 'testimg1'} } }) } + + before do + repository.permissions.create(user: user, build: true) + Travis.config.artifact_manager = { url: 'http://localhost:9911' , auth_key: 'test_test'} + stub_request(:post, "http://localhost:9911/create").to_return(status: 401) + end + + include_examples 'does not restart the job' + end end end From bce0e10ada09ae677e2fe2ace0caf3681a7a09c1 Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Mon, 12 May 2025 11:23:46 +0300 Subject: [PATCH 05/11] Address review comments --- lib/travis/api/v3/models/custom_image.rb | 2 +- lib/travis/api/v3/models/custom_image_log.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/travis/api/v3/models/custom_image.rb b/lib/travis/api/v3/models/custom_image.rb index b0763a940..8a5dd3b33 100644 --- a/lib/travis/api/v3/models/custom_image.rb +++ b/lib/travis/api/v3/models/custom_image.rb @@ -7,7 +7,7 @@ class Models::CustomImage < Model scope :available, -> { where(state: 'available') } def created_by - user_id = custom_image_logs.where(action: 'created').first&.sender_id + user_id = custom_image_logs.created.frist&.sender_id return unless user_id User.find(user_id) diff --git a/lib/travis/api/v3/models/custom_image_log.rb b/lib/travis/api/v3/models/custom_image_log.rb index 255763935..26de4ba02 100644 --- a/lib/travis/api/v3/models/custom_image_log.rb +++ b/lib/travis/api/v3/models/custom_image_log.rb @@ -2,5 +2,6 @@ module Travis::API::V3 class Models::CustomImageLog < Model after_initialize :readonly! belongs_to :custom_image + enum action: { created: 'created', used: 'used', deleted: 'deleted', other: 'other' } end end From 6f78487850ca896a58c418c7ac9ea3803af5f2c8 Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Wed, 28 May 2025 13:36:13 +0300 Subject: [PATCH 06/11] Fixes --- lib/travis/api/v3/artifact_manager_client.rb | 8 ++++---- lib/travis/api/v3/models/custom_image.rb | 2 +- lib/travis/api/v3/queries/custom_images.rb | 6 +++--- lib/travis/api/v3/services/custom_images/delete.rb | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/travis/api/v3/artifact_manager_client.rb b/lib/travis/api/v3/artifact_manager_client.rb index e0f9a4744..05e6368fe 100644 --- a/lib/travis/api/v3/artifact_manager_client.rb +++ b/lib/travis/api/v3/artifact_manager_client.rb @@ -37,11 +37,11 @@ def images(owner_type, owner_id) handle_images_response(response) end - def delete_images(image_ids) - response = connection.delete('/images') do |req| - req.body = { image_ids: }.to_json + def delete_images(owner_type, owner_id, image_ids) + image_ids.each do |image_id| + response = connection.delete("/image/#{owner_type.downcase}/#{owner_id}/#{image_id}") + handle_errors_and_respond(response) end - handle_errors_and_respond(response) end private diff --git a/lib/travis/api/v3/models/custom_image.rb b/lib/travis/api/v3/models/custom_image.rb index 8a5dd3b33..bfe3c30ce 100644 --- a/lib/travis/api/v3/models/custom_image.rb +++ b/lib/travis/api/v3/models/custom_image.rb @@ -7,7 +7,7 @@ class Models::CustomImage < Model scope :available, -> { where(state: 'available') } def created_by - user_id = custom_image_logs.created.frist&.sender_id + user_id = custom_image_logs.created.first&.sender_id return unless user_id User.find(user_id) diff --git a/lib/travis/api/v3/queries/custom_images.rb b/lib/travis/api/v3/queries/custom_images.rb index c77b5298b..045c6d46c 100644 --- a/lib/travis/api/v3/queries/custom_images.rb +++ b/lib/travis/api/v3/queries/custom_images.rb @@ -1,12 +1,12 @@ module Travis::API::V3 class Queries::CustomImages < Query def for_owner(owner) - Models::CustomImage.where(owner_id: owner.id, owner_type: owner_type(owner)) + Models::CustomImage.where(owner_id: owner.id, owner_type: owner_type(owner)).order('created_at DESC') end - def delete(image_ids, sender) + def delete(image_ids, owner, sender) client = ArtifactManagerClient.new(sender.id) - client.delete_images(image_ids) + client.delete_images(owner_type(owner), owner.id, image_ids) end private diff --git a/lib/travis/api/v3/services/custom_images/delete.rb b/lib/travis/api/v3/services/custom_images/delete.rb index 9b7ff063d..a8e54dfe1 100644 --- a/lib/travis/api/v3/services/custom_images/delete.rb +++ b/lib/travis/api/v3/services/custom_images/delete.rb @@ -14,7 +14,7 @@ def run! access_control.permissions(owner).admin! end - query.delete(params['image_ids'], access_control.user) + query.delete(params['image_ids'], owner, access_control.user) deleted end end From 50b160f02c178e816c76572d22f88e985fec72e7 Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Thu, 29 May 2025 15:33:20 +0300 Subject: [PATCH 07/11] Show only active images --- lib/travis/api/v3/queries/custom_images.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/travis/api/v3/queries/custom_images.rb b/lib/travis/api/v3/queries/custom_images.rb index 045c6d46c..364a4b516 100644 --- a/lib/travis/api/v3/queries/custom_images.rb +++ b/lib/travis/api/v3/queries/custom_images.rb @@ -1,7 +1,7 @@ module Travis::API::V3 class Queries::CustomImages < Query def for_owner(owner) - Models::CustomImage.where(owner_id: owner.id, owner_type: owner_type(owner)).order('created_at DESC') + Models::CustomImage.active.where(owner_id: owner.id, owner_type: owner_type(owner)).order('created_at DESC') end def delete(image_ids, owner, sender) From e3aa4cdf27c43936f78f04def9e00cf65ae02cde Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Thu, 29 May 2025 16:27:34 +0300 Subject: [PATCH 08/11] Fix typo --- lib/travis/api/v3/queries/custom_images.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/travis/api/v3/queries/custom_images.rb b/lib/travis/api/v3/queries/custom_images.rb index 364a4b516..c95441494 100644 --- a/lib/travis/api/v3/queries/custom_images.rb +++ b/lib/travis/api/v3/queries/custom_images.rb @@ -1,7 +1,7 @@ module Travis::API::V3 class Queries::CustomImages < Query def for_owner(owner) - Models::CustomImage.active.where(owner_id: owner.id, owner_type: owner_type(owner)).order('created_at DESC') + Models::CustomImage.available.where(owner_id: owner.id, owner_type: owner_type(owner)).order('created_at DESC') end def delete(image_ids, owner, sender) From 72ff6dce0c4c88f8cb6d5cd5a9e16416b8f00bda Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Fri, 30 May 2025 10:04:56 +0300 Subject: [PATCH 09/11] Fix permissions for custom images list --- lib/travis/api/v3/services/custom_images/for_owner.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/travis/api/v3/services/custom_images/for_owner.rb b/lib/travis/api/v3/services/custom_images/for_owner.rb index b7c6a44f1..3187f23b9 100644 --- a/lib/travis/api/v3/services/custom_images/for_owner.rb +++ b/lib/travis/api/v3/services/custom_images/for_owner.rb @@ -10,10 +10,15 @@ def run! raise NotFound unless owner - repo = owner.repositories.first - raise InsufficientAccess unless repo + if owner.is_a?(Travis::API::V3::Models::User) + raise InsufficientAccess unless access_control.user.id == owner.id + else + membership = Models::Membership.where(organization_id: owner.id).joins(:user).includes(:user).first + raise NotFound unless membership - access_control.permissions(repo).build_create! + build_permission = membership.build_permission.nil? ? true : membership.build_permission + raise InsufficientAccess unless build_permission + end results = query(:custom_images).for_owner(owner) result results From 2a705d3895782fad8b48bbb38d03d9c5d8621f86 Mon Sep 17 00:00:00 2001 From: AndriiMysko Date: Fri, 6 Jun 2025 14:36:55 +0300 Subject: [PATCH 10/11] Add default artifact manager config --- lib/travis/config/defaults.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/travis/config/defaults.rb b/lib/travis/config/defaults.rb index 543113b28..f1d012360 100644 --- a/lib/travis/config/defaults.rb +++ b/lib/travis/config/defaults.rb @@ -106,7 +106,8 @@ def fallback_logs_api_auth_token recaptcha: { endpoint: 'https://www.google.com', secret: ENV['RECAPTCHA_SECRET_KEY'] || '' }, antifraud: { captcha_max_failed_attempts: 3, captcha_block_duration: 24, credit_card_max_failed_attempts: 3, credit_card_block_duration: 24 }, legacy_roles: false, - internal_users: [{id: 0, login: 'cron'}] + internal_users: [{id: 0, login: 'cron'}], + artifact_manager: { url: 'http://artifact_manager:3434', auth_key: 'secret' } default :_access => [:key] From 08405b3abbb59c06fe3fe78da3ae08086e180b58 Mon Sep 17 00:00:00 2001 From: gabriel-arc <57348209+GbArc@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:21:00 +0200 Subject: [PATCH 11/11] expose membership /build_permission (#1373) * expose membership /build_permission * specs --------- Co-authored-by: GbArc --- lib/travis/api/v3/renderer/membership.rb | 6 ++++++ lib/travis/api/v3/renderer/user.rb | 2 +- lib/travis/api/v3/services/custom_images/delete.rb | 2 +- spec/v3/artifact_manager_client_spec.rb | 10 ++++------ spec/v3/services/custom_images/delete_spec.rb | 6 +----- spec/v3/services/custom_images/for_owner_spec.rb | 2 +- spec/v3/services/installation/find_spec.rb | 1 + spec/v3/services/owner/find_spec.rb | 4 ++++ spec/v3/services/v2_subscription/executions_spec.rb | 3 +++ 9 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 lib/travis/api/v3/renderer/membership.rb diff --git a/lib/travis/api/v3/renderer/membership.rb b/lib/travis/api/v3/renderer/membership.rb new file mode 100644 index 000000000..af3c66c30 --- /dev/null +++ b/lib/travis/api/v3/renderer/membership.rb @@ -0,0 +1,6 @@ +module Travis::API::V3 + class Renderer::Membership < ModelRenderer + representation(:minimal, :organization_id, :build_permission) + representation(:standard, :user_id, :organization_id, :role, :build_permission) + end +end diff --git a/lib/travis/api/v3/renderer/user.rb b/lib/travis/api/v3/renderer/user.rb index a336297c4..6fcc34c2b 100644 --- a/lib/travis/api/v3/renderer/user.rb +++ b/lib/travis/api/v3/renderer/user.rb @@ -2,7 +2,7 @@ module Travis::API::V3 class Renderer::User < Renderer::Owner - representation(:standard, :email, :is_syncing, :synced_at, :recently_signed_up, :secure_user_hash, :ro_mode, :confirmed_at, :custom_keys, :internal, :last_activity_at) + representation(:standard, :email, :is_syncing, :synced_at, :recently_signed_up, :secure_user_hash, :ro_mode, :confirmed_at, :custom_keys, :internal, :last_activity_at, :memberships) representation(:additional, :emails, :collaborator) def email diff --git a/lib/travis/api/v3/services/custom_images/delete.rb b/lib/travis/api/v3/services/custom_images/delete.rb index a8e54dfe1..af2051efc 100644 --- a/lib/travis/api/v3/services/custom_images/delete.rb +++ b/lib/travis/api/v3/services/custom_images/delete.rb @@ -9,7 +9,7 @@ def run! raise NotFound unless owner if owner.is_a?(Travis::API::V3::Models::User) - access_control.permissions(owner).write! + access_control.permissions(owner).sync! else access_control.permissions(owner).admin! end diff --git a/spec/v3/artifact_manager_client_spec.rb b/spec/v3/artifact_manager_client_spec.rb index 68b427713..687ee8a41 100644 --- a/spec/v3/artifact_manager_client_spec.rb +++ b/spec/v3/artifact_manager_client_spec.rb @@ -11,19 +11,17 @@ describe '#delete_images' do let(:image_ids) { [1, 2, 3] } - subject { client.delete_images(image_ids) } + subject { client.delete_images('user', user_id, image_ids) } it 'sends a DELETE request to the artifact manager' do - stub_request(:delete, "#{url}/images") - .with(body: { image_ids: image_ids }.to_json, headers: { 'X-Travis-User-Id' => user_id.to_s }) - .to_return(status: 200, body: { images: [] }.to_json, headers: { 'Content-Type' => 'application/json' }) + stub_request(:delete, %r{#{url}/image/user/#{user_id}/\d+}) + .to_return(status: 200, headers: { 'Content-Type' => 'application/json' }) subject end it 'raises an error if the request fails' do - stub_request(:delete, "#{url}/images") - .with(body: { image_ids: image_ids }.to_json, headers: { 'X-Travis-User-Id' => user_id.to_s }) + stub_request(:delete, %r{#{url}/image/user/#{user_id}/\d+}) .to_return(status: 400, body: { error: 'Bad Request' }.to_json, headers: { 'Content-Type' => 'application/json' }) expect { subject }.to raise_error(Travis::API::V3::ClientError, 'Bad Request') diff --git a/spec/v3/services/custom_images/delete_spec.rb b/spec/v3/services/custom_images/delete_spec.rb index e21d6eea3..dda61cd52 100644 --- a/spec/v3/services/custom_images/delete_spec.rb +++ b/spec/v3/services/custom_images/delete_spec.rb @@ -15,11 +15,7 @@ context 'authenticated' do describe "deleting custom images by id list" do before do - stub_request(:delete, "#{Travis.config.artifact_manager.url}/images") - .with( - body: { image_ids: ['1', '2', '3'] }.to_json, - headers: { 'X-Travis-User-Id' => user.id.to_s } - ) + stub_request(:delete, %r{#{Travis.config.artifact_manager.url}/image/user/#{user.id}/\d+}) .to_return(status: 204, headers: { 'Content-Type' => 'application/json' }) end diff --git a/spec/v3/services/custom_images/for_owner_spec.rb b/spec/v3/services/custom_images/for_owner_spec.rb index e3687c624..36412debc 100644 --- a/spec/v3/services/custom_images/for_owner_spec.rb +++ b/spec/v3/services/custom_images/for_owner_spec.rb @@ -36,7 +36,7 @@ let(:authorization) { { 'permissions' => [ 'repository_state_update' ] } } it 'returns an empty list' do - get("/v3/owner/#{other_user.login}/custom_images", {}, json_headers.merge('HTTP_AUTHORIZATION' => "token #{other_user_token}")) + get("/v3/owner/#{user.login}/custom_images", {}, json_headers.merge('HTTP_AUTHORIZATION' => "token #{other_user_token}")) expect(JSON.parse(last_response.body)).to include( 'error_type' => 'insufficient_access' diff --git a/spec/v3/services/installation/find_spec.rb b/spec/v3/services/installation/find_spec.rb index 884e0db9f..1ebb672f3 100644 --- a/spec/v3/services/installation/find_spec.rb +++ b/spec/v3/services/installation/find_spec.rb @@ -61,6 +61,7 @@ "vcs_type" => user.vcs_type, "avatar_url" => "https://0.gravatar.com/avatar/07fb84848e68b96b69022d333ca8a3e2", "is_syncing" => nil, + "memberships" => [], "synced_at" => nil, "education" => nil, "allowance" => { diff --git a/spec/v3/services/owner/find_spec.rb b/spec/v3/services/owner/find_spec.rb index ade9a5861..e21785b1a 100644 --- a/spec/v3/services/owner/find_spec.rb +++ b/spec/v3/services/owner/find_spec.rb @@ -442,6 +442,7 @@ "vcs_type" => user.vcs_type, "avatar_url" => nil, "is_syncing" => nil, + "memberships" => [], "synced_at" => nil, "education" => nil, "allow_migration"=> false, @@ -480,6 +481,7 @@ "avatar_url" => nil, "education" => nil, "is_syncing" => nil, + "memberships" => [], "synced_at" => nil, "allow_migration"=> false, "allowance" => { @@ -517,6 +519,7 @@ "avatar_url" => nil, "education" => nil, "is_syncing" => nil, + "memberships" => [], "synced_at" => nil, "allow_migration" => false, "allowance" => { @@ -561,6 +564,7 @@ "avatar_url" => nil, "education" => nil, "is_syncing" => nil, + "memberships" => [], "synced_at" => nil, "allow_migration" => false, "allowance" => { diff --git a/spec/v3/services/v2_subscription/executions_spec.rb b/spec/v3/services/v2_subscription/executions_spec.rb index ef5ea0b84..532cec919 100644 --- a/spec/v3/services/v2_subscription/executions_spec.rb +++ b/spec/v3/services/v2_subscription/executions_spec.rb @@ -211,6 +211,7 @@ "account_env_vars" => [], "email"=>"sven@fuchs.com", "is_syncing"=>nil, + "memberships"=>[], "synced_at"=>nil, "recently_signed_up"=>false, "secure_user_hash"=>nil, @@ -271,6 +272,7 @@ "account_env_vars" => [], "email"=>"sven@fuchs.com", "is_syncing"=>nil, + "memberships"=>[], "synced_at"=>nil, "recently_signed_up"=>false, "secure_user_hash"=>nil, @@ -310,6 +312,7 @@ "account_env_vars" => [], "email"=>"sven@fuchs.com", "is_syncing"=>nil, + "memberships"=>[], "synced_at"=>nil, "recently_signed_up"=>false, "secure_user_hash"=>nil,