Short description: * Need: migration of database, with roles relevant to it. * "From" server: pg 8.1; no superuser access. * "To" server: pg 8.4; full access. * Database in question: depends on few roles, mainly group roles used to set permission; there are other roles (login roles) which are members of these and are intended for the users of the system. * Proposed solution: migrate the database and reset users passwords. Long description: I've been searching a way to achieve this. Having not found it, I proposed the following solution: 1) Get the roles which are explicitly part of the database through ownership or acl. 2) Get the roles which descend into the membership graph (members of roles in (1) plus the members of these and so on). 3) Dump them all (which includes the statements "CREATE ROLE", "ALTER ROLE" and "GRANT"'s referred to role membership). Why not include the upper part of the graph (parent roles)? well, unless any of these are superusers any other permission is irrelevant for the purpose of that single database. In fact, that's a TODO which is not needed for my particular case. I wrote two functions for this: the first one is a privileged one and the second is not. I did the second because I can't access to the passwords of the users in the server I'm migrating and asking the admins to do this is such a pain. After the restoration I will reset the application users' passwords and securely transmit them to them. Additionally, the functions can dump the roles of any number of databases in the cluster. In the attachment you'll find two queries which are later combined and slightly modified to compose the functions. One of the queries is a "WITH RECURSIVE ..." query, so the PostgreSQL version should be 8.3 or above I guess. Given the case that the cluster which I'm migrating from is 8.1, I had to take a workaround in order for this to work: 1) Dump the needed catalog tables: pg_auth_members, pg_authid for privileged role OR pg_roles for unprivileged role (which is in fact a view), pg_database and pg_shdepend. 2) Restore the tables in some user-schema of some database in the 8.4 cluster. 3) Modify the functions to query the specified schema instead of pg_catalog. I provide this code in the hope that it will be useful to someone, but I also expect some comments and/or corrections. NOTICE: the privileged function was already successfully tested in a production environment between two 8.4 servers. -- Diego Augusto Molina diegoaugustomolina@xxxxxxxxx ES: Por favor, evite adjuntar documentos de Microsoft Office. Serán desestimados. EN: Please, avoid attaching Microsoft Office documents. They shall be discarded. LINK: http://www.gnu.org/philosophy/no-word-attachments.html
-- ES: Consulta que lista los oid de los roles de los cuales depende la base de datos, así como los roles que dependen de éstos primeros. -- EN: Query that lists the oids of the roles in which the database deppends on as well as those roles which descend from them. WITH RECURSIVE deps(rol) AS ( SELECT DISTINCT roles.oid FROM pg_catalog.pg_database db INNER JOIN pg_catalog.pg_shdepend dep ON (db.oid = dep.dbid) INNER JOIN pg_catalog.pg_roles roles ON (dep.refobjid = roles.oid) WHERE dep.refclassid = 'pg_catalog.pg_authid'::REGCLASS AND ARRAY[db.datname] <@ $1 UNION ALL SELECT am.member AS rol FROM pg_catalog.pg_auth_members am INNER JOIN deps ON (am.roleid = deps.rol) ) SELECT * FROM deps -- ES: La siguiente consulta genera el dump de los roles. La columna 'orden' establece el orden en que deben ejecutarse las sentencias. -- EN: This query generates the dump of the roles. The column 'orden' sets the order in which the sentencies are to be executed. SELECT 1::SMALLINT AS orden, 'CREATE ROLE "' || rolname || '" ' CASE WHEN NOT rolsuper THEN 'NO' ELSE '' END || 'SUPERUSER ' || -- 'NOSUPERUSER ' || CASE WHEN NOT rolinherit THEN 'NO' ELSE '' END || 'INHERIT ' || CASE WHEN NOT rolcreaterole THEN 'NO' ELSE '' END || 'CREATEROLE ' || -- 'NOCREATEROLE ' || CASE WHEN NOT rolcreatedb THEN 'NO' ELSE '' END || 'CREATEDB ' || -- 'NOCREATEDB ' || CASE WHEN NOT rolcanlogin THEN 'NO' ELSE '' END || 'LOGIN ' || 'CONNECTION LIMIT ' || rolconnlimit || CASE WHEN rolvaliduntil IS NULL THEN '' ELSE E' VALID UNTIL \'' || rolvaliduntil::text || E'\'' END || ';' AS sentencia FROM pg_catalog.pg_roles --WHERE ARRAY[oid] <@ $1 --WHERE oid IN (<LISTA_OID_ROLES>) UNION SELECT 2::SMALLINT AS orden, 'ALTER ROLE "' || rolname || '" SET ' || array_to_string( rolconfig, ';ALTER ROLE "' || rolname || '" SET ' ) || ';' AS sentencia FROM pg_catalog.pg_roles WHERE rolconfig IS NOT NULL --AND ARRAY[oid] <@ $1 --AND oid IN (<LISTA_OID_ROLES>) UNION SELECT 2::SMALLINT AS orden, 'GRANT "' || rol.rolname || '" TO "' || member.rolname || '"' || CASE WHEN am.admin_option THEN 'WITH ADMIN OPTION' ELSE '' END || ';' AS sentencia FROM pg_catalog.pg_auth_members am INNER JOIN pg_catalog.pg_roles rol ON (am.roleid = rol.oid) INNER JOIN pg_catalog.pg_roles member ON (am.member = member.oid) --WHERE rol.oid IN (<LISTA_OID_ROLES>) ORDER BY orden, sentencia ASC; -- ES: La siguiente función combina las dos consultas anteriores, y además agrega la contraseña (debe ser ejecutado con un superusuario) -- EN: This function combines the above queries and adds the password. Requires superuser privileges. CREATE OR REPLACE FUNCTION dump_db_roles(VARIADIC name[]) RETURNS TABLE(orden smallint, sentencia text) AS $BODY$ WITH RECURSIVE deps(rol) AS ( SELECT DISTINCT roles.oid FROM pg_catalog.pg_database db INNER JOIN pg_catalog.pg_shdepend dep ON (db.oid = dep.dbid) INNER JOIN pg_catalog.pg_authid roles ON (dep.refobjid = roles.oid) WHERE dep.refclassid = 'pg_catalog.pg_authid'::REGCLASS AND ARRAY[db.datname] <@ $1 UNION ALL SELECT am.member AS rol FROM pg_catalog.pg_auth_members am INNER JOIN deps ON (am.roleid = deps.rol) ) SELECT 1::SMALLINT AS orden, 'CREATE ROLE "' || r.rolname || '" WITH ' || CASE WHEN r.rolpassword IS NOT NULL THEN E'ENCRYPTED PASSWORD \'' || r.rolpassword || E'\'' ELSE '' END || CASE WHEN NOT r.rolsuper THEN 'NO' ELSE '' END || 'SUPERUSER ' || CASE WHEN NOT r.rolinherit THEN 'NO' ELSE '' END || 'INHERIT ' || CASE WHEN NOT r.rolcreaterole THEN 'NO' ELSE '' END || 'CREATEROLE ' || CASE WHEN NOT r.rolcreatedb THEN 'NO' ELSE '' END || 'CREATEDB ' || CASE WHEN NOT r.rolcanlogin THEN 'NO' ELSE '' END || 'LOGIN ' || 'CONNECTION LIMIT ' || r.rolconnlimit || CASE WHEN r.rolvaliduntil IS NULL THEN '' ELSE E' VALID UNTIL \'' || rolvaliduntil::text || E'\'' END || ';' AS sentencia FROM pg_catalog.pg_authid r INNER JOIN deps on (r.oid = deps.rol) UNION SELECT 2::SMALLINT AS orden, 'ALTER ROLE "' || r.rolname || '" SET ' || array_to_string( r.rolconfig, ';ALTER ROLE "' || r.rolname || '" SET ' ) || ';' AS sentencia FROM pg_catalog.pg_authid r INNER JOIN deps on (r.oid = deps.rol) WHERE rolconfig IS NOT NULL UNION SELECT 2::SMALLINT AS orden, 'GRANT "' || rol.rolname || '" TO "' || member.rolname || '"' || CASE WHEN am.admin_option THEN 'WITH ADMIN OPTION' ELSE '' END || ';' AS sentencia FROM pg_catalog.pg_auth_members am INNER JOIN pg_catalog.pg_authid rol ON (am.roleid = rol.oid) INNER JOIN pg_catalog.pg_authid member ON (am.member = member.oid) INNER JOIN deps on (rol.oid = deps.rol) ORDER BY orden, sentencia ASC; $BODY$ LANGUAGE sql VOLATILE COST 100 ROWS 1000; ALTER FUNCTION dump_db_roles(name[]) OWNER TO postgres; -- ES: La siguiente función es igual que la anterior, pero no muestra las contraseñas y puede ser ejecutado por cualquier rol. -- EN: This function is the same as the previous one except that it doesn't show the passwords and thus can be executed by any role. CREATE OR REPLACE FUNCTION unpriv_dump_db_roles(VARIADIC name[]) RETURNS TABLE(orden smallint, sentencia text) AS $BODY$ WITH RECURSIVE deps(rol) AS ( SELECT DISTINCT roles.oid FROM pg_catalog.pg_database db INNER JOIN pg_catalog.pg_shdepend dep ON (db.oid = dep.dbid) INNER JOIN pg_catalog.pg_roles roles ON (dep.refobjid = roles.oid) WHERE dep.refclassid = 'pg_catalog.pg_authid'::REGCLASS AND ARRAY[db.datname] <@ $1 UNION ALL SELECT am.member AS rol FROM pg_catalog.pg_auth_members am INNER JOIN deps ON (am.roleid = deps.rol) ) SELECT 1::SMALLINT AS orden, 'CREATE ROLE "' || r.rolname || '" ' CASE WHEN NOT r.rolsuper THEN 'NO' ELSE '' END || 'SUPERUSER ' || CASE WHEN NOT r.rolinherit THEN 'NO' ELSE '' END || 'INHERIT ' || CASE WHEN NOT r.rolcreaterole THEN 'NO' ELSE '' END || 'CREATEROLE ' || CASE WHEN NOT r.rolcreatedb THEN 'NO' ELSE '' END || 'CREATEDB ' || CASE WHEN NOT r.rolcanlogin THEN 'NO' ELSE '' END || 'LOGIN ' || 'CONNECTION LIMIT ' || r.rolconnlimit || CASE WHEN r.rolvaliduntil IS NULL THEN '' ELSE E' VALID UNTIL \'' || rolvaliduntil::text || E'\'' END || ';' AS sentencia FROM pg_catalog.pg_roles r INNER JOIN deps on (r.oid = deps.rol) UNION SELECT 2::SMALLINT AS orden, 'ALTER ROLE "' || r.rolname || '" SET ' || array_to_string( r.rolconfig, ';ALTER ROLE "' || r.rolname || '" SET ' ) || ';' AS sentencia FROM pg_catalog.pg_roles r INNER JOIN deps on (r.oid = deps.rol) WHERE rolconfig IS NOT NULL UNION SELECT 2::SMALLINT AS orden, 'GRANT "' || rol.rolname || '" TO "' || member.rolname || '"' || CASE WHEN am.admin_option THEN 'WITH ADMIN OPTION' ELSE '' END || ';' AS sentencia FROM pg_catalog.pg_auth_members am INNER JOIN pg_catalog.pg_roles rol ON (am.roleid = rol.oid) INNER JOIN pg_catalog.pg_roles member ON (am.member = member.oid) INNER JOIN deps on (rol.oid = deps.rol) ORDER BY orden, sentencia ASC; $BODY$ LANGUAGE sql VOLATILE COST 100 ROWS 1000; ALTER FUNCTION unpriv_dump_db_roles(name[]) OWNER TO postgres;
-- Sent via pgsql-general mailing list (pgsql-general@xxxxxxxxxxxxxx) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-general