diff --git a/db/Provision/Service/db/pgsql.php b/db/Provision/Service/db/pgsql.php new file mode 100644 index 0000000..7f3909c --- /dev/null +++ b/db/Provision/Service/db/pgsql.php @@ -0,0 +1,247 @@ +query("DROP DATABASE %s", $name); + } + + function create_database($name) { + return $this->query("CREATE DATABASE %s", $name); + } + + function create_user($name, $password) { + return $this->query("CREATE USER %s WITH PASSWORD '%s'", $name, $password); + } + + function drop_user($name) { + return $this->query("DROP USER %s", $name); + } + + /** + * Generate a new pgsql database and user account for the specified credentials + */ + function create_site_database($creds = array()) { + if (!sizeof($creds)) { + $creds = $this->generate_site_credentials(); + } + extract($creds); + + if (!$this->can_create_database()) { + drush_set_error('PROVISION_CREATE_DB_FAILED'); + drush_log("Database could not be created.", 'error'); + return FALSE; + } + + $this->create_database($db_name); + + foreach ($this->grant_host_list() as $db_grant_host) { + drush_log(dt("Granting privileges to %user@%client on %database", array('%user' => $db_user, '%client' => $db_grant_host, '%database' => $db_name))); + $this->create_user($db_user, $db_passwd); + if (!$this->grant($db_name, $db_user, $db_passwd, $db_grant_host)) { + drush_set_error('PROVISION_CREATE_DB_FAILED', dt("Could not create database user @user", array('@user' => $db_user))); + } + } + + $status = $this->database_exists($db_name); + + if ($status) { + drush_log(dt('Created @name database', array("@name" => $db_name)), 'success'); + } + else { + drush_set_error('PROVISION_CREATE_DB_FAILED', dt("Could not create @name database", array("@name" => $db_name))); + } + return $status; + } + + function generate_site_credentials() { + $creds = array(); + // replace with service type + $db_type = drush_get_option('db_type', 'pgsql'); + // As of Drupal 7 there is no more mysqli type + if (drush_drupal_major_version() >= 7) { + $db_type = ($db_type == 'mysqli') ? 'mysql' : $db_type; + } + + //TODO - this should not be here at all + $creds['db_type'] = drush_set_option('db_type', $db_type, 'site'); + $creds['db_host'] = drush_set_option('db_host', $this->server->remote_host, 'site'); + $creds['db_port'] = drush_set_option('db_port', $this->server->db_port, 'site'); + $creds['db_passwd'] = drush_set_option('db_passwd', provision_password(), 'site'); + $creds['db_name'] = drush_set_option('db_name', $this->suggest_db_name(), 'site'); + $creds['db_user'] = drush_set_option('db_user', $creds['db_name'], 'site'); + + return $creds; + } + + function can_create_database() { + $test = drush_get_option('aegir_db_prefix', 'site_') . 'test'; + $this->create_database($test); + + if ($this->database_exists($test)) { + if (!$this->drop_database($test)) { + drush_log(dt("Failed to drop database @dbname", array('@dbname' => $test)), 'warning'); + } + return TRUE; + } + return FALSE; + } + + /** + * Verifies that provision can grant privileges to a user on a database. + * + * @return + * TRUE if the check was successful. + */ + function can_grant_privileges() { + $dbname = drush_get_option('aegir_db_prefix', 'site_'); + $user = $dbname . '_user'; + $password = $dbname . '_password'; + $host = $dbname . '_host'; + $this->create_database($dbname); + if (!$this->create_user($user, $password)) { + drush_log(dt("Failed to create user @username", array('@username' => $user)), 'warning'); + return FALSE; + } + if ($status = $this->grant($dbname, $user, $password, $host)) { + $this->revoke($dbname, $user, $host); + } + $this->drop_database($dbname); + $this->drop_user($user); + return $status; + } + + function grant($name, $username, $password, $host = '') { + return $this->query("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", $name, $username); + } + + function revoke($name, $username, $host = '') { + $host = ($host) ? $host : '%'; + $success = $this->query("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s", $name, $username); + + // check if there are any privileges left for the user + $grants = $this->query("SELECT * FROM has_database_privilege('%s', '%s', 'create')", $username, $name); + $grant_found = FALSE; + if ($grants) { + while ($grant = $grants->fetch()) { + // those are empty grants: just the user line + if ($grant['has_database_privilege'] == TRUE) { + $grant_found = TRUE; + } + } + } + if (!$grant_found) { + $success = $this->query("DROP USER %s", $username) && $success; + } + return $success; + } + + + function import_dump($dump_file, $creds) { + extract($creds); + + $cmd = sprintf("psql --defaults-file=/dev/fd/3 %s", escapeshellcmd($db_name)); + + $success = $this->safe_shell_exec($cmd, $db_host, $db_user, $db_passwd, $dump_file); + + drush_log(sprintf("Importing database using command: %s", $cmd)); + + if (!$success) { + drush_set_error('PROVISION_DB_IMPORT_FAILED', dt("Database import failed: %output", array('%output' => $this->safe_shell_exec_output))); + } + } + + function grant_host(Provision_Context_server $server) { + $command = sprintf('psql -U intntnllyInvalid -h %s -p %s -w -e "SELECT VERSION()"', + escapeshellarg($this->server->remote_host), + escapeshellarg($this->server->db_port)); + + + $server->shell_exec($command); + if (preg_match("/psql: fe_sendauth: no password supplied/", implode('', drush_shell_exec_output()), $match)) { + return $this->server->remote_host; + } + else { + return drush_set_error('PROVISION_DB_CONNECT_FAIL', dt('Dummy connection failed to fail. Either your PostgreSQL permissions are too lax, or the response was not understood. See http://is.gd/Y6i4FO for more information. %msg', array('%msg' => join("\n", drush_shell_exec_output())))); + } + } + + function generate_dump() { + // Aet the umask to 077 so that the dump itself is generated so it's + // non-readable by the webserver. + umask(0077); + // Mixed copy-paste of drush_shell_exec and provision_shell_exec. + $cmd = sprintf("mysqldump --defaults-file=/dev/fd/3 --single-transaction --quick %s | sed 's|/\\*!50001 CREATE ALGORITHM=UNDEFINED \\*/|/\\*!50001 CREATE \\*/|g; s|/\\*!50017 DEFINER=`[^`]*`@`[^`]*`\s*\\*/||g' | sed '/\\*!50013 DEFINER=.*/ d' > %s/database.sql", escapeshellcmd(drush_get_option('db_name')), escapeshellcmd(d()->site_path)); + $success = $this->safe_shell_exec($cmd, drush_get_option('db_host'), urldecode(drush_get_option('db_user')), urldecode(drush_get_option('db_passwd'))); + + $dump_size_too_small = filesize(d()->site_path . '/database.sql') < 1024; + if ((!$success || $dump_size_too_small) && !drush_get_option('force', FALSE)) { + drush_set_error('PROVISION_BACKUP_FAILED', dt('Could not generate database backup from mysqldump. (error: %msg)', array('%msg' => $this->safe_shell_exec_output))); + } + // Reset the umask to normal permissions. + umask(0022); + } + + /** + * We go through all this trouble to hide the password from the commandline, + * it's the most secure way (apart from writing a temporary file, which would + * create conflicts in parallel runs) + * + * XXX: this needs to be refactored so it: + * - works even if /dev/fd/3 doesn't exit + * - has a meaningful name (we're talking about reading and writing + * dumps here, really, or at least call mysql and mysqldump, not + * just any command) + * - can be pushed upstream to drush (http://drupal.org/node/671906) + */ + function safe_shell_exec($cmd, $db_host, $db_user, $db_passwd, $dump_file = NULL) { + $mycnf = sprintf('[client] +host=%s +user=%s +password="%s" +port=%s +', $db_host, $db_user, $db_passwd, $this->server->db_port); + + $stdin_spec = (!is_null($dump_file)) ? array("file", $dump_file, "r") : array("pipe", "r"); + + $descriptorspec = array( + 0 => $stdin_spec, + 1 => array("pipe", "w"), // stdout is a pipe that the child will write to + 2 => array("pipe", "w"), // stderr is a file to write to + 3 => array("pipe", "r"), // fd3 is our special file descriptor where we pass credentials + ); + $pipes = array(); + $process = proc_open($cmd, $descriptorspec, $pipes); + $this->safe_shell_exec_output = ''; + if (is_resource($process)) { + fwrite($pipes[3], $mycnf); + fclose($pipes[3]); + + $this->safe_shell_exec_output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]); + // "It is important that you close any pipes before calling + // proc_close in order to avoid a deadlock" + fclose($pipes[1]); + fclose($pipes[2]); + $return_value = proc_close($process); + } + else { + // XXX: failed to execute? unsure when this happens + $return_value = -1; + } + return ($return_value == 0); + } +}