Files
ubicloud/model/github/github_repository.rb
Enes Cakir 1ce4e1930a Abort incomplete uploads before deleting the bucket
For performance reasons, we now create cache entries outside of
transactions, which may leave behind multipart uploads if the process is
interrupted.
ref: 9f2abaf176

While Cloudflare automatically aborts incomplete uploads after 24 hours
(and we typically rely on that), our E2E tests delete the bucket
immediately after the test completes. To prevent deletion failures in
tests, we now explicitly cancel any in-progress uploads before deleting
the bucket.
2025-06-18 10:19:00 +03:00

112 lines
3.9 KiB
Ruby

# frozen_string_literal: true
require "aws-sdk-s3"
require_relative "../../model"
class GithubRepository < Sequel::Model
one_to_one :strand, key: :id
many_to_one :installation, key: :installation_id, class: :GithubInstallation
one_to_many :runners, key: :repository_id, class: :GithubRunner
one_to_many :cache_entries, key: :repository_id, class: :GithubCacheEntry
plugin :association_dependencies, cache_entries: :destroy
plugin ResourceMethods
include SemaphoreMethods
semaphore :destroy
plugin :column_encryption do |enc|
enc.column :secret_key
end
CACHE_SIZE_LIMIT = 10 * 1024 * 1024 * 1024 # 10GB
alias_method :bucket_name, :ubid
def blob_storage_client
@blob_storage_client ||= s3_client(access_key, secret_key)
end
def url_presigner
@url_presigner ||= Aws::S3::Presigner.new(client: blob_storage_client)
end
def admin_client
@admin_client ||= s3_client(Config.github_cache_blob_storage_access_key, Config.github_cache_blob_storage_secret_key)
end
def destroy_blob_storage
# Abort any ongoing multipart uploads to ensure the bucket is empty before deleting it
blob_storage_client.list_multipart_uploads(bucket: bucket_name).uploads.each do
blob_storage_client.abort_multipart_upload(bucket: bucket_name, key: it.key, upload_id: it.upload_id)
end
begin
admin_client.delete_bucket(bucket: bucket_name)
rescue Aws::S3::Errors::NoSuchBucket
end
CloudflareClient.new(Config.github_cache_blob_storage_api_key).delete_token(access_key)
this.update(access_key: nil, secret_key: nil)
end
def setup_blob_storage
DB.transaction do
lock!
return if access_key && secret_key
begin
admin_client.create_bucket({
bucket: bucket_name,
create_bucket_configuration: {location_constraint: Config.github_cache_blob_storage_region}
})
rescue Aws::S3::Errors::BucketAlreadyOwnedByYou
end
policies = [
{
"effect" => "allow",
"permission_groups" => [{"id" => "2efd5506f9c8494dacb1fa10a3e7d5b6", "name" => "Workers R2 Storage Bucket Item Write"}],
"resources" => {"com.cloudflare.edge.r2.bucket.#{Config.github_cache_blob_storage_account_id}_default_#{bucket_name}" => "*"}
}
]
token_id, token = CloudflareClient.new(Config.github_cache_blob_storage_api_key).create_token("#{bucket_name}-token", policies)
update(access_key: token_id, secret_key: Digest::SHA256.hexdigest(token))
end
end
private def s3_client(access_key_id, secret_access_key)
Aws::S3::Client.new(
endpoint: Config.github_cache_blob_storage_endpoint,
access_key_id:,
secret_access_key:,
region: Config.github_cache_blob_storage_region,
request_checksum_calculation: "when_required",
response_checksum_validation: "when_required"
)
end
end
# Table: github_repository
# Columns:
# id | uuid | PRIMARY KEY
# installation_id | uuid |
# name | text | NOT NULL
# created_at | timestamp with time zone | NOT NULL DEFAULT CURRENT_TIMESTAMP
# last_job_at | timestamp with time zone | NOT NULL DEFAULT CURRENT_TIMESTAMP
# last_check_at | timestamp with time zone | NOT NULL DEFAULT CURRENT_TIMESTAMP
# default_branch | text |
# access_key | text |
# secret_key | text |
# Indexes:
# github_repository_pkey | PRIMARY KEY btree (id)
# github_repository_installation_id_name_index | UNIQUE btree (installation_id, name)
# Foreign key constraints:
# github_repository_installation_id_fkey | (installation_id) REFERENCES github_installation(id)
# Referenced By:
# github_cache_entry | github_cache_entry_repository_id_fkey | (repository_id) REFERENCES github_repository(id)
# github_runner | github_runner_repository_id_fkey | (repository_id) REFERENCES github_repository(id)