diff --git a/.travis.yml b/.travis.yml index bf630a5..99731e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,18 @@ jobs: env: SUPPORTED_PG_VERSIONS=10.4 script: sudo make test + - stage: # Intentionally left blank to parallelize + env: SUPPORTED_PG_VERSIONS=11.11 + script: sudo make test + + - stage: # Intentionally left blank to parallelize + env: SUPPORTED_PG_VERSIONS=12.6 + script: sudo make test + + - stage: # Intentionally left blank to parallelize + env: SUPPORTED_PG_VERSIONS=13.2 + script: sudo make test + - stage: # Intentionally left blank to parallelize install: pip install -e . -r requirements-docs.txt script: make docs diff --git a/Makefile b/Makefile index 3c8f97e..5189229 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: attach build build_tester clean coverage create_network docs psql release_pypi release_pypitest release_quay remove_network start_postgres stop_postgres test test_one_pg_version test27 test36 view_docs wait_for_postgres -SUPPORTED_PG_VERSIONS ?= 9.5.13 9.6.4 10.4 +SUPPORTED_PG_VERSIONS ?= 9.5.13 9.6.4 10.4 11.11 12.6 13.2 # The default Postgres that will be used in individual targets POSTGRES_VERSION ?= 10.4 diff --git a/requirements.txt b/requirements.txt index 4fcc6a0..f808bfe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Cerberus==1.1 click==6.7 Jinja2==2.10.1 -MarkupSafe==1.0 +MarkupSafe==1.1.1 psycopg2==2.7.3 PyYAML==5.2 diff --git a/tests/conftest.py b/tests/conftest.py index 4da78df..2b1b995 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -67,7 +67,9 @@ def drop_users_and_objects(cursor): WHERE rolname NOT IN ( 'test_user', 'postgres', 'pg_signal_backend', -- Roles introduced in Postgres 10: - 'pg_monitor', 'pg_read_all_settings', 'pg_read_all_stats', 'pg_stat_scan_tables' + 'pg_monitor', 'pg_read_all_settings', 'pg_read_all_stats', 'pg_stat_scan_tables', + -- Roles introduced in Postgres 11: + 'pg_execute_server_program', 'pg_read_server_files', 'pg_write_server_files' ); """) users = [u[0] for u in cursor.fetchall()] @@ -79,42 +81,64 @@ def drop_users_and_objects(cursor): @pytest.fixture def base_spec(cursor): """ A spec with the existing state of the test database before anything has been done """ - spec = dedent(""" - postgres: - attributes: - - BYPASSRLS - - CREATEDB - - CREATEROLE - - REPLICATION - can_login: true - is_superuser: true - owns: - schemas: - - information_schema - - pg_catalog - - public - tables: - - information_schema.* - - pg_catalog.* - privileges: - schemas: - write: + cursor.execute("SELECT substring(version from 'PostgreSQL ([0-9.]*) ') FROM version()") + pg_version = int(cursor.fetchone()[0].split('.')[0]) + spec = "" + + # Before version 10 Postgres creates a `postgres` user plus the `test_user` + if pg_version <= 10: + spec += dedent(""" + postgres: + attributes: + - BYPASSRLS + - CREATEDB + - CREATEROLE + - REPLICATION + can_login: true + is_superuser: true + owns: + schemas: - information_schema - pg_catalog - public + tables: + - information_schema.* + - pg_catalog.* + privileges: + schemas: + write: + - information_schema + - pg_catalog + - public + + test_user: + attributes: + - PASSWORD "test_password" + can_login: yes + is_superuser: yes + """) + + # In version 10 we have some more roles to deal with + if pg_version == 10: + spec += dedent(""" + pg_monitor: + member_of: + - pg_read_all_settings + - pg_read_all_stats + - pg_stat_scan_tables - test_user: - attributes: - - PASSWORD "test_password" - can_login: yes - is_superuser: yes + pg_read_all_settings: + + pg_read_all_stats: + + pg_stat_scan_tables: """) - # Postgres 10 introduces several new roles that we have to account for - cursor.execute("SELECT substring(version from 'PostgreSQL ([0-9.]*) ') FROM version()") - pg_version = cursor.fetchone()[0] - if pg_version.startswith('10.'): - spec += dedent(""" + # In version 11 and onwards Posgres only creates the `test_user` we specify + # Postgres 11 introduces several new roles that we have to account for + if pg_version >= 11: + spec = dedent(""" + pg_execute_server_program: pg_read_all_settings: @@ -127,7 +151,35 @@ def base_spec(cursor): - pg_read_all_settings - pg_stat_scan_tables - pg_read_all_stats - """) + + pg_read_server_files: + + pg_write_server_files: + + test_user: + attributes: + - BYPASSRLS + - CREATEDB + - CREATEROLE + - PASSWORD "test_password" + - REPLICATION + can_login: true + is_superuser: true + owns: + schemas: + - information_schema + - pg_catalog + - public + tables: + - information_schema.* + - pg_catalog.* + privileges: + schemas: + write: + - information_schema + - pg_catalog + - public + """) return spec @@ -141,7 +193,7 @@ def spec_with_new_user(tmpdir, base_spec): {new_user}: has_personal_schema: yes member_of: - - postgres + - test_user privileges: tables: read: diff --git a/tests/test_context.py b/tests/test_context.py index 73247a8..d267b02 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -14,6 +14,8 @@ TABLES = tuple('table{}'.format(i) for i in range(6)) SEQUENCES = tuple('seq{}'.format(i) for i in range(6)) DUMMY = 'foo' +TEST_USER='test_user' +POSTGRES_USER='postgres' @run_setup_sql( @@ -322,14 +324,27 @@ def test_get_all_personal_schemas(cursor): def test_get_all_role_attributes(cursor): dbcontext = context.DatabaseContext(cursor, verbose=True) - expected = set(['test_user', 'postgres', ROLES[0], ROLES[1]]) - pg_version = dbcontext.get_version_info().postgres_version + expected = set(['test_user', ROLES[0], ROLES[1]]) + + pg_version = int(dbcontext.get_version_info().postgres_version.split('.')[0]) + + if pg_version <= 10: + expected.update(set([ + 'postgres'] + )) + # Postgres 10 introduces several new roles that we have to account for - if pg_version.startswith('10.'): + if pg_version >= 10: expected.update(set([ 'pg_read_all_settings', 'pg_stat_scan_tables', 'pg_read_all_stats', 'pg_monitor'] )) + # Postgres 11 introduces several new roles that we have to account for + if pg_version >= 11: + expected.update(set([ + 'pg_read_server_files', 'pg_write_server_files', 'pg_execute_server_program'] + )) + actual = dbcontext.get_all_role_attributes() assert set(actual.keys()) == expected @@ -385,15 +400,18 @@ def test_is_superuser(all_role_attributes, expected): ]) def test_get_all_schemas_and_owners(cursor): dbcontext = context.DatabaseContext(cursor, verbose=True) + pg_version = int(dbcontext.get_version_info().postgres_version.split('.')[0]) + + expected_owner = TEST_USER if pg_version >= 11 else POSTGRES_USER expected = { common.ObjectName(SCHEMAS[0]): ROLES[0], common.ObjectName(SCHEMAS[1]): ROLES[0], common.ObjectName(SCHEMAS[2]): ROLES[1], common.ObjectName(ROLES[1]): ROLES[1], # These already existed - common.ObjectName('public'): 'postgres', - common.ObjectName('information_schema'): 'postgres', - common.ObjectName('pg_catalog'): 'postgres', + common.ObjectName('public'): expected_owner, + common.ObjectName('information_schema'): expected_owner, + common.ObjectName('pg_catalog'): expected_owner, } actual = dbcontext.get_all_schemas_and_owners() @@ -420,9 +438,10 @@ def test_get_all_memberships(cursor): dbcontext = context.DatabaseContext(cursor, verbose=True) expected = set([('role1', 'role0'), ('role2', 'role1')]) - pg_version = dbcontext.get_version_info().postgres_version + pg_version = int(dbcontext.get_version_info().postgres_version.split('.')[0]) + # Postgres 10 introduces several new roles and memberships that we have to account for - if pg_version.startswith('10.'): + if pg_version >= 10: expected.update(set([ ('pg_monitor', 'pg_stat_scan_tables'), ('pg_monitor', 'pg_read_all_stats'), diff --git a/tests/test_core_configure.py b/tests/test_core_configure.py index c3374dc..3a000c9 100644 --- a/tests/test_core_configure.py +++ b/tests/test_core_configure.py @@ -78,7 +78,7 @@ def test_configure_live_mode_works(capsys, cursor, spec_with_new_user, db_config assert cursor.rowcount == expected if live_mode: - cursor.execute(Q_HAS_ROLE.format(NEW_USER, 'postgres')) + cursor.execute(Q_HAS_ROLE.format(NEW_USER, 'test_user')) assert cursor.rowcount == expected cursor.execute(Q_HAS_PRIVILEGE.format(NEW_USER, 'pg_catalog.pg_class'))