mirror of
https://github.com/ubicloud/ubicloud.git
synced 2025-10-06 06:41:57 +08:00
For the cli, I choose to use a separate option for this. While it currently wouldn't be ambiguous, as soon as we restrict private subnet connection to private subnets in the same location, users will be able to provide a private subnet name without a location, and a postgres resource ubid is an allowable private subnet name.
168 lines
6.8 KiB
Ruby
168 lines
6.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class Clover
|
|
def authorized_postgres_resource(perm: "Postgres:view", location_id: nil, key: "postgres_resource_id", id: nil)
|
|
authorized_object(association: :postgres_resources, key:, perm:, location_id:, id:)
|
|
end
|
|
|
|
def postgres_post(name)
|
|
authorize("Postgres:create", @project.id)
|
|
fail Validation::ValidationFailed.new({billing_info: "Project doesn't have valid billing information"}) unless @project.has_valid_payment_method?
|
|
|
|
flavor = typecast_params.nonempty_str("flavor", PostgresResource::Flavor::STANDARD)
|
|
size = typecast_params.nonempty_str!("size")
|
|
storage_size = typecast_params.pos_int("storage_size")
|
|
ha_type = typecast_params.nonempty_str("ha_type", PostgresResource::HaType::NONE)
|
|
version = typecast_params.nonempty_str("version", PostgresResource::DEFAULT_VERSION)
|
|
tags = typecast_params.array(:Hash, "tags", [])
|
|
with_firewall_rules = !typecast_params.bool("restrict_by_default")
|
|
|
|
postgres_params = {
|
|
"flavor" => flavor,
|
|
"location" => @location,
|
|
"family" => Option::POSTGRES_SIZE_OPTIONS[size]&.family,
|
|
"size" => size,
|
|
"storage_size" => storage_size.to_s,
|
|
"ha_type" => ha_type,
|
|
"version" => version
|
|
}
|
|
|
|
validate_postgres_input(name, postgres_params)
|
|
|
|
parsed_size = Option::POSTGRES_SIZE_OPTIONS[postgres_params["size"]]
|
|
requested_standby_count = Option::POSTGRES_HA_OPTIONS[postgres_params["ha_type"]].standby_count
|
|
requested_postgres_vcpu_count = (requested_standby_count + 1) * parsed_size.vcpu_count
|
|
Validation.validate_vcpu_quota(@project, "PostgresVCpu", requested_postgres_vcpu_count)
|
|
|
|
if typecast_params.ubid_uuid("connect_to_subnet_id")
|
|
unless (connect_to_subnet = authorized_private_subnet(key: "connect_to_subnet_id", location_id: @location.id, perm: "PrivateSubnet:connect"))
|
|
raise CloverError.new(400, "InvalidRequest", "Subnet to be connected not found")
|
|
end
|
|
end
|
|
|
|
pg = nil
|
|
DB.transaction do
|
|
pg = Prog::Postgres::PostgresResourceNexus.assemble(
|
|
project_id: @project.id,
|
|
location_id: @location.id,
|
|
name:,
|
|
target_vm_size: parsed_size.name,
|
|
target_storage_size_gib: storage_size,
|
|
ha_type:,
|
|
version:,
|
|
with_firewall_rules:,
|
|
connect_to_subnet:,
|
|
flavor:
|
|
).subject
|
|
pg.update(tags:)
|
|
audit_log(pg, "create")
|
|
end
|
|
send_notification_mail_to_partners(pg, current_account.email)
|
|
|
|
if api?
|
|
Serializers::Postgres.serialize(pg, {detailed: true})
|
|
else
|
|
flash["notice"] = "'#{name}' will be ready in a few minutes"
|
|
request.redirect pg, "/overview"
|
|
end
|
|
end
|
|
|
|
def postgres_list
|
|
dataset = dataset_authorize(@project.postgres_resources_dataset.eager, "Postgres:view").eager(:semaphores, :location, strand: :children)
|
|
if api?
|
|
dataset = dataset.where(location_id: @location.id) if @location
|
|
paginated_result(dataset, Serializers::Postgres)
|
|
else
|
|
@postgres_databases = dataset.eager(:representative_server, :timeline).all
|
|
.group_by { |r| r.read_replica? ? r[:parent_id] : r[:id] }
|
|
.flat_map { |group_id, rs| rs.sort_by { |r| r[:created_at] } }
|
|
view "postgres/index"
|
|
end
|
|
end
|
|
|
|
def send_notification_mail_to_partners(resource, user_email)
|
|
if [PostgresResource::Flavor::PARADEDB, PostgresResource::Flavor::LANTERN].include?(resource.flavor) && (email = Config.send(:"postgres_#{resource.flavor}_notification_email"))
|
|
flavor_name = resource.flavor.capitalize
|
|
Util.send_email(email, "New #{flavor_name} Postgres database has been created.",
|
|
greeting: "Hello #{flavor_name} team,",
|
|
body: ["New #{flavor_name} Postgres database has been created.",
|
|
"ID: #{resource.ubid}",
|
|
"Location: #{resource.location.display_name}",
|
|
"Name: #{resource.name}",
|
|
"E-mail: #{user_email}",
|
|
"Instance VM Size: #{resource.target_vm_size}",
|
|
"Instance Storage Size: #{resource.target_storage_size_gib}",
|
|
"HA: #{resource.ha_type}"])
|
|
end
|
|
end
|
|
|
|
def generate_postgres_options(flavor: nil, location: nil, connectable_subnets: nil)
|
|
options = OptionTreeGenerator.new
|
|
|
|
options.add_option(name: "name")
|
|
|
|
options.add_option(name: "flavor", values: flavor || postgres_flavors.keys)
|
|
|
|
options.add_option(name: "location", values: location || postgres_locations, parent: "flavor") do |flavor, location|
|
|
flavor == PostgresResource::Flavor::STANDARD || location.provider != "aws"
|
|
end
|
|
|
|
options.add_option(name: "family", values: Option::POSTGRES_FAMILY_OPTIONS.keys, parent: "location") do |flavor, location, family|
|
|
if location.aws?
|
|
family == "m6id" || (Option::AWS_FAMILY_OPTIONS.include?(family) && @project.send(:"get_ff_enable_#{family}"))
|
|
else
|
|
family == "standard" || family == "burstable"
|
|
end
|
|
end
|
|
|
|
options.add_option(name: "size", values: Option::POSTGRES_SIZE_OPTIONS.keys, parent: "family") do |flavor, location, family, size|
|
|
Option::POSTGRES_SIZE_OPTIONS[size].family == family
|
|
end
|
|
|
|
storage_size_options = Option::POSTGRES_STORAGE_SIZE_OPTIONS + Option::AWS_STORAGE_SIZE_OPTIONS.values.flat_map { |h| h.values.flatten }.uniq
|
|
options.add_option(name: "storage_size", values: storage_size_options, parent: "size") do |flavor, location, family, size, storage_size|
|
|
vcpu_count = Option::POSTGRES_SIZE_OPTIONS[size].vcpu_count
|
|
|
|
if location.aws?
|
|
Option::AWS_STORAGE_SIZE_OPTIONS[family][vcpu_count].include?(storage_size)
|
|
else
|
|
min_storage = (vcpu_count >= 30) ? 1024 : vcpu_count * 32
|
|
min_storage /= 2 if family == "burstable"
|
|
[min_storage, min_storage * 2, min_storage * 4].include?(storage_size.to_i)
|
|
end
|
|
end
|
|
|
|
options.add_option(name: "version", values: Option::POSTGRES_VERSION_OPTIONS)
|
|
|
|
options.add_option(name: "ha_type", values: Option::POSTGRES_HA_OPTIONS.keys, parent: "storage_size")
|
|
|
|
if connectable_subnets&.any?
|
|
options.add_option(name: "connect_to_subnet_id", values: connectable_subnets, parent: "location") do |flavor, location, private_subnet|
|
|
private_subnet[:location_id] == location.id
|
|
end
|
|
end
|
|
|
|
options.serialize
|
|
end
|
|
|
|
def postgres_flavors
|
|
Option::POSTGRES_FLAVOR_OPTIONS.reject { |k, _| k == PostgresResource::Flavor::LANTERN && !@project.get_ff_postgres_lantern }
|
|
end
|
|
|
|
def postgres_locations
|
|
Location.where(name: ["hetzner-fsn1", "leaseweb-wdc02"]).all + @project.locations
|
|
end
|
|
|
|
def validate_postgres_input(name, postgres_params)
|
|
Validation.validate_name(name)
|
|
|
|
option_tree, option_parents = generate_postgres_options
|
|
|
|
begin
|
|
Validation.validate_from_option_tree(option_tree, option_parents, postgres_params)
|
|
rescue Validation::ValidationFailed => e
|
|
fail Validation::ValidationFailed.new({size: "Invalid size."}) if e.details.key?(:family)
|
|
raise e
|
|
end
|
|
end
|
|
end
|