Files
ubicloud/routes/project/location/postgres.rb
Jeremy Evans e6b7e5e879 Change rubocop TargetRubyVersion to 3.4
Disable Style/RedundantLineContinuation, as it incorrectly removes
line continutations in rhizome/host/lib/vm_setup.rb that are not
redundant.

All code changes are for _1 => it in blocks.
2025-04-26 06:51:19 +09:00

324 lines
11 KiB
Ruby

# frozen_string_literal: true
class Clover
hash_branch(:project_location_prefix, "postgres") do |r|
r.get api? do
postgres_list
end
r.on POSTGRES_RESOURCE_NAME_OR_UBID do |pg_name, pg_ubid|
if pg_name
r.post api? do
check_visible_location
postgres_post(pg_name)
end
filter = {Sequel[:postgres_resource][:name] => pg_name}
else
filter = {Sequel[:postgres_resource][:id] => UBID.to_uuid(pg_ubid)}
end
filter[:location_id] = @location.id
pg = @project.postgres_resources_dataset.first(filter)
next (r.delete? ? 204 : 404) unless pg
r.get true do
authorize("Postgres:view", pg.id)
response.headers["Cache-Control"] = "no-store"
if api?
Serializers::Postgres.serialize(pg, {detailed: true})
else
@pg = Serializers::Postgres.serialize(pg, {detailed: true, include_path: true})
@family = Validation.validate_vm_size(pg.target_vm_size, "x64").family
@option_tree, @option_parents = generate_postgres_configure_options(flavor: @pg[:flavor], location: @location)
view "postgres/show"
end
end
r.delete true do
authorize("Postgres:delete", pg.id)
pg.incr_destroy
204
end
r.patch do
authorize("Postgres:edit", pg.id)
allowed_optional_parameters = ["size", "storage_size", "ha_type"]
ignored_parameters = ["family"]
request_body_params = validate_request_params([], allowed_optional_parameters, ignored_parameters)
target_vm_size = Validation.validate_postgres_size(pg.location, request_body_params["size"] || pg.target_vm_size, @project.id)
target_storage_size_gib = Validation.validate_postgres_storage_size(pg.location, target_vm_size.vm_size, request_body_params["storage_size"] || pg.target_storage_size_gib, @project.id)
ha_type = request_body_params["ha_type"] || PostgresResource::HaType::NONE
Validation.validate_postgres_ha_type(ha_type)
if pg.representative_server.nil? || target_storage_size_gib < pg.representative_server.storage_size_gib
begin
current_disk_usage = pg.representative_server.vm.sshable.cmd("df --output=used /dev/vdb | tail -n 1").strip.to_i / (1024 * 1024)
rescue
fail CloverError.new(400, "InvalidRequest", "Database is not ready for update", {})
end
if target_storage_size_gib * 0.8 < current_disk_usage
fail Validation::ValidationFailed.new({storage_size: "Insufficient storage size is requested. It is only possible to reduce the storage size if the current usage is less than 80% of the requested size."})
end
end
current_postgres_vcpu_count = (PostgresResource::TARGET_STANDBY_COUNT_MAP[pg.ha_type] + 1) * pg.representative_server.vm.vcpus
requested_postgres_vcpu_count = (PostgresResource::TARGET_STANDBY_COUNT_MAP[ha_type] + 1) * target_vm_size.vcpu
Validation.validate_vcpu_quota(@project, "PostgresVCpu", requested_postgres_vcpu_count - current_postgres_vcpu_count)
DB.transaction do
pg.update(target_vm_size: target_vm_size.vm_size, target_storage_size_gib:, ha_type:)
pg.read_replicas.map { it.update(target_vm_size: target_vm_size.vm_size, target_storage_size_gib:) }
end
if api?
Serializers::Postgres.serialize(pg, {detailed: true})
else
flash["notice"] = "'#{pg.name}' will be updated according to requested configuration"
response["Location"] = "#{@project.path}#{pg.path}"
200
end
end
r.post "restart" do
authorize("Postgres:edit", pg.id)
pg.servers.each do |s|
s.incr_restart
rescue Sequel::ForeignKeyConstraintViolation
end
if api?
Serializers::Postgres.serialize(pg, {detailed: true})
else
flash["notice"] = "'#{pg.name}' will be restarted in a few seconds"
r.redirect "#{@project.path}#{pg.path}"
end
end
r.on "firewall-rule" do
r.get api?, true do
authorize("Postgres:view", pg.id)
{
items: Serializers::PostgresFirewallRule.serialize(pg.firewall_rules),
count: pg.firewall_rules.count
}
end
r.post true do
authorize("Postgres:edit", pg.id)
required_parameters = ["cidr"]
request_body_params = validate_request_params(required_parameters)
parsed_cidr = Validation.validate_cidr(request_body_params["cidr"])
firewall_rule = DB.transaction do
pg.incr_update_firewall_rules
PostgresFirewallRule.create_with_id(
postgres_resource_id: pg.id,
cidr: parsed_cidr.to_s
)
end
if api?
Serializers::PostgresFirewallRule.serialize(firewall_rule)
else
flash["notice"] = "Firewall rule is created"
r.redirect "#{@project.path}#{pg.path}"
end
end
r.delete String do |firewall_rule_ubid|
authorize("Postgres:edit", pg.id)
if (fwr = PostgresFirewallRule.from_ubid(firewall_rule_ubid))
DB.transaction do
fwr.destroy
pg.incr_update_firewall_rules
end
end
204
end
end
r.on "metric-destination" do
r.post true do
authorize("Postgres:edit", pg.id)
password_param = (api? ? "password" : "metric-destination-password")
required_parameters = ["url", "username", password_param]
request_body_params = validate_request_params(required_parameters)
Validation.validate_url(request_body_params["url"])
DB.transaction do
PostgresMetricDestination.create_with_id(
postgres_resource_id: pg.id,
url: request_body_params["url"],
username: request_body_params["username"],
password: request_body_params[password_param]
)
pg.servers.each(&:incr_configure_prometheus)
end
if api?
Serializers::Postgres.serialize(pg, {detailed: true})
else
flash["notice"] = "Metric destination is created"
r.redirect "#{@project.path}#{pg.path}"
end
end
r.delete String do |metric_destination_ubid|
authorize("Postgres:edit", pg.id)
if (md = PostgresMetricDestination.from_ubid(metric_destination_ubid))
DB.transaction do
md.destroy
pg.servers.each(&:incr_configure_prometheus)
end
end
204
end
end
r.on "read-replica" do
r.post true do
required_parameters = ["name"]
request_body_params = validate_request_params(required_parameters, [], [])
st = Prog::Postgres::PostgresResourceNexus.assemble(
project_id: @project.id,
location_id: pg.location_id,
name: request_body_params["name"],
target_vm_size: pg.target_vm_size,
target_storage_size_gib: pg.target_storage_size_gib,
ha_type: PostgresResource::HaType::NONE,
version: pg.version,
flavor: pg.flavor,
parent_id: pg.id,
restore_target: nil
)
send_notification_mail_to_partners(st.subject, current_account.email)
if api?
Serializers::Postgres.serialize(st.subject, {detailed: true})
else
flash["notice"] = "'#{request_body_params["name"]}' will be ready in a few minutes"
r.redirect "#{@project.path}#{st.subject.path}"
end
end
end
r.post "promote" do
authorize("Postgres:edit", pg.id)
unless pg.read_replica?
error_msg = "Non read replica servers cannot be promoted."
if api?
fail CloverError.new(400, "InvalidRequest", error_msg)
else
flash["error"] = error_msg
redirect_back_with_inputs
end
end
pg.incr_promote
if api?
Serializers::Postgres.serialize(pg)
else
flash["notice"] = "'#{pg.name}' will be promoted in a few minutes, please refresh the page"
r.redirect "#{@project.path}#{pg.path}"
end
end
r.post "restore" do
authorize("Postgres:create", @project.id)
authorize("Postgres:view", pg.id)
required_parameters = ["name", "restore_target"]
request_body_params = validate_request_params(required_parameters)
st = Prog::Postgres::PostgresResourceNexus.assemble(
project_id: @project.id,
location_id: pg.location_id,
name: request_body_params["name"],
target_vm_size: pg.target_vm_size,
target_storage_size_gib: pg.target_storage_size_gib,
version: pg.version,
flavor: pg.flavor,
parent_id: pg.id,
restore_target: request_body_params["restore_target"]
)
send_notification_mail_to_partners(st.subject, current_account.email)
if api?
Serializers::Postgres.serialize(st.subject, {detailed: true})
else
flash["notice"] = "'#{request_body_params["name"]}' will be ready in a few minutes"
r.redirect "#{@project.path}#{st.subject.path}"
end
end
r.post "reset-superuser-password" do
authorize("Postgres:view", pg.id)
unless pg.representative_server.primary?
if api?
fail CloverError.new(400, "InvalidRequest", "Superuser password cannot be updated during restore!")
else
flash["error"] = "Superuser password cannot be updated during restore!"
redirect_back_with_inputs
end
end
required_parameters = api? ? ["password"] : ["password", "repeat_password"]
request_body_params = validate_request_params(required_parameters)
Validation.validate_postgres_superuser_password(request_body_params["password"], request_body_params["repeat_password"])
DB.transaction do
pg.update(superuser_password: request_body_params["password"])
pg.representative_server.incr_update_superuser_password
end
if api?
Serializers::Postgres.serialize(pg, {detailed: true})
else
flash["notice"] = "The superuser password will be updated in a few seconds"
r.redirect "#{@project.path}#{pg.path}"
end
end
r.post "set-maintenance-window" do
authorize("Postgres:edit", pg.id)
allowed_optional_parameters = ["maintenance_window_start_at"]
request_body_params = validate_request_params([], allowed_optional_parameters)
pg.update(maintenance_window_start_at: request_body_params["maintenance_window_start_at"])
if api?
Serializers::Postgres.serialize(pg, {detailed: true})
else
flash["notice"] = "Maintenance window is set"
r.redirect "#{@project.path}#{pg.path}"
end
end
r.get "ca-certificates" do
authorize("Postgres:view", pg.id)
return 404 unless (certs = pg.ca_certificates)
response.headers["Content-Disposition"] = "attachment; filename=\"#{pg.name}.pem\""
response.headers["Content-Type"] = "application/x-pem-file"
certs
end
end
end
end