db/postgresql_scanner.py (77 lines of code) (raw):

"""Copyright 2021 Google LLC. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. version 1.5.1 """ import json import logging from db.db_info import Query import psycopg2 class PostgreSQLScanner: """Helper class to scan PostgreSql database.""" def __init__(self, region): self.region = region def scan(self, rds_info, output): """Connects to PostgreSql database and collects data. Args: rds_info: Dictionary object with database connection information output: Dictionary object to store the collected data Returns: True if collection is successful False otherwise """ queries = self.get_queries() collection = {} try: conn = psycopg2.connect( host=rds_info.host, port=rds_info.port, database=rds_info.dbname, user=rds_info.username, password=rds_info.password) for query in queries: try: cur = conn.cursor() cur.execute(query.query) row_headers = [x[0] for x in cur.description] query_results = cur.fetchall() if query.query_type == "PostgreSQL_Version": version = query_results[0][0] collection["version"] = version output["version"] = version else: result_array = [] for result in query_results: result_array.append( dict(zip(row_headers, self.check_result(result)))) collection[query.query_type] = result_array except Exception as ex: # pylint: disable=broad-except if query.query_type == "PostgreSQL_Version": raise ex logging.error("Failed to run %s", query.query_type) logging.error(ex) output["PostgreSQL"] = collection return True except Exception as e: # pylint: disable=broad-except logging.error("Received an unexpected error") logging.error(e) return False def check_result(self, result): """Converts any non-primitive types in the resultset to JSON strings. Args: result: Query result set Returns: Updated results set """ res = [] for i, item in enumerate(result): if isinstance(item, list): res.append(json.dumps(item)) else: res.append(result[i]) return res def get_queries(self): """Gets a list of data collection queries. Returns: List of data collection queries """ return [ Query("PostgreSQL_Version", """ select version() """), Query("PostgreSQL_Extensions", """ select * from pg_extension """), Query( "PostgreSQL_ForeignTables", """ select n.nspname AS "Schema", /* for foreign tables */ c.relname AS "Table", s.srvname AS "Server" FROM pg_catalog.pg_foreign_table ft INNER JOIN pg_catalog.pg_class c ON c.oid = ft.ftrelid INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace INNER JOIN pg_catalog.pg_foreign_server s ON s.oid = ft.ftserver WHERE pg_catalog.pg_table_is_visible(c.oid) ORDER BY 1, 2 """), Query( "PostgreSQL_TablesNoPK", """ select round(pg_relation_size(relid)/( 1024.0 * 1024 * 1024 ), 2) as size, relname from pg_stat_user_tables where relid not in (select indrelid from pg_index where indisprimary) """), Query( "PostgreSQL_UserTableStats", """ select pg_total_relation_size(relid) as total_size, pg_relation_size(relid) as size, * from pg_stat_user_tables """), Query( "PostgreSQL_AWSExtensionSchemaCheck", """ select exists ( select FROM information_schema.tables WHERE table_schema = 'aws_oracle_ext' and table_name = 'versions' ) as SCTOracleExtensionExists """), Query( "PostgreSQL_AWSExtensionVersion", """ SELECT componentversion as AWSExtensionVersion FROM aws_oracle_ext.versions as extVersion """), Query( "PostgreSQL_AWSExtensionUsageDetails", """ (with alias1 as ( select alias1.proname, ns.nspname, case when relkind = 'r' then 'TABLE' END AS objType, depend.relname, pg_get_expr( pg_attrdef.adbin, pg_attrdef.adrelid ) as def from pg_depend inner join ( select distinct pg_proc.oid as procoid, nspname || '.' || proname as proname, pg_namespace.oid from pg_namespace, pg_proc where nspname = 'aws_oracle_ext' and pg_proc.pronamespace = pg_namespace.oid ) alias1 on pg_depend.refobjid = alias1.procoid inner join pg_attrdef on pg_attrdef.oid = pg_depend.objid inner join pg_class depend on depend.oid = pg_attrdef.adrelid inner join pg_namespace ns on ns.oid = depend.relnamespace ), alias2 as ( select alias1.nspname as schema, alias1.relname as table_name, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) ) [1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' ) select alias2.schema as schemaName, 'N/A' as language, 'TableDefaultConstraints' as type, alias2.table_name as typeName, alias2.funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 group by alias2.schema, alias2.table_name, alias2.funcname ) UNION (with alias1 as ( select pgc.conname as constraint_name, ccu.table_schema as table_schema, ccu.table_name, ccu.column_name, pg_get_constraintdef(pgc.oid) as def from pg_constraint pgc join pg_namespace nsp on nsp.oid = pgc.connamespace join pg_class cls on pgc.conrelid = cls.oid left join information_schema.constraint_column_usage ccu on pgc.conname = ccu.constraint_name and nsp.nspname = ccu.constraint_schema where contype = 'c' order by pgc.conname ), alias2 as ( select alias1.table_schema, alias1.constraint_name, alias1.table_name, alias1.column_name, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' ) select alias2.table_schema as schemaName, 'N/A' as language, 'TableCheckConstraints' as type, alias2.table_name as typeName, alias2.funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 group by alias2.table_schema, alias2.table_name, alias2.funcname ) UNION (with alias1 as ( select alias1.proname, nspname, case when relkind = 'i' then 'INDEX' END AS objType, depend.relname, pg_get_indexdef(depend.oid) def from pg_depend inner join ( select distinct pg_proc.oid as procoid, nspname || '.' || proname as proname, pg_namespace.oid from pg_namespace, pg_proc where nspname = 'aws_oracle_ext' and pg_proc.pronamespace = pg_namespace.oid ) alias1 on pg_depend.refobjid = alias1.procoid inner join pg_class depend on depend.oid = pg_depend.objid inner join pg_namespace ns on ns.oid = depend.relnamespace where relkind = 'i' ), alias2 as ( select alias1.nspname as Schema, alias1.relname as IndexName, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' ) select alias2.Schema as schemaName, 'N/A' as language, 'TableIndexesAsFunctions' as type, alias2.IndexName as typeName, alias2.funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 group by alias2.Schema, alias2.IndexName, alias2.funcname ) UNION (with alias1 as ( select alias1.proname, nspname, case when depend.relkind = 'v' then 'VIEW' END AS objType, depend.relname, pg_get_viewdef(depend.oid) def from pg_depend inner join ( select distinct pg_proc.oid as procoid, nspname || '.' || proname as proname, pg_namespace.oid from pg_namespace, pg_proc where nspname = 'aws_oracle_ext' and pg_proc.pronamespace = pg_namespace.oid ) alias1 on pg_depend.refobjid = alias1.procoid inner join pg_rewrite on pg_rewrite.oid = pg_depend.objid inner join pg_class depend on depend.oid = pg_rewrite.ev_class inner join pg_namespace ns on ns.oid = depend.relnamespace where not exists( select 1 from pg_namespace where pg_namespace.oid = depend.relnamespace and nspname = 'aws_oracle_ext' ) ), alias2 as ( select alias1.nspname as Schema, alias1.relname as ViewName, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' ) select alias2.Schema as schemaName, 'N/A' as language, 'Views' as type, alias2.ViewName as typeName, alias2.funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 group by alias2.Schema, alias2.ViewName, alias2.funcname ) UNION (with alias1 as ( select distinct n.nspname as function_schema, p.proname as function_name, l.lanname as function_language, ( select 'Y' from pg_trigger where tgfoid = (n.nspname || '.' || p.proname) :: regproc ) as Trigger_Func, lower(pg_get_functiondef(p.oid) :: text) as def from pg_proc p left join pg_namespace n on p.pronamespace = n.oid left join pg_language l on p.prolang = l.oid left join pg_type t on t.oid = p.prorettype where n.nspname not in ( 'pg_catalog', 'information_schema', 'aws_oracle_ext' ) and p.prokind not in ('a', 'w') and l.lanname in ('sql', 'plpgsql') order by function_schema, function_name ), alias2 as ( select alias1.function_schema, alias1.function_name, alias1.function_language, alias1.Trigger_Func, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' and Trigger_Func = 'Y' ) select function_schema as schemaName, function_language as language, 'Triggers' as type, function_name as typeName, funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 where 1 = 1 group by function_schema, function_language, function_name, funcname order by 3, 4 desc ) UNION (with alias1 as ( select distinct n.nspname as function_schema, p.proname as function_name, l.lanname as function_language, ( select 'Y' from pg_trigger where tgfoid = (n.nspname || '.' || p.proname) :: regproc ) as Trigger_Func, lower(pg_get_functiondef(p.oid) :: text) as def from pg_proc p left join pg_namespace n on p.pronamespace = n.oid left join pg_language l on p.prolang = l.oid left join pg_type t on t.oid = p.prorettype where n.nspname not in ( 'pg_catalog', 'information_schema', 'aws_oracle_ext' ) and p.prokind not in ('a', 'w', 'p') and l.lanname in ('sql', 'plpgsql') order by function_schema, function_name ), alias2 as ( select alias1.function_schema, alias1.function_name, alias1.function_language, alias1.Trigger_Func, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' and alias1.Trigger_Func is NULL ) select function_schema as schemaName, function_language as language, 'Functions' as type, function_name as typeName, funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 where 1 = 1 group by function_schema, function_language, function_name, funcname order by 3, 4 desc ) UNION (with alias1 as ( select distinct n.nspname as function_schema, p.proname as function_name, l.lanname as function_language, ( select 'Y' from pg_trigger where tgfoid = (n.nspname || '.' || p.proname) :: regproc ) as Trigger_Func, lower(pg_get_functiondef(p.oid) :: text) as def from pg_proc p left join pg_namespace n on p.pronamespace = n.oid left join pg_language l on p.prolang = l.oid left join pg_type t on t.oid = p.prorettype where n.nspname not in ( 'pg_catalog', 'information_schema', 'aws_oracle_ext' ) and p.prokind not in ('a', 'w', 'f') and l.lanname in ('sql', 'plpgsql') order by function_schema, function_name ), alias2 as ( select alias1.function_schema, alias1.function_name, alias1.function_language, alias1.Trigger_Func, alias2.* from alias1 cross join lateral ( select i as funcname, cntgroup as cnt from ( select ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] i, count(1) cntgroup group by ( regexp_matches( alias1.def, 'aws_oracle_ext[.][a-z]*[_,a-z,$,""]*', 'ig' ) )[1] ) t ) as alias2 where def ~*'aws_oracle_ext.*' ) select function_schema as schemaName, function_language as language, 'Procedures' as type, function_name as typeName, funcname as AWSExtensionDependency, sum(cnt) as SCTFunctionReferenceCount from alias2 where 1 = 1 group by function_schema, function_language, function_name, funcname order by 3, 4 desc ) """), ]