module Seira class Db class Create include Seira::Commands attr_reader :app, :action, :args, :context attr_reader :name, :version, :cpu, :memory, :storage, :replica_for, :make_highly_available attr_reader :root_password, :proxyuser_password def initialize(app:, action:, args:, context:) @app = app @action = action @args = args @context = context # We allow overriding the version, so you could specify a mysql version but much of the # below assumes postgres for now @version = 'POSTGRES_9_6' @cpu = 1 # Number of CPUs @memory = 4 # GB @storage = 10 # GB @replica_for = nil @make_highly_available = false @root_password = nil @proxyuser_password = nil end def run(existing_instances) @name = "#{app}-#{Seira::Random.unique_name(existing_instances)}" run_create_command if replica_for.nil? update_root_password create_proxy_user end set_secrets write_pgbouncer_yaml puts "To use this database, deploy the pgbouncer config file that was created and use the ENV that was set." puts "To make this database the primary, promote it using the CLI and update the DATABASE_URL." end private def run_create_command # The 'beta' is needed for HA and other beta features create_command = "beta sql instances create #{name}" args.each do |arg| if arg.start_with? '--version=' @version = arg.split('=')[1] elsif arg.start_with? '--cpu=' @cpu = arg.split('=')[1] elsif arg.start_with? '--memory=' @memory = arg.split('=')[1] elsif arg.start_with? '--storage=' @storage = arg.split('=')[1] elsif arg.start_with? '--primary=' @replica_for = arg.split('=')[1] # TODO: Read secret to get it automatically, but allow for fallback elsif arg.start_with? '--highly-available' @make_highly_available = true elsif arg.start_with? '--database-name=' @database_name = arg.split('=')[1] elsif /^--[\w\-]+=.+$/.match? arg create_command += " #{arg}" else puts "Warning: Unrecognized argument '#{arg}'" end end if make_highly_available && !replica_for.nil? puts "Cannot create an HA read-replica." exit(1) end # Basic configs create_command += " --database-version=#{version}" # A read replica cannot have HA, inherits the cpu, mem and storage of its primary if replica_for.nil? # Make sure to do automated daily backups by default, unless it's a replica create_command += " --backup" create_command += " --cpu=#{cpu}" create_command += " --memory=#{memory}" create_command += " --storage-size=#{storage}" # Make HA if asked for create_command += " --availability-type=REGIONAL" if make_highly_available else create_command += " --master-instance-name=#{replica_for}" # We don't need to wait for it to finish to move ahead if it's a replica, as we don't # make any changes to the database itself create_command += " --async" end # Create the sql instance with the specified/default parameters if gcloud(create_command, context: context, format: :boolean) async_additional = unless replica_for.nil? ". Database is still being created and may take some time to be available." end puts "Successfully created sql instance #{name}#{async_additional}" else puts "Failed to create sql instance" exit(1) end end def update_root_password # Set the root user's password to something secure @root_password = SecureRandom.urlsafe_base64(32) if gcloud("sql users set-password postgres '' --instance=#{name} --password=#{root_password}", context: context, format: :boolean) puts "Set root password to #{root_password}" else puts "Failed to set root password" exit(1) end end def create_proxy_user # Create proxyuser with secure password @proxyuser_password = SecureRandom.urlsafe_base64(32) if gcloud("sql users create proxyuser '' --instance=#{name} --password=#{proxyuser_password}", context: context, format: :boolean) puts "Created proxyuser with password #{proxyuser_password}" else puts "Failed to create proxyuser" exit(1) end # Connect to the instance and remove some of the default group memberships and permissions # from proxyuser, leaving it with only what it needs to be able to do expect_script = <<~BASH set timeout 90 spawn gcloud sql connect #{name} expect "Password for user postgres:" send "#{root_password}\\r" expect "postgres=>" send "ALTER ROLE proxyuser NOCREATEDB NOCREATEROLE;\\r" expect "postgres=>" BASH if system("expect <